כל מפתח ווב מכיר את התחושה של לראות את האפליקציה שלו מרונדרת בצורה מושלמת בסביבת staging מבוקרת. השקת ווידג'ט מוטמע (embedded widget) הורסת את הנוחות הזו לחלוטין. אינך עוד האדריכל של הדף. אתה אורח בלתי מוזמן, שמזריק אפליקציית React לתוך DOM שאינו בבעלותך, לתוך CSS cascade שלא כתבת, ולסביבת runtime שעשויה לפעול נגדך באופן פעיל. בזמן הפיתוח וההשקה של ווידג'ט Clanker Support, למדנו שהנחות היסוד של פיתוח ווב סטנדרטי קורסות ברגע שהקוד שלך רץ בתוך תבנית (theme) של מישהו אחר. האתר המארח עשוי לאפס גדלי גופנים, להסתיר div-ים ריקים, או לאכוף מחזור חיים של סקריפט שמבטל את הקונפיגורציה שלך עוד לפני שהספקת לקרוא אותה. אלו הם חוקי ההגנה שכתבנו בדם בזמן עבודה ב-production.

קובץ אחד, מצב כשל אחד

Bundlers מודרניים מפתים אותך עם code splitting ו-dynamic imports. התנגד להם. ווידג'ט מוטמע חייב להישלח כ-Immediately Invoked Function Expression (IIFE) בקובץ יחיד. כשלקוח מעתיק את תגית ה-script שלך לתוך התבנית שלו, הוא מצפה לבקשת רשת אחת. אם ה-bundle שלך מנסה לבצע lazy-load לספריית parsing כבדה או לקטע של מודל שפה, ה-fetch עלול להיכשל בשקט. לאתר המארח עשוי להיות Content Security Policy מחמיר, חוסם פרסומות אגרסיבי, או נתיב CDN שאינו תואם להנחות ה-publicPath שלך. על ידי כפיית הכל לתוך IIFE אחד, אתה מבטל את הבלתי ידוע של טעינת chunks משניים. אם תלות (dependency) מתעקשת לבצע lazy-loading לרכיבים הפנימיים שלה, בצע לה alias בזמן ה-build ל-stub קל משקל. התוצאה היא artifact יחיד, מצב כשל (failure mode) יחיד, וסשן debugging הרבה יותר קל כשמנהל האתר של לקוח שולח לך צילום מסך של בועת צ'אט שבורה במייל.

גם ה-Shadow DOM דולף

מפתחים מתייחסים לעיתים קרובות ל-Shadow DOM כמבצר בלתי חדיר. הוא אכן מבודד את הסלקטורים שלך מה-CSS של דף המארח, אך הוא אינו מבודד ירושה (inheritance). מאפיינים כמו font-family, line-height, color, ו-text-align זורמים מטה אל עץ ה-shadow שלך כאילו הגבול לא היה קיים. חנות Shopify עם הצהרת font-family: "Comic Sans MS" גלובלית תדביק את ווידג'ט התמיכה שעיצבת בקפידה, אלא אם תקבע במפורש כל מאפיין יורש ברכיב השורש (root element) שלך. הגדר את הטיפוגרפיה, המרווחים ויישור הטקסט שלך עם ערכים קונקרטיים כבר ברמת המארח. הנח שדף האב עוין ונטרל (reset) כל דבר שחשוב לך. ה-Shadow DOM מגן על ה-classes שלך, לא על האסתטיקה שלך.

משחק ההיעלמות של ה-div הריק

זה תפס אותנו לגמרי לא מוכנים. תבניות פופולריות רבות, כולל Shopify Dawn, מגיעות עם כלל CSS שנראה תמים: div:empty { display: none; }. כשהווידג'ט שלך עושה mount, הוא בדרך כלל מכוון ל-div מארח שמתחיל כריק. לפני שה-JavaScript שלך רץ ו-React מבצע hydration לצומת (node), ה-div הזה הוא פשוט ריק. גיליון העיצוב של התבנית מסתיר אותו. הסקריפט שלך רץ, קורא ל-ReactDOM.createRoot, וכלום לא מופיע. אין שגיאה ב-console. האלמנט פשוט חדל להתקיים בפריסה (layout). הפתרון הוא כוח גס ומפורש: החל inline style של display: block !important לנקודת ה-mount שלך. אל תסתמך על ספריית ה-CSS-in-JS שלך שתטפל בזה מאוחר יותר. עד שגיליונות העיצוב שלך יחולו, התבנית המארחת כבר ניצחה.

זנחו את ה-rem לטובת px

באפליקציה רגילה, יחידות יחסיות כמו rem הן הבחירה האחראית. בתוך ווידג'ט מוטמע, הן מהוות נטל. ערך rem נגזר מגדל גופן ה-html השורשי של המסמך המארח, לא של הווידג'ט שלך. אם דף המארח מגדיר html { font-size: 10px; } או משתמש בטריק ה-62.5% הישן, כל סולם הטיפוגרפיה והמרווחים שלך ישתנה ללא אזהרה. גובה שורה נוח של 1.6rem עלול להצטמצם ל-16px, או שה-padding שלך עלול להצטמצם לרצועות בלתי קריאות. מכיוון שאינך יכול לחזות או לשלוט בגודל השורשי של המארח, פיקסלים הם היחידה האמינה היחידה עבור ווידג'ט מוטמע. הם מרונדרים באותו גודל פיזי ללא קשר להנחות של הדף הסובב. החלף את הגמישות הנגישות התאורטית של rem באמינות המעשית של px כשאתה חי בתוך ה-cascade של אתר אחר.

