وب کامپوننت چیست؟ API، Shadow DOM و معماری تولید

تیم شما یک <checkout-button> ساخته که در Demo عالی است: ظاهر ایزوله، API کوتاه و قابل‌استفاده در چند پروژه. اما در Production سه اتفاق می‌افتد: React شیء order را به Attribute نامناسب تبدیل می‌کند، رویداد داخل Shadow DOM به برنامه میزبان نمی‌رسد و دکمه در Submit فرم یا Keyboard navigation مثل کنترل بومی رفتار نمی‌کند. مشکل از «بد بودن وب کامپوننت» نیست؛ مشکل این است که Tag ساخته‌اید، اما قرارداد پلتفرم نساخته‌اید.

وب کامپوننت‌ها یا Web Components می‌توانند مرز مشترک میان Design system، چند فریمورک، Widget قابل‌جاسازی و سامانه قدیمی باشند. آن‌ها خودکار سریع، امن، سئوپسند، دسترس‌پذیر یا مستقل از وابستگی نیستند. ارزش واقعی وقتی ایجاد می‌شود که Public API، Semantics، Styling، SSR، Versioning و Test matrix از ابتدا طراحی شوند.

وب کامپوننت چیست؟

Web Components نام یک فریمورک یا Package واحد نیست؛ چتری برای چند قابلیت استاندارد پلتفرم وب است که امکان تعریف عنصر سفارشی و ترکیب آن با DOM، CSS و HTML را می‌دهد. HTML Living Standard برای Custom Elements تعریف، Upgrade، Lifecycle، ElementInternals، Form association و CustomElementRegistry را مشخص می‌کند.

قابلیتنقشبرداشت اشتباه
Custom Elementsنام و Lifecycle عنصر سفارشیبه‌تنهایی UI کامل می‌سازد
Shadow DOMمرز Tree و CSS با قواعد مشخصSandbox امنیتی و CSS کاملاً نفوذناپذیر است
TemplateFragment غیرفعال برای Clone/SSR patternهمیشه سریع‌تر از DOM API است
SlotComposition محتوای Light DOMمحتوا را واقعاً به Shadow tree منتقل می‌کند
ES Modulesبارگذاری و Packaging کدجزء انحصاری Web Components است

نام Autonomous custom element باید خط تیره داشته باشد؛ مثل md-product-card. اما داشتن خط تیره به آن Semantics نمی‌دهد. مرورگر و Screen reader از روی نام smart-button نمی‌فهمند عنصر Button است.

چه زمانی Web Components انتخاب خوبی است؟

سناریوWeb Component fitدلیل
Design system میان چند Stackقوی، با Adapter/Testمرز DOM مشترک
Widget نصب‌شونده روی سایت مشتریقویکاهش Collision و API توزیع
Legacy modernization تدریجیمتوسط تا قویStrangler boundary کوچک
یک App با یک Framework پایداروابسته به نیازComponent بومی Framework ممکن است ساده‌تر باشد
صفحه محتوایی کم‌تعاملاغلب ضعیفHTML/CSS بومی کافی است
کنترل بومی پیچیده مثل SelectپرریسکSemantics/Keyboard/Form سنگین
Business workflow کاملمرز نامناسبState/Data/Navigation را در Tag پنهان می‌کند

اگر تنها مصرف‌کننده یک Stack است و تیم از Component مدل همان Stack بهره می‌برد، Web Component الزاماً ROI ندارد. برای مقایسه هزینه یادگیری، Rendering و Ecosystem، مقاله انتخاب فریمورک با Workload و Pilot نشان می‌دهد نام فناوری جای Requirement را نمی‌گیرد.

اصل اول: HTML بومی را دوباره نسازید

قبل از ساخت Custom element بپرسید آیا button، input، dialog، details یا ترکیب ساده HTML نیاز را حل می‌کند. کنترل بومی Focus، Keyboard، Form، Autofill، Validation، Accessibility mapping، High contrast و رفتار Platform را رایگان دارد. ساخت معادل Autonomous یعنی پذیرش مسئولیت همه این رفتارها.

وب کامپوننت می‌تواند در Shadow DOM از عنصر بومی استفاده و API سطح بالاتری ارائه کند؛ مثلاً Date range از چند Input بومی. اما <x-button> با div داخلی معمولاً از <button> واقعی پرهزینه‌تر است.

Component contract پیش از Class

Tag name کوچک‌ترین بخش API است. Contract باید برای Consumer بدون خواندن Implementation قابل‌فهم باشد.

