هر توسعه‌دهنده وب این حس را می‌شناسد که اپلیکیشن خود را در یک محیط استیجینگ (staging) کنترل‌شده، به شکلی بی‌نقص رندر می‌شود. عرضه یک ویجت تعبیه‌شده (embedded widget) این آرامش را کاملاً از بین می‌برد. شما دیگر معمار صفحه نیستید؛ بلکه یک مهمان ناخوانده هستید که یک اپلیکیشن React را به یک DOM تزریق می‌کنید که مالک آن نیستید، به یک سلسله‌مراتب CSS (cascade) که خودتان آن را ننوشته‌اید، و به یک محیط زمان اجرا (runtime) که ممکن است فعالانه علیه شما عمل کند. هنگام ساخت و عرضه ویجت Clanker Support، متوجه شدیم که فرضیات استاندارد توسعه وب، درست در لحظه‌ای که کد شما در قالب (theme) شخص دیگری اجرا می‌شود، فرو می‌ریزند. سایت میزبان ممکن است اندازه فونت‌ها را بازنشانی کند، divهای خالی را پنهان کند، یا یک چرخه حیات اسکریپت (script lifecycle) را اعمال کند که پیکربندی شما را پیش از آنکه بتوانید آن را بخوانید، باطل کند. در اینجا قوانین دفاعی‌ای را می‌آوریم که با تجربه‌های تلخ در محیط عملیاتی (production) آموخته‌ایم.

یک فایل، یک حالت شکست

باندلرهای مدرن شما را با کدشکنی (code splitting) و ایمپورت‌های پویا وسوسه می‌کنند. در برابر آن‌ها مقاومت کنید. یک ویجت تعبیه‌شده باید به صورت یک فایل واحد و در قالب یک IIFE (Immediately Invoked Function Expression) عرضه شود. وقتی مشتری تگ اسکریپت شما را در قالب خود کپی می‌کند، انتظار یک درخواست شبکه را دارد. اگر باندل شما سعی کند یک کتابخانه سنگینِ تجزیه‌کننده (parsing library) یا یک تکه از مدل زبانی را به صورت lazy-load بارگذاری کند، عملیات fetch ممکن است بی‌صدا با شکست مواجه شود. میزبان ممکن است یک Content Security Policy سخت‌گیرانه، یک مسدودکننده تبلیغات تهاجمی، یا یک مسیر CDN داشته باشد که با فرض‌های publicPath شما مطابقت ندارد. با اجبار کردن همه چیز به یک IIFE، عدم قطعیت‌های مربوط به بارگذاری تکه‌های ثانویه (secondary chunks) را از بین می‌برید. اگر وابستگی‌ای (dependency) بر lazy-loading بخش‌های داخلی خود اصرار داشت، در زمان ساخت (build time) آن را به یک stub سبک تغییر نام (alias) دهید. نتیجه، یک محصول واحد، یک حالت شکست واحد، و یک جلسه عیب‌یابی بسیار آسان‌تر است، آن هم زمانی که مدیر سایت مشتری، اسکرین‌شاتی از یک حباب چت خراب را برای شما ایمیل می‌کند.

Shadow DOM هم نشت می‌کند

توسعه‌دهندگان اغلب با Shadow DOM مانند یک دژ نفوذناپذیر برخورد می‌کنند. درست است که انتخابگرهای (selectors) شما را از CSS صفحه میزبان جدا می‌کند، اما ارث‌بری (inheritance) را جدا نمی‌کند. ویژگی‌هایی مانند font-family ،line-height ،color و text-align به گونه‌ای به سمت پایینِ درخت سایه (shadow tree) شما جریان می‌یابند که انگار مرزی وجود ندارد. یک فروشگاه Shopify با تعریف سراسری font-family: "Comic Sans MS"، ویجت پشتیبانی طراحی‌شده با دقت شما را آلوده خواهد کرد، مگر اینکه صراحتاً هر ویژگی قابل ارث‌بری را در عنصر ریشه (root element) خود تثبیت کنید. تایپوگرافی، فاصله‌گذاری و تراز متن خود را با مقادیر مشخص دقیقاً در سطح میزبان تنظیم کنید. فرض کنید صفحه والد خصمانه است و هر چیزی را که برایتان مهم است بازنشانی (reset) کنید. Shadow DOM از کلاس‌های شما محافظت می‌کند، نه از زیبایی‌شناسی شما.

نمایش جادویی ناپدید شدن divهای خالی

این مورد ما را کاملاً غافلگیر کرد. بسیاری از قالب‌های محبوب، از جمله Shopify Dawn، با یک قانون CSS عرضه می‌شوند که بی‌خطر به نظر می‌رسد: div:empty { display: none; }. وقتی ویجت شما نصب (mount) می‌شود، معمولاً یک div میزبان را هدف قرار می‌دهد که در ابتدا خالی است. قبل از اینکه جاوااسکریپت شما اجرا شود و React گره (node) را hydrate کند، آن div دقیقاً خالی است. استایل‌شیت قالب آن را پنهان می‌کند. اسکریپت شما اجرا می‌شود، ReactDOM.createRoot را فراخوانی می‌کند، اما هیچ چیز ظاهر نمی‌شود. هیچ خطایی در کنسول وجود ندارد؛ عنصر صرفاً در چیدمان (layout) از بین رفته است. راه حل، استفاده از روش مستقیم و صریح است: یک استایل خطی (inline style) از display: block !important را به نقطه نصب (mount point) خود اعمال کنید. منتظر نمانید تا کتابخانه CSS-in-JS شما بعداً این کار را انجام دهد. تا زمانی که استایل‌شیت‌های شما اعمال شوند، قالب میزبان قبلاً پیروز شده است.