קרא את הקונפיגורציה שלך לפני שהיא נעלמת

אם אתם מעבירים הגדרות (configuration) לווידג'ט שלכם דרך מאפייני נתונים (data attributes) בתגית ה-script, עליכם לקרוא אותם באופן סינכרוני. הדפדפן מספק את document.currentScript כך שסקריפט יכול לבדוק את התגית שלו, אך הפניה זו היא זמנית (ephemeral). אם תחכו ל-DOMContentLoaded או לכל גבול אסינכרוני אחר, document.currentScript יהפוך ל-null. ההגדרות שלכם יתאדו. קראו את המאפיינים הללו מיד ברמת העל (top level) של הרצת הסקריפט. תפסו את ה-API key, את ה-widget ID ואת ערכת הנושא של הצבעים (color theme) באותו הרגע, שמרו אותם בתוך closure או במשתנה מודול, ורק אז המשיכו עם עליית ה-React.

תנו לכתובת ה-URL של הסקריפט לקבוע את מקור ה-API

כתיבת כתובת ה-API של סביבת הייצור (production) כקוד קשיח (hardcoding) בתוך ה-bundle שלכם היא טעות שמתרבת בסביבות שונות. במקום זאת, גזרו את מקור ה-API ממאפיין ה-src של אלמנט הסקריפט עצמו. אם הווידג'ט נטען מ-https://cdn.staging.example.com/widget.js, קריאות ה-API שלו צריכות לפנות כברירת מחדל ל-https://api.staging.example.com. אם מפתח מכניס את תגית ה-script לקובץ HTML מקומי המוגש מ-localhost:3000, ה-build המקומי צריך לנתב בקשות לשרת מקומי. המוסכמה הזו מסירה את הצורך ב-builds ייעודיים לסביבות שונות, ב-feature flags או בהגדרות ידניות מצד המשתמש שמשתמש ב-embed. זה פשוט עובד, כי מיקום התשתית משתמע ממיקום ההפצה.

התייחסו לכותרות מטמון (Cache Headers) כאל חבל הצלה לתיקוני חירום

משתמשים מעתיקים את תגית ה-script שלכם פעם אחת לתבנית ה-footer שלהם ושוכחים ממנה. אתם לא יכולים לשלוח אימייל לחמישה אלף סוחרים ולבקש מהם לעדכן פרמטר שאילתה של גרסה (version query parameter). המשמעות היא שכותרות המטמון שלכם הן חלק מאסטרטגיית התגובה לאירועים (incident response) שלכם. הגדירו max-age קצר על ה-widget bundle שלכם, כך שכאשר תפיצו תיקון קריטי, הוא יופץ תוך שעות ולא שבועות. הנוחות של נכס מטמון ארוך טווח אינה שווה את השיתוק שנוצר כשאתם יודעים שאלפי אתרים מריצים גרסה שבורה שאינכם יכולים למשוך חזרה. קבלו את עלות התעבורה ב-CDN. השפיות שלכם תלויה בכך.

הפכו את ה-CSP שלכם עבור ה-embeds מבוססי iframe

אם אתם מציעים אפשרות embedding מבוססת iframe, מדיניות אבטחת התוכן (Content Security Policy) שלכם דורשת היפוך של חשיבה סטנדרטית של אפליקציות ווב. בדרך כלל אולי תאסרו framing כדי למנוע clickjacking. עבור ווידג'ט, עליכם לאפשר זאת. הגדירו frame-ancestors * כדי שכל אתר יוכל לארח את ה-iframe שלכם. לאחר מכן, היו קשוחים לגבי כל השאר. הגבילו בצורה הדוקה את script-src, style-src ו-connect-src בתוך מדיניות ה-iframe הזו. אתם חושפים את עצמכם במכוון לרשת כולה דרך וקטור ה-framing, לכן עליכם לוודא שלקוד הרץ בתוך ה-iframe אין שום אפשרות להתנהג בצורה לא תקינה אם דף מארח מנסה לתמרן אותו.

גישת ה"אורח"

בניית embeds דורשת גישה שונה מאשר בניית אפליקציות ווב סטנדרטיות. באפליקציה שלכם, אתם הבעלים של המכולה (container), הניתוב (routing), צינור הבנייה (build pipeline) והעיצובים הגלובליים. ב-embed, אתם לא מחזיקים בכלום. דף המארח הוא שרירותי, לעיתים קרובות עתיק, מדי פעם עוין, ותמיד מחוץ לשליטתכם. כל הנחה חייבת להיות הגנתית. הגדירו במפורש למה אתם מתכוונים, ודאו את הסביבה באופן אקטיבי, ותכננו למקרה של תקלות שאינכם יכולים לראות. ווידג'ט ה-Clanker Support עובד היום לא בגלל שהרשת היא צפויה, אלא בגלל שהפסקנו לסמוך עליה שתהיה כזו.