سطح Contractتصمیمنمونه
Name/OwnerNamespace، مالک، maturitymd-price، تیم Commerce
AttributesPrimitive/SSR-friendly configcurrency="IRR"
PropertiesObject/Array/Function و runtime stateprice.value = 120000
Eventsنام، detail، bubbles/composed/cancelablemd-change
MethodsImperative capability ضروریfocus()، نه getterهای داخلی
SlotsComposition و fallbackslot="label"
StylesTokens، Parts، States--md-accent، ::part(label)
SemanticsRole، name، keyboard، formButton/Input behavior
Async/ErrorLoading، abort، retry، errorEvent یا State مستند
RenderingLight/Shadow، SSR، upgrade، no-JSDeclarative Shadow DOM
CompatibilityBrowser/Framework/version matrixChrome/Firefox/Safari/WebView

این Contract باید در همان Governance سیستم طراحی ثبت شود. معماری Token، Component maturity، Ownership و Migration در راهنمای ساخت و نگهداری سیستم طراحی عمیق‌تر پوشش داده شده است.

مرز Component را درست انتخاب کنید

نوعمالک چه چیزی است؟از چه چیزی پرهیز کند؟
PrimitiveSemantics و Visual stateBusiness API و Fetch
Compositeترکیب چند PrimitiveGlobal navigation/state
Form controlValue/validity/focus/labelارسال مستقیم سفارش
Data displayنمایش داده آمادهمالکیت Source of truth
Embeddable widgetConfig، isolation، telemetryدسترسی نامحدود به Host
App islandWorkflow محدود و مستقلتبدیل هر صفحه به Micro-app

State اشتراکی، Cache، Server data و Navigation را بی‌دلیل داخل Custom element پنهان نکنید. مرز مالکیت و Consistency در راهنمای مدیریت State فرانت‌اند مشخص شده است.

Custom Elements و Upgrade

مرورگر ممکن است Markup را پیش از بارگذاری Definition بسازد. عنصر ابتدا Undefined است و بعد از customElements.define() Upgrade می‌شود. این ویژگی Progressive enhancement را ممکن می‌کند، اما اگر Content اولیه، Layout و State را طراحی نکنید باعث Flash، Layout shift یا تعامل از دست‌رفته می‌شود.

  • Constructor را برای State اولیه، Listener و Shadow root سبک نگه دارید.
  • خواندن Child/Attribute و کار وابسته به Document را به Callback مناسب منتقل کنید.
  • connectedCallback ممکن است چند بار اجرا شود؛ اتصال را Idempotent و Cleanup را متقارن کنید.
  • در disconnectedCallback Listener بیرونی، Observer، Timer و Request را پاک/لغو کنید.
  • attributeChangedCallback فقط برای observedAttributes است و Loop بازتاب نسازید.
  • برای Definition دیرهنگام، حالت Undefined و :defined را طراحی کنید.

نمونه حداقلی با Contract روشن

نمونه زیر صرفاً الگوی آموزشی است: Attribute برای داده Primitive قابل‌سریال‌سازی، Property برای Number و Rendering امن با textContent. در Production باید Error، Browser matrix، Accessibility و تست افزوده شوند.

class MdPrice extends HTMLElement {
  static observedAttributes = ['currency', 'locale'];

  #value = 0;
  #output;

