প্রত্যেক ওয়েব ডেভেলপারের সেই অনুভূতিটি জানা আছে যখন তারা একটি নিয়ন্ত্রিত স্টেজিং এনভায়রনমেন্টে তাদের অ্যাপ্লিকেশনটি নিখুঁতভাবে রেন্ডার হতে দেখে। একটি এমবেডেড উইজেট শিপ করা সেই স্বাচ্ছন্দ্যকে পুরোপুরি নষ্ট করে দেয়। আপনি আর সেই পেজের আর্কিটেক্ট নন। আপনি একজন অনাহুত অতিথি, যিনি এমন একটি DOM-এ একটি React অ্যাপ্লিকেশন ইনজেক্ট করছেন যা আপনার নয়, এমন একটি CSS ক্যাসকেডে যা আপনি তৈরি করেননি, এবং এমন একটি রানটাইম এনভায়রনমেন্টে যা হয়তো সক্রিয়ভাবে আপনার বিরুদ্ধে কাজ করছে। Clanker Support উইজেটটি তৈরি এবং শিপ করার সময় আমরা শিখেছি যে, যখন আপনার কোড অন্য কারো থিমের ভেতরে চলে, তখন সাধারণ ওয়েব ডেভেলপমেন্টের ধারণাগুলো ভেঙে পড়ে। হোস্ট সাইট ফন্ট সাইজ রিসেট করতে পারে, খালি div গুলো লুকিয়ে ফেলতে পারে, অথবা এমন একটি স্ক্রিপ্ট লাইফসাইকেল প্রয়োগ করতে পারে যা আপনার কনফিগারেশন পড়ার আগেই তা বাতিল করে দেয়। এখানে সেই রক্ষণাত্মক নিয়মগুলো দেওয়া হলো যা আমরা প্রোডাকশনে কাজ করতে করতে অর্জিত কঠিন অভিজ্ঞতার মাধ্যমে শিখেছি।
একটি ফাইল, একটি ফেইলর মোড
আধুনিক বান্ডলারগুলো আপনাকে কোড স্প্লিটিং এবং ডায়নামিক ইমপোর্টের প্রলোভন দেখায়। এগুলোকে প্রতিরোধ করুন। একটি এমবেডেড উইজেট অবশ্যই একটি সিঙ্গেল-ফাইল Immediately Invoked Function Expression (IIFE) হিসেবে শিপ করতে হবে। যখন একজন গ্রাহক তাদের টেমপ্লেটে আপনার স্ক্রিপ্ট ট্যাগ কপি করেন, তারা একটি মাত্র নেটওয়ার্ক রিকোয়েস্ট আশা করেন। যদি আপনার বান্ডল কোনো ভারী পার্সিং লাইব্রেরি বা ল্যাঙ্গুয়েজ মডেল চাঙ্ক লেজি-লোড করার চেষ্টা করে, তবে ফেচ (fetch) অপারেশনটি নিঃশব্দে ব্যর্থ হতে পারে। হোস্ট সাইটে একটি কঠোর Content Security Policy, একটি আক্রমণাত্মক অ্যাড ব্লকার, অথবা একটি CDN পাথ থাকতে পারে যা আপনার publicPath অনুমানের সাথে মেলে না। সবকিছুকে একটি IIFE-তে জোর করে নিয়ে আসার মাধ্যমে, আপনি সেকেন্ডারি চাঙ্ক লোডিংয়ের অনিশ্চয়তা দূর করতে পারেন। যদি কোনো ডিপেন্ডেন্সি তার নিজস্ব ইন্টারনাল লেজি-লোড করতে চায়, তবে বিল্ড টাইমে সেটিকে একটি লাইটওয়েট স্টাব (stub)-এ এলিয়াস (alias) করে দিন। এর ফলে আপনি পাবেন একটি সিঙ্গেল আর্টিফ্যাক্ট, একটি সিঙ্গেল ফেইলর মোড, এবং যখন কোনো গ্রাহকের সাইট ম্যানেজার আপনাকে একটি ভাঙা চ্যাট বাবলের স্ক্রিনশট ইমেল করবেন, তখন ডিবাগিং করা অনেক সহজ হবে।
Shadow DOM-ও লিক করে
ডেভেলপাররা প্রায়ই Shadow DOM-কে একটি দুর্ভেদ্য দুর্গ হিসেবে বিবেচনা করেন। এটি হোস্ট পেজের CSS থেকে আপনার সিলেক্টরগুলোকে আলাদা করে রাখলেও, এটি ইনহেরিটেন্স (inheritance) বা উত্তরাধিকারকে আলাদা করতে পারে না। font-family, line-height, color, এবং text-align-এর মতো প্রোপার্টিগুলো আপনার শ্যাডো ট্রিতে এমনভাবে প্রবাহিত হয় যেন সেখানে কোনো সীমানা নেই। একটি Shopify স্টোরে যদি গ্লোবাল font-family: "Comic Sans MS" ডিক্লারেশন থাকে, তবে সেটি আপনার যত্ন সহকারে ডিজাইন করা সাপোর্ট উইজেটকেও আক্রান্ত করবে, যদি না আপনি আপনার রুট এলিমেন্টে প্রতিটি ইনহেরিটেবল প্রোপার্টি স্পষ্টভাবে পিন (pin) করে দেন। হোস্ট লেভেলেই আপনার নিজস্ব টাইপোগ্রাফি, স্পেসিং এবং টেক্সট অ্যালাইনমেন্ট সুনির্দিষ্ট ভ্যালু দিয়ে সেট করে নিন। ধরে নিন প্যারেন্ট পেজটি প্রতিকূল এবং আপনার প্রয়োজনীয় সবকিছু রিসেট করে নিন। Shadow DOM আপনার ক্লাসগুলোকে রক্ষা করে, আপনার নান্দনিকতাকে (aesthetics) নয়।
খালি Div-এর অদৃশ্য হয়ে যাওয়ার খেলা
এটি আমাদের পুরোপুরি অপ্রস্তুত করে দিয়েছিল। Shopify Dawn সহ অনেক জনপ্রিয় থিমে একটি CSS রুল থাকে যা দেখতে নিরীহ মনে হয়: div:empty { display: none; }। যখন আপনার উইজেট মাউন্ট হয়, এটি সাধারণত একটি হোস্ট div-কে টার্গেট করে যা শুরুতে খালি থাকে। আপনার JavaScript কার্যকর হওয়ার এবং React নোডটি হাইড্রেট (hydrate) করার আগে, সেই div-টি আক্ষরিক অর্থেই খালি থাকে। থিমের স্টাইলশিট এটিকে লুকিয়ে ফেলে। আপনার স্ক্রিপ্ট চলে, ReactDOM.createRoot কল করে, কিন্তু কিছুই দেখা যায় না। কনসোলে কোনো এরর দেখায় না। এলিমেন্টটি লেআউটে অস্তিত্বহীন হয়ে যায়। এর সমাধান হলো সরাসরি এবং সুনির্দিষ্ট: আপনার মাউন্ট পয়েন্টে একটি ইনলাইন স্টাইল display: block !important প্রয়োগ করুন। এটি পরে হ্যান্ডেল করার জন্য আপনার CSS-in-JS লাইব্রেরির ওপর নির্ভর করবেন না। আপনার স্টাইলশিট প্রয়োগ হওয়ার আগেই হোস্ট থিম জয়ী হয়ে যায়।
rem বাদ দিয়ে px ব্যবহার করুন
একটি সাধারণ অ্যাপ্লিকেশনে rem-এর মতো রিলেটিভ ইউনিটগুলো একটি দায়িত্বশীল পছন্দ। কিন্তু একটি এমবেডেড উইজেটের ক্ষেত্রে এগুলো একটি ঝুঁকি। একটি rem ভ্যালু হোস্ট ডকুমেন্টের রুট html ফন্ট সাইজের বিপরীতে কাজ করে, আপনার উইজেটের বিপরীতে নয়। যদি হোস্ট পেজ html { font-size: 10px; } সেট করে বা পুরনো 62.5% ট্রিক ব্যবহার করে, তবে আপনার পুরো টাইপোগ্রাফিক এবং স্পেসিং স্কেল কোনো সতর্কতা ছাড়াই বদলে যাবে। একটি আরামদায়ক 1.6rem লাইন হাইট 16px-এ নেমে আসতে পারে, অথবা আপনার প্যাডিং এত ছোট হয়ে যেতে পারে যে তা পড়া অসম্ভব হবে। যেহেতু আপনি হোস্টের রুট সাইজিং প্রেডিক্ট বা কন্ট্রোল করতে পারেন না, তাই এমবেডেড উইজেটের জন্য পিক্সেল (px) হলো একমাত্র নির্ভরযোগ্য ইউনিট। আশেপাশের পেজের সেটিংস যাই হোক না কেন, এগুলো একই ফিজিক্যাল সাইজে রেন্ডার হয়। যখন আপনি অন্য একটি সাইটের ক্যাসকেডের ভেতরে বাস করবেন, তখন rem-এর তাত্ত্বিক অ্যাক্সেসিবিলিটি ফ্লেক্সিবিলিটির বদলে px-এর ব্যবহারিক নির্ভরযোগ্যতাকে বেছে নিন।
আপনার কনফিগারেশন অদৃশ্য হওয়ার আগেই তা পড়ুন
আপনি যদি স্ক্রিপ্ট ট্যাগের data attributes-এর মাধ্যমে আপনার উইজেটে কনফিগারেশন পাস করেন, তবে আপনাকে সেগুলো synchronously পড়তে হবে। ব্রাউজার document.currentScript প্রদান করে যাতে একটি স্ক্রিপ্ট তার নিজস্ব ট্যাগটি পরীক্ষা করতে পারে, কিন্তু এই reference-টি ephemeral বা ক্ষণস্থায়ী। আপনি যদি DOMContentLoaded বা কোনো asynchronous boundary-র জন্য অপেক্ষা করেন, তবে document.currentScript null হয়ে যাবে। আপনার কনফিগারেশন হারিয়ে যাবে। আপনার স্ক্রিপ্ট এক্সিকিউশনের একদম শুরুতে সেই attributes গুলো পড়ে ফেলুন। তখনই API key, widget ID এবং color theme ক্যাপচার করুন, সেগুলো একটি closure বা module variable-এ সংরক্ষণ করুন, এবং তারপর React বুট করার প্রক্রিয়া শুরু করুন।
স্ক্রিপ্ট URL-কে API Origin নির্বাচন করতে দিন
আপনার bundle-এ একটি production API URL hardcode করা একটি ভুল যা বিভিন্ন environment-এ সমস্যা বহুগুণ বাড়িয়ে দেয়। পরিবর্তে, স্ক্রিপ্ট এলিমেন্টের নিজস্ব src attribute থেকে আপনার API origin বের করে নিন। যদি উইজেটটি https://cdn.staging.example.com/widget.js থেকে লোড হয়, তবে এর API call গুলো ডিফল্টভাবে https://api.staging.example.com-এ হওয়া উচিত। যদি একজন ডেভেলপার localhost:3000 থেকে সার্ভ করা একটি লোকাল HTML ফাইলে স্ক্রিপ্ট ট্যাগটি ব্যবহার করেন, তবে লোকাল build-টি রিকোয়েস্টগুলো একটি লোকাল সার্ভারে পাঠাতে হবে। এই নিয়মটি environment-specific builds, feature flags, বা embed ব্যবহারকারীর কাছ থেকে manual configuration-এর প্রয়োজনীয়তা দূর করে। এটি এমনিতেই কাজ করবে, কারণ delivery location-এর মাধ্যমেই infrastructure location বোঝা যায়।
Cache Headers-কে একটি Hotfix Lifeline হিসেবে বিবেচনা করুন
ব্যবহারকারীরা একবার আপনার স্ক্রিপ্ট ট্যাগটি তাদের footer template-এ কপি করে এবং তারপর সেটি ভুলে যায়। আপনি পাঁচ হাজার মার্চেন্টকে ইমেল করে একটি version query parameter আপডেট করতে বলতে পারবেন না। এর মানে হলো আপনার cache headers আপনার incident response strategy-র একটি অংশ। আপনার widget bundle-এ একটি ছোট max-age সেট করুন যাতে আপনি যখন কোনো critical fix পাঠান, সেটি কয়েক সপ্তাহের বদলে কয়েক ঘণ্টার মধ্যেই ছড়িয়ে পড়ে। একটি দীর্ঘস্থায়ী cached asset-এর সুবিধা এমন কোনো অচল অবস্থার জন্য যথেষ্ট নয় যেখানে আপনি জানেন যে হাজার হাজার সাইট একটি ত্রুটিপূর্ণ version চালাচ্ছে যা আপনি আর ফিরিয়ে নিতে পারবেন না। CDN traffic cost মেনে নিন। আপনার মানসিক প্রশান্তি এর ওপর নির্ভর করছে।
iframe Embed-এর জন্য আপনার CSP উল্টে দিন
আপনি যদি iframe-ভিত্তিক embedding অপশন অফার করেন, তবে আপনার Content Security Policy-র ক্ষেত্রে সাধারণ ওয়েব অ্যাপ্লিকেশন চিন্তাধারার উল্টোটা করতে হবে। সাধারণত clickjacking রোধ করতে আপনি framing নিষিদ্ধ করতে পারেন। কিন্তু একটি উইজেটের জন্য আপনাকে এটি অনুমতি দিতে হবে। frame-ancestors * সেট করুন যাতে যেকোনো সাইট আপনার iframe হোস্ট করতে পারে। এরপর অন্য সব বিষয়ে অত্যন্ত কঠোর হোন। সেই iframe policy-র ভেতরে script-src, style-src, এবং connect-src-কে কঠোরভাবে লকডাউন করুন। আপনি ফ্রেমিন ভেক্টরের মাধ্যমে নিজেকে ইচ্ছাকৃতভাবে পুরো ওয়েব জগতের কাছে উন্মুক্ত করছেন, তাই আপনাকে নিশ্চিত করতে হবে যে হোস্ট পেজ যদি এটিকে ম্যানিপুলেট করার চেষ্টা করে, তবে iframe-এর ভেতরে চলা কোডটির কোনো বিশৃঙ্খলা করার সুযোগ না থাকে।
গেস্ট মাইন্ডসেট (The Guest Mindset)
Embed তৈরি করার জন্য সাধারণ ওয়েব অ্যাপ্লিকেশন তৈরির চেয়ে ভিন্ন মানসিকতা প্রয়োজন। আপনার নিজস্ব অ্যাপে, আপনি container, routing, build pipeline এবং global styles-এর মালিক। কিন্তু একটি embed-এ, আপনি কিছুরই মালিক নন। হোস্ট পেজটি যেকোনো রকম হতে পারে, প্রায়শই পুরনো, মাঝে মাঝে প্রতিকূল এবং সবসময় আপনার নিয়ন্ত্রণের বাইরে। প্রতিটি অনুমান অবশ্যই defensive হতে হবে। আপনি কী বোঝাতে চাচ্ছেন তা স্পষ্টভাবে উল্লেখ করুন, environment-কে দ্রুত যাচাই (validate) করুন এবং এমন সম্ভাব্য ত্রুটির জন্য ডিজাইন করুন যা আপনি দেখতে পাচ্ছেন না। Clanker Support widget আজ কাজ করছে কারণ ওয়েবটি অনুমানযোগ্য বলে নয়, বরং আমরা এটি অনুমানযোগ্য হওয়ার ওপর বিশ্বাস করা বন্ধ করে দিয়েছি।