rem را به خاطر px رها کنید

در یک اپلیکیشن معمولی، واحدهای نسبی مانند rem انتخابی مسئولانه هستند. در یک ویجت تعبیه‌شده، آن‌ها یک نقطه ضعف محساری هستند. مقدار rem نسبت به اندازه فونت html ریشه در سند میزبان محاسبه می‌شود، نه در ویجت شما. اگر صفحه میزبان html { font-size: 10px; } را تنظیم کند یا از ترفند قدیمی ۶۲.۵٪ استفاده کند، تمام مقیاس تایپوگرافی و فاصله‌گذاری شما بدون هشدار تغییر می‌کند. یک ارتفاع خط 1.6rem راحت ممکن است به 16px کاهش یابد، یا پدینگ شما ممکن است به نوارهای غیرقابل خواندن تبدیل شود. از آنجایی که نمی‌توانید اندازه ریشه میزبان را پیش‌بینی یا کنترل کنید، پیکسل‌ها تنها واحد صادق برای یک ویجت تعبیه‌شده هستند. آن‌ها بدون توجه به فرضیات صفحه اطراف، با همان اندازه فیزیکی رندر می‌شوند. وقتی درون سلسله‌مراتب (cascade) سایت دیگری زندگی می‌کنید، انعطاف‌پذیری تئوریِ دسترسی‌پذیری (accessibility) در rem را با قابلیت اطمینان عملی در px معاوضه کنید.

پیکربندی خود را پیش از ناپدید شدن بخوانید

اگر پیکربندی را از طریق ویژگی‌های data در تگ script به ویجت خود منتقل می‌کنید، باید آن‌ها را به‌صورت همزمان (synchronously) بخوانید. مرورگر document.currentScript را فراهم می‌کند تا یک اسکریپت بتواند تگ خود را بررسی کند، اما این مرجع گذرا است. اگر منتظر DOMContentLoaded یا هر مرز ناهمزمان (asynchronous boundary) دیگری بمانید، document.currentScript برابر با null می‌شود. پیکربندی شما از بین می‌رود. آن ویژگی‌ها را بلافاصله در سطح بالای اجرای اسکریپت خود بخوانید. کلید API، شناسه ویجت و تم رنگی را در همان لحظه استخراج کنید، آن‌ها را در یک closure یا متغیر ماژول ذخیره کنید و تنها پس از آن فرآیند راه‌اندازی React را آغاز کنید.

اجازه دهید URL اسکریپت، مبدأ API را تعیین کند

هاردکد کردن یک URL مربوط به API محیط عملیاتی (production) در باندل شما، اشتباهی است که در محیط‌های مختلف خود را نشان می‌دهد. در عوض، مبدأ API خود را از ویژگی src خودِ عنصر اسکریپت استخراج کنید. اگر ویجت از https://cdn.staging.example.com/widget.js بارگذاری شود، فراخوانی‌های API آن باید به‌طور پیش‌فرض به https://api.staging.example.com هدایت شوند. اگر توسعه‌دهنده‌ای تگ اسکریپت را در یک فایل HTML محلی که از localhost:3000 سرو می‌شود قرار دهد، نسخه محلی باید درخواست‌ها را به یک سرور محلی هدایت کند. این قرارداد نیاز به ساخت‌های مخصوص هر محیط، پرچم‌های ویژگی (feature flags) یا پیکربندی دستی توسط کاربرِ جاسازی‌کننده را از بین می‌برد. این روش به‌سادگی کار می‌کند، زیرا مکان زیرساخت از روی مکان تحویل قابل تشخیص است.

با هدرهای کش مانند یک راه نجات برای اصلاحات فوری برخورد کنید

کاربران تگ اسکریپت شما را یک بار در قالب فوتر خود کپی می‌کنند و دیگر فراموشش می‌کنند. شما نمی‌توانید به پنج هزار فروشنده ایمیل بزنید و از آن‌ها بخواهید پارامتر کوئری نسخه را تغییر دهند. این بدان معناست که هدرهای کش شما بخشی از استراتژی پاسخ به حوادث شما هستند. یک max-age کوتاه برای باندل ویجت خود تنظیم کنید تا وقتی یک اصلاحیه حیاتی ارسال می‌کنید، در عرض چند ساعت، و نه چند هفته، منتشر شود. راحتیِ داشتن یک دارایی کش‌شده با عمر طولانی، ارزش این فلج‌شدگی را ندارد که بدانید هزاران سایت در حال اجرای نسخه‌ای خراب هستند که نمی‌توانید آن را بازپس بگیرید. هزینه ترافیک CDN را بپذیرید. سلامت روان شما به آن بستگی دارد.

سیاست CSP خود را برای جاسازی‌های iframe معکوس کنید

اگر گزینه جاسازی مبتنی بر iframe را ارائه می‌دهید، سیاست امنیت محتوا (CSP) شما نیازمند واژگونیِ تفکر استاندارد در برنامه‌های وب است. به‌طور معمول ممکن است برای جلوگیری از clickjacking، قاب‌بندی (framing) را ممنوع کنید. اما برای یک ویجت، باید آن را مجاز کنید. مقدار frame-ancestors * را تنظیم کنید تا