  constructor() {
    super();
    const root = this.attachShadow({ mode: 'open' });
    this.#output = document.createElement('span');
    this.#output.part = 'value';
    root.append(this.#output);
  }

  get value() { return this.#value; }
  set value(next) {
    const parsed = Number(next);
    this.#value = Number.isFinite(parsed) ? parsed : 0;
    this.#render();
  }

  connectedCallback() { this.#render(); }
  attributeChangedCallback() { this.#render(); }

  #render() {
    if (!this.isConnected) return;
    const locale = this.getAttribute('locale') || 'fa-IR';
    const currency = this.getAttribute('currency') || 'IRR';
    this.#output.textContent = new Intl.NumberFormat(locale, {
      style: 'currency', currency
    }).format(this.#value);
  }
}

if (!customElements.get('md-price')) {
  customElements.define('md-price', MdPrice);
}
<md-price locale="fa-IR" currency="IRR"></md-price>
<script type="module">
  const price = document.querySelector('md-price');
  price.value = 125000; // Property: number, not serialized attribute
</script>

در ایران باید قرارداد واحد مبلغ نیز روشن باشد؛ IRR در Intl به معنی ریال است، نه تومان. تبدیل Business value به واحد نمایش را بیرون یا در API صریح انجام دهید.

Attribute و Property یک چیز نیستند

Attribute در Markup رشته‌ای و مناسب SSR/URL/Inspector است؛ Property می‌تواند Number، Boolean، Object، Array یا Function باشد. Reflection باید آگاهانه و یک‌طرفه/دوطرفه مستند شود.

دادهAttributePropertyقاعده
Label/variantمناسباختیاریPrimitive و Serializable
BooleanPresence semanticsBooleandisabled="false" هنوز حاضر است
NumberParse/validate لازمNumberNaN و range policy
Object/Arrayنامناسب مگر JSON قراردادیمناسبReference/mutation policy
Callbackنامناسبگاهی Method/propertyEvent معمولاً decoupled‌تر است
State داخلیفقط اگر Public/serializablePrivateImplementation را Leak نکنید

Setter باید قبل و بعد از Connection کار کند. Consumer ممکن است Property را قبل از Upgrade تنظیم کرده باشد؛ تست کنید Definition دیررس مقدار Instance را نابود نکند.

Events؛ مرز Shadow را آگاهانه رد کنید

رویدادهای DOM از Shadow tree با Retargeting عبور می‌کنند. Custom event برای رسیدن به Consumer بیرونی معمولاً باید Contract روشنی برای bubbles و composed داشته باشد. همه Eventها را composed نکنید؛ رخداد داخلی نباید API عمومی شود.

this.dispatchEvent(new CustomEvent('md-change', {
  detail: { value: this.value, source: 'keyboard' },
  bubbles: true,
  composed: true,
  cancelable: false
}));
فیلدپرسش Contract
NameNamespace و Semantics چیست؟
detailSchema، version و PII policy چیست؟
bubblesDelegation لازم است؟
composedباید از Shadow boundary خارج شود؟
cancelableConsumer حق جلوگیری دارد؟
Timingقبل/بعد State commit ارسال می‌شود؟

DOM Standard برای Shadow tree و Event path منبع مرجع رفتار Retargeting است. تست را با event.composedPath() و Browser واقعی انجام دهید، نه حدس از target.

Slot و Composition

محتوای Slotted در Light DOM می‌ماند و برای نمایش به Slot تخصیص می‌یابد. این تفاوت بر Styling، Event، Accessibility و Query اثر دارد.

  • Slot نام‌دار را بخشی از API و SemVer بدانید.
  • Fallback content را برای حالت خالی و no-JS طراحی کنید.
  • slotchange تغییر Node تخصیص‌یافته را می‌بیند، نه هر تغییر عمیق متن.
  • ترتیب DOM و ترتیب بصری را برای Keyboard/Screen reader جابه‌جا نکنید.
  • از Slot برای Composition محتوایی استفاده کنید؛ Object عظیم را در Attribute نگذارید.

Shadow DOM چه چیزی را کپسوله می‌کند؟

Shadow DOM مرز Tree و Style scoping است، نه مرز امنیت. Script صفحه می‌تواند با Host تعامل کند، Event بگیرد، Property تنظیم کند و در حالت open به shadowRoot برسد. حالت closed نیز راز یا Sandbox مطمئن نمی‌سازد. داده حساس، Secret و Trust boundary را با Authorization، iframe sandbox/Origin و کنترل امنیتی مناسب حل کنید.

ادعاواقعیت
CSS بیرون هرگز داخل اثر نداردInherited properties و Custom properties می‌توانند عبور کنند
CSS داخل هرگز بیرون اثر نداردRuleها Scoped‌اند، اما Host و Slotted content قواعد ویژه دارند
closed یعنی امنفقط API دسترسی عادی را محدود می‌کند؛ Security boundary نیست
Shadow همیشه لازم استLight DOM برای محتوا، Semantics و Styling عمومی گاهی بهتر است
هر Shadow root Performance را بهتر می‌کندهزینه Style/DOM/Memory به Implementation و تعداد Instance بستگی دارد

Light DOM یا Shadow DOM؟

نیازانتخاب محتملTrade-off
Widget شخص ثالث با Collision زیادShadowTheming/API دقیق لازم
محتوای مقاله/SEO و CSS سایتLightIsolation کمتر
Primitive سیستم طراحیShadow یا native wrapperAccessibility/Parts contract
Layout سادهLight/CSSCustom element شاید اضافه باشد
SSR با EncapsulationDeclarative Shadow DOMToolchain/Hydration compatibility

Styling API؛ Implementation را عمومی نکنید

Consumer نباید برای Theme به Class داخلی یا Selector شکننده وابسته شود. سه لایه رایج دارید:

  • CSS Custom properties برای Tokenهای محدود و inheritable.
  • ::part() برای بخش‌هایی که آگاهانه قابل Style هستند.
  • Attribute یا :state() برای State عمومی.

CSS Scoping Module توضیح می‌دهد part و exportparts چگونه یک Styling API پایدار می‌سازند بدون اینکه ساختار داخلی افشا شود. Part name، Token و State عمومی تغییر Breaking محسوب می‌شوند.

سطحنمونهتعهد
Token--md-color-accentType، fallback، contrast
Partmd-card::part(title)نام و scope پایدار
Host state[disabled] یا :state(busy)Semantics و transition
Slot::slotted(...)فقط Node تخصیص‌یافته مستقیم
Internal class.titlePublic نیست

Form-associated Custom Elements

برای کنترل فرم، نمایش Input کافی نیست. Name/value، Form owner، Disabled، Reset، State restore، Constraint validation، Label، Autofill و Submit باید کار کنند. استاندارد با static formAssociated = true و ElementInternals بخشی از این قرارداد را ممکن می‌کند.

  • setFormValue() مقدار Submit را تعیین می‌کند.
  • setValidity() State و پیام Validation را هماهنگ می‌کند.
  • formResetCallback و formStateRestoreCallback را در Matrix تست کنید.
  • Label، accessible name، focus و Error association را با AT واقعی بسنجید.
  • اگر Support matrix یا UX پیچیده است، Hidden input fallback را با دقت و بدون Duplicate value بررسی کنید.

وجود API به معنی رفتار بومی کامل نیست. برای کنترل‌های پیچیده، هزینه بازسازی Semantics را با استفاده مستقیم از HTML native مقایسه کنید.

دسترس‌پذیری از داخل و بیرون Shadow

Autonomous custom element Semantics بومی ندارد. ترجیح اول استفاده از کنترل Native داخل Component است. اگر Custom widget می‌سازید، Role، accessible name، state، Keyboard interaction، focus order، error، live update، forced colors، zoom و reduced motion را قرارداد کنید.

ARIA Authoring Practices Guide الگوی Keyboard و Semantics Widgetها را توضیح می‌دهد، اما خودش تصریح می‌کند Examples کد Production یا Design system آماده نیستند. معیار پذیرش را بر WCAG 2.2، رفتار بومی مشابه و Test با Browser/Screen reader واقعی بنا کنید. فرآیند کامل در راهنمای ممیزی دسترس‌پذیری وب آمده است.

سطح تستنمونه
SemanticsRole/name/value/state در Accessibility tree
KeyboardTab، Enter/Space، Arrow، Escape، Focus return
VisualFocus visible، Contrast، Forced colors، ۲۰۰%/۴۰۰%
ContentLabel/Error/Instruction و Dynamic announcement
FormSubmit/Reset/Invalid/Autofill
AT matrixحداقل Browser+Screen readerهای کاربران واقعی

RTL، زبان فارسی و Internationalization

  • dir و lang Host/Document را در Contract لحاظ کنید؛ Direction را از متن حدس نزنید.
  • از Logical properties مثل margin-inline-start استفاده کنید.
  • آمیختگی فارسی/لاتین، موبایل، ایمیل، SKU و عدد را با bdi/dir="auto" در جای درست تست کنید.
  • ریال/تومان، رقم فارسی/لاتین و جداکننده را از Business unit جدا کنید.
  • Gregorian timestamp را Source نگه دارید و نمایش شمسی را Locale layer بدانید.
  • Font fallback، Line height، نیم‌فاصله، ی/ک و طول ترجمه را Visual regression کنید.
  • Accessible name و Error فارسی را همراه Screen reader بررسی کنید؛ فقط Screenshot کافی نیست.

SSR و Declarative Shadow DOM

Shadow DOM امپراتیو داخل HTML خام Server نیست، مگر با Declarative Shadow DOM (DSD). Parser می‌تواند <template shadowrootmode="open"> را به Shadow root تبدیل کند. این امکان First render بدون انتظار برای JavaScript را می‌دهد؛ ولی Server renderer، Sanitizer، CDN transform، Framework hydration و Client definition باید با آن سازگار باشند.

<md-card>
  <template shadowrootmode="open">
    <style>:host { display: block }</style>
    <slot name="title">بدون عنوان</slot>
  </template>
  <h2 slot="title">عنوان محصول</h2>
</md-card>

قواعد Parser در HTML Living Standard رفتار DSD و Serialization را تعریف می‌کند. قابلیت را صرفاً به‌خاطر حضور در Standard فعال نکنید؛ Matrix Browser/WebView و Toolchain خود را تست کنید.

SSR contract

پرسشEvidence
بدون JS چه دیده می‌شود؟HTML snapshot و keyboard path
قبل Upgrade Layout ثابت است؟CLS/filmstrip
Hydration دوباره DOM نمی‌سازد؟node identity و console clean
State Server/Client برابر است؟locale/timezone/data fixture
DSD در sanitizer باقی می‌ماند؟edge/CMS pipeline test
Failure قابل‌بازیابی است؟JS blocked/timeout test

SEO؛ Web Component نه مزیت است نه جریمه

Search engine به خروجی و دسترسی محتوا اهمیت می‌دهد، نه نام معماری. محتوای اصلی، لینک، عنوان، Structured data و Product truth را در HTML قابل‌دسترسی Server قرار دهید؛ تعامل را Enhance کنید. Rendering وابسته به Fetch دیرهنگام، Shadow بسته و Client error می‌تواند Discovery/Indexing/Measurement را سخت کند.

برای Rendering، URL، Service worker و Crawl test به راهنمای سئو فنی PWA و JavaScript رجوع کنید. Web Component نباید Canonical، Heading hierarchy یا Link semantics را پنهان کند.

Performance؛ اندازه بگیرید، حدس نزنید

Shadow DOM به مرورگر مجوز «بهینه‌سازی جادویی» نمی‌دهد. هزینه به JavaScript، Parse/Compile/Evaluate، DOM size، Style، تعداد Instance، Layout، Listener، Hydration و Network بستگی دارد. Lazy loading هم خود Web Components نیست؛ تصمیم Packaging و Resource scheduling است.

ریسکSignalکنترل
Definition bundle بزرگJS transfer/evalESM split بر اساس Route/visibility
CSS تکراری در InstanceMemory/style costShared stylesheet strategy با Support test
Upgrade دیرFOUC/CLS/interaction gapSSR/DSD، critical definition، fallback
Hydration سنگینLong task/INPProgressive/island hydration
DOM عمیقnode count/layoutمرز کوچک و Markup ساده
Observer/Listener نشتmemory/CPU driftLifecycle cleanup test
Dependency تکراریbundle duplicationPeer/externalization strategy

Lighthouse یک Sample است. LCP/INP/CLS، Error و Component timing را در RUM با Version/Route/Device/Network ببینید. تعریف و عیب‌یابی کامل در راهنمای Core Web Vitals و RUM آمده است.

امنیت؛ Shadow DOM Sandbox نیست

  • Settingهای innerHTML و ShadowRoot.innerHTML Sanitization خودکار ندارند.
  • Untrusted HTML را به متن تبدیل یا با Sanitizer/Trusted Types policy معتبر پردازش کنید.
  • Event detail، Attribute و Property ورودی را Untrusted بدانید.
  • Secret، Token و مجوز را در Client component پنهان نکنید.
  • Widget شخص ثالث را با Threat model، CSP، Origin/iframe boundary و Supply-chain control بررسی کنید.
  • Dependency و Package را با Lockfile، Provenance، SBOM و Update policy مدیریت کنید.

Interop با React و فریمورک‌ها

«در هر فریمورکی بدون تغییر کار می‌کند» بیش‌ازحد ساده است. قرارداد DOM قابل‌مصرف است، اما Property assignment، Event subscription، SSR serialization، Hydration، JSX types، Slot syntax و Controlled state ممکن است Adapter بخواهند.

مستندات رسمی React برای Custom HTML elements Attribute و Property را جدا می‌کند. React ۱۹ در Client اگر نام با Property روی Instance منطبق باشد Property می‌گذارد؛ در SSR فقط Primitiveهای مناسب را Attribute می‌کند و Object/Function یا false را حذف می‌کند. این رفتار را برای نسخه واقعی React خود Contract-test کنید.

سطح InteropتستAdapter محتمل
Primitive inputstring/number/booleanNormalization
Rich propertyobject before/after upgraderef/effect یا wrapper
Custom eventname/composed/detailaddEventListener wrapper
SSRserver markup parityserialization policy
Hydrationwarning/node identityclient-only boundary/DSD adapter
TypesJSX/TS autocompleteIntrinsicElements declaration
Formssubmit/reset/validationframework form adapter

Framework-agnostic یعنی Runtime-free نیست

خروجی ممکن است استاندارد DOM باشد، اما Component تولیدشده با Lit، Stencil یا ابزار دیگر می‌تواند Runtime، Helper یا Convention داشته باشد. اندازه، نسخه، SSR adapter، Decorator/build pipeline و Debugging بخشی از TCO است. یک ADR بنویسید: Native، Library-assisted یا Compiler-generated؛ سپس خروجی Bundle و API را Pilot کنید.

Packaging و Registration

CustomElementRegistry سراسری نام را یک‌بار ثبت می‌کند؛ تعریف دوباره همان نام با Class دیگر خطا می‌دهد. Package باید روشن کند Import چه اثری دارد.

تصمیمگزینهTrade-off
RegistrationSide-effect importساده، Tree-shaking/تست سخت‌تر
RegistrationExport class + define functionکنترل بیشتر، API بزرگ‌تر
DistributionSource ESMConsumer build dependency
DistributionCompiled ESMسازگاری هدف روشن لازم
DependenciesBundled/peer/externalتکرار در برابر هماهنگی نسخه
TypesProperties/events/JSXGenerator و drift control
MetadataMachine-readable manifestIDE/docs integration

Scoped custom element registries در Living Standard حضور دارند، اما Presence در Specification مساوی Compatibility ناوگان شما نیست. تا عبور از Browser/WebView/SSR matrix، آن را راه‌حل قطعی تضاد نسخه ندانید.

Versioning؛ DOM API هم Breaking change دارد

تغییرنوع محتملMigration
حذف Attribute/PropertyBreakingDeprecation warning و codemod
تغییر Event detailBreakingVersioned schema
تغییر Slot/Part nameBreakingAlias دوره‌ای
تغییر Default semanticsBreaking/UXAccessibility regression gate
افزودن Optional propertyMinorDefault پایدار
اصلاح داخلی بدون ContractPatchContract tests

Tag versioned مثل x-button-v2 گاهی برای Coexistence لازم است، اما Naming debt می‌سازد. Registry conflict، dual version و Consumer migration را مثل بدهی فنی مدیریت کنید؛ چارچوب تصمیم در راهنمای مدیریت بدهی فنی آمده است.

Testing matrix تولید

JSDOM-only برای Shadow/Event/Form/Accessibility/Rendering کافی نیست. لایه اصلی باید در Browser واقعی باشد. پروژه Web Platform Tests روش آزمون بین‌مرورگری استانداردهای وب را مستند می‌کند؛ تست محصول شما باید روی همان روحیه، اما با Contract کسب‌وکار ساخته شود.

لایهموارد
APIattr/property/reflection/default/error
Lifecyclepre-upgrade/connect/move/disconnect/reconnect/cleanup
Compositionslot/fallback/slotchange/nested shadow
Eventsretarget/bubbles/composed/cancel/timing
Formlabel/value/submit/reset/validity/restore
A11ytree/keyboard/focus/AT/contrast/zoom
SSRDSD/no-JS/hydration/mismatch/sanitizer
FrameworkReact/Vue/Angular/Svelte versions in scope
Localefa-IR/RTL/BiDi/number/currency/date/font
Performance۱/۱۰۰/۱۰۰۰ instances، bundle، INP/CLS/memory
Securityuntrusted input/XSS/CSP/dependency
Compatibilitybrowser/mobile WebView/assistive tech

Visual regression کافی نیست

Screenshot می‌گوید Pixel تغییر کرده؛ نمی‌گوید Event composed است، Form value Submit می‌شود، accessible name درست است یا Cleanup انجام شده. Contract test، interaction test، accessibility tree snapshot، performance budget و consumer integration را کنار Visual regression بگذارید.

CI/CD و انتشار کتابخانه

  • Package build reproducible، Lockfile و Provenance داشته باشد.
  • API/Custom Elements manifest و Type declarations با کد Drift نکنند.
  • Browser/framework matrix در Pull request روی Critical component اجرا شود.
  • Bundle/performance/accessibility budget Gate داشته باشد.
  • Canary channel، changelog، deprecation telemetry و rollback مشخص باشد.
  • Consumer app نمونه روی نسخه منتشرشده، نه Workspace source، تست شود.

Artifact، SBOM، Progressive delivery و Rollback در راهنمای CI/CD امن کامل‌تر شرح داده شده‌اند.

Observability برای Component library

Telemetry را به Spyware UI تبدیل نکنید. Component می‌تواند Version، upgrade duration، render error و feature state کم‌حجم بدهد؛ Business event متعلق به App است. PII و محتوای Slot را Log نکنید. Error را با component/version/consumer/browser و sample rate کنترل‌شده پیوند دهید.

SignalOwnerتصمیم
Definition load failurePlatformRollback/CDN
Upgrade/hydration errorComponentFix compatibility
Event contract errorConsumer+ComponentAdapter/version
Accessibility regressionDesign systemBlock release
INP/CLS by versionPerformanceCanary/rollback
Deprecated API usageMigration ownerConsumer outreach

مهاجرت تدریجی

  1. Inventory: Component، Consumer، Framework/version، Usage و pain را فهرست کنید.
  2. Contract: DOM API، Semantics، Token، SSR و Compatibility را فریز کنید.
  3. Pilot: یک Component متوسط و قابل‌بازگشت؛ نه Button پایه یا Checkout بحرانی.
  4. Adapter: Wrapper و Type برای Frameworkهای واقعی بسازید.
  5. Parity: Visual، interaction، a11y، performance و analytics را مقایسه کنید.
  6. Canary: یک Consumer/Cohort را مهاجرت و Error/INP/Support را Guardrail کنید.
  7. Deprecate: Migration guide، codemod، deadline و owner بدهید.
  8. Remove: پس از صفرشدن Usage و داشتن rollback، نسخه قدیمی را حذف کنید.

ملاحظات ایران

  • Browser/WebView: فرض «همه مرورگرهای مدرن» را با سهم واقعی Chrome/Firefox/Safari و Android WebView داخل اپ‌ها جایگزین کنید.
  • شبکه: Registry/CDN خارجی، Source map و Dynamic import را روی چند ISP و اختلال/Timeout آزمایش و Mirror/Cache مجاز طراحی کنید.
  • Supply chain: دسترسی NPM/Git/CDN، تحریم/شرایط خدمت، Integrity، Vendor package و مسیر Restore آفلاین را بررسی کنید.
  • فارسی: RTL/BiDi، فونت، نیم‌فاصله، ی/ک، رقم، ریال/تومان، شمسی و طول ترجمه را Fixture رسمی کنید.
  • SEO: HTML اصلی را Server-render کنید تا اختلال JavaScript یا شبکه محتوای حیاتی را حذف نکند.
  • Performance: Budget را روی Android میان‌رده و شبکه واقعی، نه Laptop توسعه‌دهنده، تعیین کنید.
  • پشتیبانی: Browser/version/component build را بدون PII در گزارش خطا قابل‌استخراج کنید.

Runbookهای ضروری

IncidentContainRecover
Definition load failedFallback/disable enhancementCDN/package rollback
Registry duplicateStop second registrationDependency dedupe/version plan
Hydration mismatchClient boundary/fallbackServer-client parity fix
Form value missingDisable rollout/backup inputElementInternals contract fix
A11y regressionRollback critical componentSemantics/keyboard/AT verification
Performance regressionCanary stop/lazy feature offbundle/instance profiling
XSS/dependency issueFeature/package quarantinepatch/provenance/consumer update

برنامه ۹۰روزه Web Components

روز ۱ تا ۳۰: Fit و Contract

  • Consumer/Framework/Browser/WebView/SSR/AT inventory بسازید.
  • Native HTML، Framework component و Web Component را مقایسه کنید.
  • یک Pilot و Owner انتخاب کنید.
  • Attribute/Property/Event/Slot/Part/Semantics contract بنویسید.
  • Baseline bundle/CWV/a11y/defect و Exit criteria ثبت کنید.

روز ۳۱ تا ۶۰: Build و Matrix

  • Native-first و Progressive enhancement را پیاده کنید.
  • SSR/DSD/no-JS و Upgrade/Hydration را تست کنید.
  • React و سایر Consumerهای در Scope را Contract-test کنید.
  • Form/A11y/RTL/BiDi/Performance/Security matrix را اجرا کنید.
  • Package/Types/Docs/Manifest/Provenance و Runbook بسازید.

روز ۶۱ تا ۹۰: Canary و تصمیم

  • یک Consumer را Canary و Version telemetry را فعال کنید.
  • Error، INP/CLS، a11y، support و developer time را مقایسه کنید.
  • Migration/Deprecation/rollback را تمرین کنید.
  • TCO شامل Adapter، Matrix و Governance را محاسبه کنید.
  • Scale، Iterate یا Stop را با Evidence ثبت کنید.

اشتباه‌های پرتکرار

  • فرض اینکه Web Components جایگزین Framework است.
  • ساخت Button/Input بومی از صفر بدون Semantics و Form.
  • نامیدن Shadow DOM به‌عنوان مرز امنیتی یا CSS نفوذناپذیر.
  • ارسال Object با Attribute و نداشتن Property contract.
  • رویداد داخلی بدون bubbles/composed یا Leak همه Eventها.
  • وابستگی Consumer به Class داخلی به‌جای Part/Token.
  • کار سنگین/خواندن Child در Constructor و Cleanup ناقص.
  • CSR-only برای محتوای اصلی و ادعای SEO خودکار.
  • انتظار Performance بهتر فقط به‌خاطر Shadow.
  • تست صرفاً با JSDOM یا Screenshot.
  • ادعای Framework agnostic بدون React/SSR/Form tests.
  • ثبت Side-effect نامشخص و تضاد CustomElementRegistry.
  • تغییر Slot/Part/Event بدون SemVer و Migration.
  • استفاده از قابلیت تازه Specification بدون Support matrix.
  • Pilot روی Checkout بحرانی به‌جای Component قابل‌بازگشت.

چک‌لیست Production

  • Fit نسبت به Native/Framework با ADR و Pilot ثابت شده است.
  • Name/Owner/Maturity/Consumers و Version policy روشن است.
  • Attribute/Property/Method/Event/Slot/Part/State contract مستند است.
  • Constructor/Lifecycle/cleanup و pre-upgrade property تست شده‌اند.
  • Light/Shadow و open/closed براساس نیاز، نه مد، انتخاب شده است.
  • Native semantics یا ElementInternals/Form contract کامل است.
  • Keyboard/Focus/AT/WCAG/forced-colors/zoom عبور کرده‌اند.
  • fa-IR/RTL/BiDi/عدد/ریال‌تومان/شمسی/فونت تست شده‌اند.
  • SSR/DSD/no-JS/Hydration/Sanitizer parity تأیید شده است.
  • React/Framework/Type/Form adapterهای لازم Contract-test دارند.
  • Bundle/instance/INP/CLS/memory Budget در Device واقعی دارد.
  • Untrusted input، XSS، CSP و Supply chain کنترل شده‌اند.
  • Package/Types/Manifest/Docs/Provenance reproducible هستند.
  • Canary/Telemetry/Deprecation/Migration/Rollback آماده است.

پرسش‌های متداول

Web Components با Custom Elements چه تفاوتی دارد؟

Custom Elements API تعریف Tag و Lifecycle را می‌دهد؛ Web Components اصطلاح چتری برای استفاده از Custom Elements همراه Shadow DOM، Template، Slot و قابلیت‌های مرتبط است. می‌توانید Custom element بدون Shadow DOM بسازید و هر Component هم لزوماً همه این APIها را نیاز ندارد.

آیا وب کامپوننت‌ها جایگزین React، Vue یا Angular می‌شوند؟

معمولاً نه. Web Component می‌تواند مرز UI قابل‌مصرف باشد و Framework همچنان Routing، State، Data و Rendering اپ را مدیریت کند. Property، Event، SSR، Hydration و Typeها باید روی نسخه واقعی هر Framework آزمایش شوند و گاهی Wrapper لازم است.

آیا Shadow DOM برای امنیت مناسب است؟

خیر؛ Shadow DOM مرز Tree و Style scoping است، نه Sandbox امنیتی. حالت closed نیز Secret را محافظت نمی‌کند. برای کد یا محتوای نامطمئن از Authorization، Sanitization/Trusted Types، CSP و در صورت نیاز iframe/Origin boundary استفاده کنید.

آیا Web Components برای SEO و سرعت بهترند؟

خودکار نه. نتیجه به HTML سرور، JavaScript، Upgrade/Hydration، DOM/CSS، تعداد Instance و دسترسی محتوا بستگی دارد. محتوای حیاتی را SSR/Progressive enhancement کنید و LCP/INP/CLS و Crawl/Index را روی خروجی واقعی بسنجید.

برای پروژه فارسی از کجا شروع کنیم؟

یک Component متوسط و قابل‌بازگشت انتخاب کنید؛ Contract Attribute/Property/Event/Slot/Part/Semantics بنویسید؛ fa-IR/RTL/BiDi/ریال‌تومان و Android WebView واقعی را به Matrix اضافه کنید؛ SSR/no-JS، Accessibility، React و Performance را Pilot و فقط پس از Canary مقیاس دهید.

جمع‌بندی: استاندارد، نقطه شروع قرارداد است

وب کامپوننت‌ها می‌توانند عمر یک رابط را از چرخه یک Framework جدا کنند، اما فقط وقتی DOM API، Semantics، Styling، Rendering و Versioning پایدار باشند. Tag سفارشی بدون این قرارداد، Lock-in را حذف نمی‌کند؛ فقط آن را از Framework به Implementation داخلی منتقل می‌کند.

اگر امروز فقط یک کار انجام می‌دهید، یک Component موجود را انتخاب و Public surface آن را روی یک صفحه بنویسید: Attribute، Property، Event، Slot، Part، Semantics، SSR و Consumer matrix. اگر این صفحه مبهم است، هنوز زمان نوشتن Class نرسیده است.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *