הקטגוריה בתוסף אינה תווית. היא כלל הניתוב.
שישים ושתיים חבילות מבית היוצר נשלחות היום, בשש קטגוריות בדיוק: 27 widget, 15 library, 10 ui, 5 graphics, 3 dbs ו-2 framework. הקטגוריה אינה מוסכמת שמות ואינה תגית. היא שדה plugin_type ברשומת התוסף, ושלוש מערכות נפרדות קוראות אותו: העורך מחליט באיזה שקע התוסף מופיע, האימות מחליט אילו קבצים עליו לייצא, וזמן הריצה מחליט על איזה משלושת משטחי ההרצה הקוד שלו רץ. שום דבר אינו מחווט ביד אחרי התקנה. חבילה אחת בקטלוג מוכיחה שהשדה הוא הסמכות ולא השם, משום שהתיקייה שלה והייצוא שלה אומרים שניהם widget בעוד ששם ה-npm שפורסם שלה אומר ui, וכל משטח ב-Studio מנתב אותה לפי השדה. פרויקט חדש מתחיל עם שני תוספים כבר מותקנים, מנהל ההתקן המקורי לנתונים ומסגרת Tailwind. כל מה שמעבר לשניים האלה הוא התקנה אחת, והקטגוריה קובעת היכן הוא נוחת.

