تیم شما یک <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 کاملاً نفوذناپذیر است |
| Template | Fragment غیرفعال برای Clone/SSR pattern | همیشه سریعتر از DOM API است |
| Slot | Composition محتوای 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/Owner | Namespace، مالک، maturity | md-price، تیم Commerce |
| Attributes | Primitive/SSR-friendly config | currency="IRR" |
| Properties | Object/Array/Function و runtime state | price.value = 120000 |
| Events | نام، detail، bubbles/composed/cancelable | md-change |
| Methods | Imperative capability ضروری | focus()، نه getterهای داخلی |
| Slots | Composition و fallback | slot="label" |
| Styles | Tokens، Parts، States | --md-accent، ::part(label) |
| Semantics | Role، name، keyboard، form | Button/Input behavior |
| Async/Error | Loading، abort، retry، error | Event یا State مستند |
| Rendering | Light/Shadow، SSR، upgrade، no-JS | Declarative Shadow DOM |
| Compatibility | Browser/Framework/version matrix | Chrome/Firefox/Safari/WebView |
این Contract باید در همان Governance سیستم طراحی ثبت شود. معماری Token، Component maturity، Ownership و Migration در راهنمای ساخت و نگهداری سیستم طراحی عمیقتر پوشش داده شده است.
مرز Component را درست انتخاب کنید
| نوع | مالک چه چیزی است؟ | از چه چیزی پرهیز کند؟ |
|---|---|---|
| Primitive | Semantics و Visual state | Business API و Fetch |
| Composite | ترکیب چند Primitive | Global navigation/state |
| Form control | Value/validity/focus/label | ارسال مستقیم سفارش |
| Data display | نمایش داده آماده | مالکیت Source of truth |
| Embeddable widget | Config، isolation، telemetry | دسترسی نامحدود به Host |
| App island | Workflow محدود و مستقل | تبدیل هر صفحه به 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 را متقارن کنید.- در
disconnectedCallbackListener بیرونی، 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 باید آگاهانه و یکطرفه/دوطرفه مستند شود.
| داده | Attribute | Property | قاعده |
|---|---|---|---|
| Label/variant | مناسب | اختیاری | Primitive و Serializable |
| Boolean | Presence semantics | Boolean | disabled="false" هنوز حاضر است |
| Number | Parse/validate لازم | Number | NaN و range policy |
| Object/Array | نامناسب مگر JSON قراردادی | مناسب | Reference/mutation policy |
| Callback | نامناسب | گاهی Method/property | Event معمولاً decoupledتر است |
| State داخلی | فقط اگر Public/serializable | Private | Implementation را 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 |
|---|---|
| Name | Namespace و Semantics چیست؟ |
| detail | Schema، version و PII policy چیست؟ |
| bubbles | Delegation لازم است؟ |
| composed | باید از Shadow boundary خارج شود؟ |
| cancelable | Consumer حق جلوگیری دارد؟ |
| 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 زیاد | Shadow | Theming/API دقیق لازم |
| محتوای مقاله/SEO و CSS سایت | Light | Isolation کمتر |
| Primitive سیستم طراحی | Shadow یا native wrapper | Accessibility/Parts contract |
| Layout ساده | Light/CSS | Custom element شاید اضافه باشد |
| SSR با Encapsulation | Declarative Shadow DOM | Toolchain/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-accent | Type، fallback، contrast |
| Part | md-card::part(title) | نام و scope پایدار |
| Host state | [disabled] یا :state(busy) | Semantics و transition |
| Slot | ::slotted(...) | فقط Node تخصیصیافته مستقیم |
| Internal class | .title | Public نیست |
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 واقعی بنا کنید. فرآیند کامل در راهنمای ممیزی دسترسپذیری وب آمده است.
| سطح تست | نمونه |
|---|---|
| Semantics | Role/name/value/state در Accessibility tree |
| Keyboard | Tab، Enter/Space، Arrow، Escape، Focus return |
| Visual | Focus visible، Contrast، Forced colors، ۲۰۰%/۴۰۰% |
| Content | Label/Error/Instruction و Dynamic announcement |
| Form | Submit/Reset/Invalid/Autofill |
| AT matrix | حداقل Browser+Screen readerهای کاربران واقعی |
RTL، زبان فارسی و Internationalization
dirوlangHost/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/eval | ESM split بر اساس Route/visibility |
| CSS تکراری در Instance | Memory/style cost | Shared stylesheet strategy با Support test |
| Upgrade دیر | FOUC/CLS/interaction gap | SSR/DSD، critical definition، fallback |
| Hydration سنگین | Long task/INP | Progressive/island hydration |
| DOM عمیق | node count/layout | مرز کوچک و Markup ساده |
| Observer/Listener نشت | memory/CPU drift | Lifecycle cleanup test |
| Dependency تکراری | bundle duplication | Peer/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.innerHTMLSanitization خودکار ندارند. - 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 input | string/number/boolean | Normalization |
| Rich property | object before/after upgrade | ref/effect یا wrapper |
| Custom event | name/composed/detail | addEventListener wrapper |
| SSR | server markup parity | serialization policy |
| Hydration | warning/node identity | client-only boundary/DSD adapter |
| Types | JSX/TS autocomplete | IntrinsicElements declaration |
| Forms | submit/reset/validation | framework 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 |
|---|---|---|
| Registration | Side-effect import | ساده، Tree-shaking/تست سختتر |
| Registration | Export class + define function | کنترل بیشتر، API بزرگتر |
| Distribution | Source ESM | Consumer build dependency |
| Distribution | Compiled ESM | سازگاری هدف روشن لازم |
| Dependencies | Bundled/peer/external | تکرار در برابر هماهنگی نسخه |
| Types | Properties/events/JSX | Generator و drift control |
| Metadata | Machine-readable manifest | IDE/docs integration |
Scoped custom element registries در Living Standard حضور دارند، اما Presence در Specification مساوی Compatibility ناوگان شما نیست. تا عبور از Browser/WebView/SSR matrix، آن را راهحل قطعی تضاد نسخه ندانید.
Versioning؛ DOM API هم Breaking change دارد
| تغییر | نوع محتمل | Migration |
|---|---|---|
| حذف Attribute/Property | Breaking | Deprecation warning و codemod |
| تغییر Event detail | Breaking | Versioned schema |
| تغییر Slot/Part name | Breaking | Alias دورهای |
| تغییر Default semantics | Breaking/UX | Accessibility regression gate |
| افزودن Optional property | Minor | Default پایدار |
| اصلاح داخلی بدون Contract | Patch | Contract 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 کسبوکار ساخته شود.
| لایه | موارد |
|---|---|
| API | attr/property/reflection/default/error |
| Lifecycle | pre-upgrade/connect/move/disconnect/reconnect/cleanup |
| Composition | slot/fallback/slotchange/nested shadow |
| Events | retarget/bubbles/composed/cancel/timing |
| Form | label/value/submit/reset/validity/restore |
| A11y | tree/keyboard/focus/AT/contrast/zoom |
| SSR | DSD/no-JS/hydration/mismatch/sanitizer |
| Framework | React/Vue/Angular/Svelte versions in scope |
| Locale | fa-IR/RTL/BiDi/number/currency/date/font |
| Performance | ۱/۱۰۰/۱۰۰۰ instances، bundle، INP/CLS/memory |
| Security | untrusted input/XSS/CSP/dependency |
| Compatibility | browser/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 کنترلشده پیوند دهید.
| Signal | Owner | تصمیم |
|---|---|---|
| Definition load failure | Platform | Rollback/CDN |
| Upgrade/hydration error | Component | Fix compatibility |
| Event contract error | Consumer+Component | Adapter/version |
| Accessibility regression | Design system | Block release |
| INP/CLS by version | Performance | Canary/rollback |
| Deprecated API usage | Migration owner | Consumer outreach |
مهاجرت تدریجی
- Inventory: Component، Consumer، Framework/version، Usage و pain را فهرست کنید.
- Contract: DOM API، Semantics، Token، SSR و Compatibility را فریز کنید.
- Pilot: یک Component متوسط و قابلبازگشت؛ نه Button پایه یا Checkout بحرانی.
- Adapter: Wrapper و Type برای Frameworkهای واقعی بسازید.
- Parity: Visual، interaction، a11y، performance و analytics را مقایسه کنید.
- Canary: یک Consumer/Cohort را مهاجرت و Error/INP/Support را Guardrail کنید.
- Deprecate: Migration guide، codemod، deadline و owner بدهید.
- 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های ضروری
| Incident | Contain | Recover |
|---|---|---|
| Definition load failed | Fallback/disable enhancement | CDN/package rollback |
| Registry duplicate | Stop second registration | Dependency dedupe/version plan |
| Hydration mismatch | Client boundary/fallback | Server-client parity fix |
| Form value missing | Disable rollout/backup input | ElementInternals contract fix |
| A11y regression | Rollback critical component | Semantics/keyboard/AT verification |
| Performance regression | Canary stop/lazy feature off | bundle/instance profiling |
| XSS/dependency issue | Feature/package quarantine | patch/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 نرسیده است.