חבילות תוסף שנשלחו, על פני שש קטגוריות בדיוק
מתודות שמוצהרות על ידי 42 החבילות שמייצאות מתודות בכלל, על פני 632 שדות
שמות קבצים שהפלטפורמה מחפשת אחרי התקנה. רובן שולחות שניים
חבילות ששולחות קוד שרת. אף תוסף widget או ui אינו עושה זאת
שש קטגוריות, והקטגוריה קובעת את שני הקצוות.
מתקינים אחד והוא מופיע במקום הנכון בלי שום דבר להגדיר, משום ששדה יחיד נקרא על ידי שישה חלקים שונים של העורך ועל ידי שלוש נקודות כניסה שונות של זמן הריצה. התמונה למטה היא החוזה, לא הבורר: בצד אחד כיצד מגיעים לתוסף מהקטגוריה הזאת, ובצד השני הייצוא המדויק שזמן הריצה קורא לו ובאיזו חתימה. יש לקרוא זאת כשישה חוזים עצמאיים שתלויים בשדה אחד ולא כממשק תוסף אחד בשישה טעמים, משום שזה מה שזה. תוסף widget ותוסף ui אינם מייצאים אותה צורה, אינם מקבלים אותם ארגומנטים ואינם מופיעים באותו מקום, ותוסף graphics אפילו אינו שולח את הקובץ שחמשת האחרים נבנים סביבו.
חוזה אחד, שש צורות, והתאים הריקים הם הטיעון.
אחרי ה-npm install הפלטפורמה מחפשת שישה שמות קבצים, index.mjs, setup.mjs, studio.mjs, runtime.mjs, server.mjs ו-style.css. היא מסתכלת קודם בשורש החבילה ואחר כך בתוך dist, רושמת באיזה מהשניים היא מצאה כדי שזמן הריצה ידע אם לטעון גם את גיליון הסגנון הבנוי, ואז מייבאת כל קובץ שמצאה חוץ מ-style.css ומ-server.mjs. הייבוא הזה הוא בדיקת האם זה נטען האמיתית: תוסף שה-index.mjs שלו זורק שגיאה בזמן הייבוא נכשל באימות בו במקום ונוחת עם טקסט השגיאה שמור ברשומה שלו, וזו הסיבה שתוסף שבור מציג לך סיבה ולא משבצת ריקה. הטבלה למטה היא מה ששש הקטגוריות באמת שולחות. סימני האיקס נושאים חלק מהטיעון בדיוק כמו הטקסט: טור ה-server.mjs ריק בכל קטגוריה שפונה לדפדפן, וזו העובדה שגם משטחי ההרצה וגם שער הצד השלישי נשענים עליה. קובץ אחד הושמט מהטבלה במכוון. studio.mjs נשלח על ידי חבילה אחת בדיוק בכל הקטלוג, מנהל ההתקן המקורי לנתונים, שבו הוא משטח שרת נפרד לזמן העיצוב עבור העורך, ולכן הוא מקבל את המשפט הזה במקום טור של חמישה סימני איקס.
index.mjs חייב לייצא | runtime.mjs חייב לייצא | server.mjs | setup.mjs | |
|---|---|---|---|---|
| widget, 27 נשלחו | methods, ובנוסף capabilities אופציונלי | פונקציה אחת לכל מתודה | ||
| library, 15 נשלחו | methods | נציג אחד לכל מתודה | 12 מתוך 15 | 12 מתוך 15 |
| ui, 10 נשלחו | properties | fn, ואופציונלית destroy | ||
| graphics, 5 נשלחו | tabs | |||
| dbs, 3 נשלחו | tabs ו-public_filter | 3 מתוך 3 | ||
| framework, 2 נשלחו | tabs, core ופרימיטיבי הממשק | core, חובה. resources, אופציונלי |
התקנה היא npm install אמיתי לתוך תיקייה ששייכת לפרויקט שלך.
שום דבר כאן אינו מטאפורה להתקנה. רשומה נכתבת אל מפת התוספים שנרכשו בפרויקט, ונושאת את דגל ההתקנה, את הגרסה, את שם תוסף ההורה במקום שיש כזה, את מניפסט הקבצים, את סוג התוסף ואת דגל הרקע. אחר כך npm install רץ בתוך תיקיית תוספים ששייכת לפרויקט הזה בלבד, כשכל ארגומנט מצוטט לצורך המעטפת. אחר כך התיקייה הזאת מסונכרנת החוצה אל השרתים האזוריים. אחר כך החבילה המותקנת נסרקת עד עומק עשר וכל קובץ נקרא אל מסד נתונים של CouchDB ששייך לפרויקט, לפי הנתיב היחסי שלו, והגדרות קיימות נישאות דרך העדכון ולא נדרסות.
- כל פרויקט מקבל עץ node_modules משלו, וחבילה שההורדה שלה נכשלת מסירה רק את עצמה. יש בקוד הערה שאומרת למה: גרסה מוקדמת יותר מחקה את כל העץ בכישלון, מה שהסיר את כל שאר התוספים שהיו לפרויקט משום שחבילה אחת לא הצליחה לרדת.
- הסרה הופכת את דגל ההתקנה, מריצה npm uninstall, ומרוקנת את מסמך התוסף בצד הפרויקט עד למזהה שלו, לגרסה שלו ולהגדרות שלו. התצורה שורדת הסרה והתקנה מחדש מאוחר יותר, וזה ההבדל בין הסרת תוסף לבין איבוד פרטי ההזדהות שהקלדת בו.
- שום דבר אינו נארז לתוך בניית האפליקציה. זמן הריצה מביא כל קובץ תוסף לכל הפעלה דרך נתיב /plugins, שמגיש מתוך העץ הייעודי לפרויקט ודורש או מזהה אפליקציה שניתן לפתור או אסימון תקף. עבור קובץ מקור שאינו ארוז, הנתיב הזה כותב מחדש את ייבואי ה-ESM היחסיים בדרך החוצה, כדי שדפדפן יוכל לטעון אותם בכלל. מה שהפריסה כן אורזת הוא מפת ההגדרות, שנאספת אל נתוני האתחול של האפליקציה.
- בהפעלת פיתוח זמן הריצה מנסה את src לפני dist עבור כל קובץ .mjs ונופל חזרה אל dist, ולכן עריכת מקור התוסף מופיעה בלי בנייה מחדש. בכל מקום אחר המניפסט מכריע, ובמקום שבו הוא רושם שהעותק הבנוי הוא זה שנמצא, dist מנצח.
- הרשומה נכתבתהתקנה, גרסה, שם ההורה, מניפסט הקבצים, סוג התוסף ודגל הרקע
- npm install רץאל תיקיית התוספים של הפרויקט הזה בלבד, כשכל ארגומנט מצוטט לצורך המעטפת
- העץ מסונכרן החוצהתיקיית התוספים של הפרויקט נדחפת אל השרתים האזוריים המרוחקים
- החבילה מצולמתכל קובץ נקרא אל מסד הנתונים של הפרויקט, לפי הנתיב היחסי שלו
תוסף ילד נוקב בהורה שלו. ההורה חייב לנקוב בילד בחזרה.
תוסף רשאי להצהיר על הורה אחד, והפלטפורמה מסרבת להתקין אותו אלא אם שתי הצהרות נפרדות מסכימות. הילד נוקב בהורה דרך שדה תלות ברשומה שלו עצמו. ההורה חייב למנות את הילד בחזרה, במערך תוספים מורשים ברשומת ההורה. אף הצהרה אינה מספיקה לבדה. הבדיקות רצות בסדר וכל כישלון חוזר כסירוב ממוספר ולא כעקבות מחסנית, וכולן רצות לפני ש-npm מופעל, ולכן התקנה שנדחתה אינה משאירה דבר על הדיסק. אותן שתי בדיקות רצות שוב מאוחר יותר, בכל קריאה ובכל כתיבה של תצורת תוסף, ושוב בשרת לפני שמתודת תוסף מורשית לרוץ.
התוסף מחליט מה התצורה שלו עצמו רשאית לספר לדפדפן.
תצורת התוסף אינה מוחזקת במרשם המשותף. היא חיה כמסמך הגדרות בתוך מסד הנתונים של CouchDB של הפרויקט שהגדיר אותה, נכתבת על ידי טופס ההגדרות ונקראת בחזרה על ידי שני הצדדים. כשהקריאה היא ציבורית, הפלטפורמה מייבאת את התוסף וקוראת לפונקציה שהתוסף מייצא בעצמו, ושולחת בחזרה רק את מה שאותה פונקציה החזירה. אין רשימת היתר של מפתחות בטוחים ברמת הפלטפורמה, וזה גם החוזק של הסידור הזה וגם המגבלה שלו: מחבר התוסף הוא זה שיודע איזה מהשדות הוא הסוד, ומחבר התוסף הוא זה שכותב את המסנן.
שלושה משטחים, וזה של השרת נבדק לפני הייבוא, לא אחריו.
קוד תוסף רץ בשלושה מקומות בדיוק. בדפדפן, כ-widget שבבעלותו מכל שזמן הריצה יצר עבורו, כתוסף ui שמעטר אלמנט שכבר היה לך, כמסגרת, או כתוסף רקע שמחובר למחזור החיים של האפליקציה. בשירות תוספים ייעודי, תהליך משלו על פורט 3008, עבור מתודות שרת של library. ובניתוב נפרד עבור מנהלי ההתקן של הנתונים, שאינו עובר דרך אותו שירות לעולם ושומר את החיבור החי שלו במטמון לכל אפליקציה ולכל תוסף. שני מסלולי השרת רצים תחת תקרה של שישים שניות.
- מתודת library היא שני קבצים שעושים שתי עבודות. הנציג בדפדפן ממיר את השדות שהוצהרו, אוכף את שדות החובה ושולח; קובץ השרת עושה את העבודה ומחזיק את פרטי ההזדהות. הפיצול הזה הוא כל הסיבה לכך שלמפתח סודי של Stripe אין מסלול בדפדפן: הוא נקרא ב-server.mjs, מתוך מסמך ההגדרות שנמסר, ובשום מקום אחר.
- לפני שהשירות טוען קובץ שרת של תוסף הוא שואל אם המפרסם הוא מבית היוצר, והוא שואל לפני הייבוא ולא אחריו. ייבוא מודול כבר מריץ את הקוד ברמה העליונה שלו, ולכן בדיקה שממוקמת אחריו אינה בודקת דבר. מתודות שרת של צד שלישי נדחות כברירת מחדל והמתג שהיה מתיר אותן מוגדר false בשתי התצורות, ולכן תוספי צד שלישי הם היום משטח מוצר בצד הדפדפן.
- תוסף רקע מקבל חמישה ווים של מחזור חיים, שנורים בסדר beforeInit, initialized, beforeMounted, mounted ו-systemReady סביב הפעלת האפליקציה, וכל אחד מהם מקבל את ההפעלה, את אובייקט האפליקציה, את ה-api ואת נתוני ההגדרות. תיבת הסימון של הרצה ברקע מוצעת רק בתוסף library, והרישום מדולג לחלוטין במצב חבילה רזה.
- השדות שמתודה מצהירה עליהם עושים עבודה כפולה. ב-Full Stack VPS הפלטפורמה עוברת על התוספים המותקנים ורושמת כל מתודה ככלי MCP, בשם התוסף ואחריו המתודה, כשסכמת הקלט שלו נבנית מאותם שדות מוצהרים והתיאור שלו נלקח מחלונית העזר שהמתודה כבר נשאה.
- שלב תהליך העבודה נורהנוקב בתוסף, במתודה ובמאפיינים עבורה
- הנציג בדפדפן מאמתהשדות מומרים לסוגים שהוצהרו, ושדות החובה נאכפים
- הוא שולח לשירות התוספיםהאסימון נבדק מול הפעלה חיה במסד הנתונים של הפרויקט שלך עצמך, לעולם לא ממטמון
- קובץ השרת רץ, עם תקרהשישים שניות, כשמסמך ההגדרות נמסר פנימה
תוסף בדפדפן מקבל אובייקט אחד, וזה כולו.
זמן הריצה בונה אובייקט פרמטרים אחד לכל קריאה ומוסר אותו כארגומנט השני. widget אינו מושיט יד אל משתנה גלובלי כדי למצוא את הפלטפורמה, ואין ממשק תוסף נפרד ללמוד מעבר למה שמגיע באותו אובייקט. המארח יוצר את ה-div של המכל, קורא את מזהה התוסף ואת שם המתודה מהצומת, מייבא את index.mjs ובודק את היכולות שהוצהרו מול הסביבה הנוכחית, פותר וממיר כל שדה שהוצהר ואוכף את שדות החובה, טוען את style.css ואת dist/runtime.css כשהמניפסט רשם אותם, מביא את הגדרות התוסף, ורק אז מייבא את runtime.mjs וממתין למתודה. שגיאה שנזרקת בתוך אותה מתודה נתפסת ומדווחת ולא מושארת להפיל את העמוד. הצורה שלמטה היא זו של widget. תוסף ui מקבל משהו שונה מספיק כדי שיהיה שווה להדפיס אותו לצידו, משום שההבדל הזה הוא כל ההבחנה בין שתי הקטגוריות: ל-widget ניתן מכל שבבעלותו, ולתוסף ui ניתן אלמנט שכבר היה לך.
מסלול המפתחים הוא npm, ושום דבר בו אינו תוצרת בית.
אין כאן שרשרת כלים קניינית ואין ממשק שורת פקודה של תוספים ללמוד. כל המסלול הוא חבילת ארגז חול שפורסמה שמפתחים מולה, בניית ספרייה רגילה ב-Vite, ו-npm. הפלטפורמה פוגשת את התוסף שלך רק בסוף המסלול, כשרושמים את שם החבילה.
- 01
פיתוח מול ארגז החול
הרתמה היא חבילה אמיתית שפורסמה, @xuda.io/plugin-sandbox בגרסה 1.0.27. היא בונה עמוד בשתי חלוניות: טופס Plugin Configurator בצד אחד, שנוצר מהשדות שהמתודה שלך מצהירה עליהם, משנה גודל בגרירה באמצע, וחלונית Live Preview בצד השני עם ה-div של המכל שיימסר למתודה שלך. תפריט נפתח בוחר איזו מהמתודות שלך לבדוק. כל שדה שמצהיר על סוג התצוגה editor מקבל עורך Monaco אמיתי שמחובר לאירוע השינוי שלו, כשהשפה נלקחת מפרמטרי התצוגה ו-javascript היא ברירת המחדל. האימוץ חלקי ושווה לומר זאת בפשטות: שלוש עשרה מתוך שישים ושתיים החבילות שנשלחו מפתחות מולה, ועשרים שולחות עמוד בדיקה משלהן תחת tests.
- 02
בנייה עם Vite, אל תוך dist
כל חבילה שמשתמשת בארגז החול נושאת את אותה תצורת vite. נקודת הכניסה של הספרייה היא src/runtime.mjs, הפורמט הוא ES, ייבואים דינמיים מוטבעים פנימה, הפלט נוחת ב-dist, כל גיליון סגנון שנפלט משנה את שמו ל-runtime.css, ושלב העתקה קטן מעביר את כל מה שב-src חוץ מ-runtime.mjs אל dist לצידו. המוסכמה הזאת היא בדיוק הסיבה לכך שהפלטפורמה רושמת דגל dist ודגל css לכל קובץ כשהיא בודקת את החבילה שלך, והסיבה לכך שזמן הריצה יודע לטעון גם את dist/runtime.css וגם את המודול.
- 03
פרסום, ואחר כך רישום
כל שישים ושניים קובצי package.json שנשלחו נושאים סקריפט פרסום זהה: npm version patch, ואחריו npm publish. semver פשוט בלי שום דבר מעליו, וסימני המים הגבוהים מספרים כמה שימוש נעשה בזה. מנהל ההתקן המקורי לנתונים נמצא ב-1.0.320, מסגרת Tailwind ב-1.0.128, ובונה האפליקציות של ה-AI ב-1.0.74. רישום מול הפלטפורמה הוא שלב נפרד שמושך את המניפסט החי מ-npm, לוקח את הגרסה משם ולא מהטופס שלך, ולוקח את סמל השוק מתוך שדה svg_icon ב-package.json שלך אם השארת את הסמל בטופס ריק.
או שמתארים אותו, ומערכת התוספים בונה תוסף בעזרת תוסף.
הבונה אינו כלי שעוזר הצ'אט יכול להגיע אליו בכוחות עצמו. שתי המתודות שלו מסומנות פרטיות, ולכן שום דבר אינו חושף אותן ככלי סוכן; לשונית התוספים מכינה את הפעולה והעוזר מריץ אותה כחלק מאותה משימה. מה שעושה את העבודה הוא עצמו תוסף library שנשלח, בונה האפליקציות של Studio, ונקרא בדיוק כמו שכל מתודת library אחרת נקראת. כל אילוץ שלמטה נמצא במחולל ולא במסמך מדיניות, והמצב הסופי הוא תוסף פרטי, במכוון.
חמש פעולות, ואין שתיים מהן שנחסמות באותה הדרך.
שתיים מאלה מתוארות דרך קבע כדבר אחד, והן אינן. חברות Agency חוסמת את עצם רישום התוסף, והיא יושבת על מתודת ההעלאה. חשבון מוכר פעיל ב-Stripe Connect חוסם את המעבר לציבורי, והוא יושב על מתודת הפרסום, שכל מי שמחובר רשאי לקרוא לה. הן עוצרות אנשים שונים ברגעים שונים. התקנת תוסף לפרויקט אינה נחסמת על ידי אף אחת מהן: היא דורשת הרשאת מנהל באותו פרויקט, ואם לתוסף יש מחיר היא מנותבת קודם דרך שלב תשלום.
הרשאה | תוכנית חברות | השער האחר | |
|---|---|---|---|
| התקנה או הסרה בפרויקט | מנהל פרויקט | תוסף בתשלום מנותב קודם דרך שלב תשלום | |
| קריאה או כתיבה של תצורת תוסף | מנהל פרויקט | שרשרת ההורים נבדקת מחדש לפני שנוגעים בכלום | |
| עצם רישום התוסף | מנהל פרויקט | Agency | הבעלות נאכפת בכל עדכון |
| פרסום שלו בשוק | כל משתמש מחובר | חשבון מוכר פעיל ב-Stripe Connect, ואחרת 403 | |
| העברה שלו לארכיון | מנהל פרויקט | הבעלים בלבד |
פרסום הוא פעולה נפרדת מעדכון הקוד, במכוון.
העלאת גרסה חדשה, או מתן אפשרות לבונה ה-AI לבנות אחת מחדש, לעולם אינה נכנסת שוב לביקורת. מחזור החיים הקיים נישא קדימה כלשונו: תוסף שהיה פרטי נשאר פרטי, תוסף שהיה ציבורי נשאר ציבורי, ומצב הביקורת שכבר היה לו הוא מצב הביקורת שהוא שומר. מעבר לציבורי הוא מתודה אחרת עם הרשאה אחרת, שרצה פעם אחת, על ידי הבעלים, במכוון, והיא הקריאה היחידה שמריצה את השער. כל מה שלמטה תלוי באותה קריאה אחת.
תשעה דברים שמערכת התוספים אינה עושה.
- תוספי צד שלישי אינם יכולים להריץ קוד שרת היום. המדיניות הזמנית היא דחייה, זהו מתג בקונפיג, והמתג הזה כבוי בשתי התצורות, ולכן כל מה שאתה בונה כדי שמישהו אחר יתקין הוא משטח בצד הדפדפן בלבד.
- חוזה ה-widget נושא יכולת headless, עבור קוד תוסף שרץ במקום שאין בו מסמך. אף אחת מ-62 החבילות שנשלחו אינה מצהירה עליה. החוזה קיים, ההוכחה שנשלחה אינה קיימת.
- זמן הריצה תומך בייצוא destroy אופציונלי בתוסף ui. אף אחד מעשרת תוספי ה-ui שנשלחו אינו מייצא אותו.
- שניים משלושת מנהלי ההתקן לנתונים הם לקריאה בלבד כפי שנשלחו. רק המקורי מממש את כל שישה השירותים. MySQL ו-CouchDB מייצאים קריאה ובדיקת חיבור ולא יותר מזה, ובקשה מכל אחד מהם למשהו נוסף זורקת שגיאה תוך נקיבה בשירות החסר.
- בורר הסמלים מחפש בתוך הערכה שבחרת, לא על פני הערכות שהתקנת. ותוסף graphics רשאי להצהיר על תלות npm אחת לכל היותר, וזו הסיבה שכל ערכה שנשלחה היא חבילת מקור אחת ועץ התיקיות שלה.
- התקנת רכיב משותף בודקת רק שני סוגים של תלות בתוספים, מסגרת הממשק בתוכנית ומנהל ההתקן לנתונים בטבלה. רכיב שמשתמש בתוסף widget אינו מצהיר עליו, ולכן אותה התקנה יכולה להצליח כשה-widget עדיין חסר.
- בונה ה-AI מוגן לפני שהקוד נארז, לעולם לא בזמן שהוא רץ. שלוש עשרה תבניות אסורות וערכה קבועה של שמות קבצים, שתיהן מוחלות על טקסט. אין מאחוריהן שום בידוד בזמן ריצה.
- שום דבר בפלטפורמה אינו מחליט אילו מפתחות תצורה בטוחים לפרסום. שיקול הדעת הזה מואצל כולו למחבר התוסף, ותוסף שאינו מפעיל שיקול דעת מוסר את כל מסמך התצורה שלו לדפדפן.
- מסלול הרישום מקבל שתי מחרוזות סוג יותר משהמוצר חושף, ושום דבר בקטלוג אינו שולח אף אחת מהן. השש שבבורר הן השש שמנתבות לאנשהו.
השאלות שמתגבשות אצלך ברגע זה
חמש מתוך שש התשובות האלה מסתיימות במגבלה. הן כאן משום שהיית פוגש אותן בשבוע הראשון ממילא.
צפייה בתוסף מותקן ומופיע בשקע שלו.
מתקינים שני תוספים ויש לך שכבת נתונים ומערכת עיצוב.
זה פשוטו כמשמעו מה שפרויקט חדש נשלח איתו: מנהל ההתקן המקורי לנתונים ומסגרת Tailwind, ולא יותר. כל מה שמעבר לשניים האלה הוא התקנה אחת, והקטגוריה שברשומה קובעת היכן הוא מופיע, בין אם זה לוח ההוספה, בורר הסמלים, בחירת מנהל ההתקן של הטבלה, מתג על אלמנט בודד, או רשימת פעולות תהליך העבודה.