עיבוד מראש פירושו שליחת תגי עיצוב של רכיב מותאם אישית – כולל עץ הצל שלו – מהשרת, כך שהרכיב מעוצב ומובנה לפני ש-JavaScript פועל. הפרימיטיב העיקרי של הפלטפורמה למטרה הזו הוא Declarative Shadow DOM (DSD): המנתח מצרף את שורש הצל מ-HTML בלבד, והרכיב משודרג מאוחר יותר ("עובר הידרציה") על ידי המחלקה שלו אם היא רשומה. השתמשו בתבנית הזו לעיבוד בצד השרת, לסטרימינג וכדי למנוע הבזק של תוכן לא מעוצב (FOUC).
יצירת שורש צל הצהרתי
מטמיעים <template> עם המאפיין shadowrootmode בתור הצאצא הראשון של המארח. המנתח מצרף את התוכן שלו כשורש צל ומסיר את התבנית. אין צורך בסקריפט.
<my-card>
<template shadowrootmode="open">
<style>
.card { border: 1px solid; border-radius: 8px; padding: 1rem; }
</style>
<div class="card">
<slot name="header"></slot>
<div class="body"><slot></slot></div>
</div>
</template>
<!-- Light DOM children are projected into the slots above. -->
<h2 slot="header">Server-rendered title</h2>
<p>Visible and styled before JS loads.</p>
</my-card>
תזמון שתיית מים
כשהמחלקה התואמת רשומה, הדפדפן משדרג את המארח. מכיוון ששורש הצל כבר קיים, ה-constructor לא צריך ליצור שורש צל שני באופן עיוור:
class MyCard extends HTMLElement {
constructor() {
super();
// Reuse the server-rendered shadow root if present; only create one if absent.
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
this.shadowRoot.append(template.content.cloneNode(true));
}
// Hydrate in place: attach listeners once against existing markup (not unguarded
// in connectedCallback, which re-runs if the element moves in the DOM), and do
// NOT re-render; that would discard the server's DOM and flash.
this.shadowRoot.getElementById('toggle')
?.addEventListener('click', () => this.toggleAttribute('open'));
}
}
כדאי להגדיר את shadowrootmode="open" (ולא את closed) כמעט בכל המקרים, כדי שסקריפטים וכלים יוכלו להגיע אל this.shadowRoot לצורך הידרציה. כדי להמיר שורש צל קיים בחזרה ל-HTML בשרת או בבדיקות, משתמשים ב-getHTML({ serializableShadowRoots: true }) ומצרפים את השורש באמצעות serializable: true.
איך להימנע מ-FOUC
DSD מעבד בצורה נכונה את ה-First Paint רק אם הסגנונות נשלחים בתוך התבנית ההצהרתית. כדי לשמור על זה:
- ממקמים את ה-CSS של הרכיב בתוך ה-
<template shadowrootmode>(תג<style>או<link>), ולא בגיליון נפרד שנטען באמצעות סקריפט. - כדאי לשלב עם
my-card:not(:defined) { … }רק לסגנונות שצריכים להמתין ל-JS – בדרך כלל כללי הגנה ברמת הדף, כמו דהייה במופעים שעברו עיבוד בצד הלקוח ומחמיצים את מעבר ה-SSR. הם נמצאים בגיליון הסגנונות של ה-DOM הבהיר (הדף), כי שורש הצל עדיין לא קיים שם ובוררי:hostלא חלים מבחוץ. סגנונות מבניים בתוך תבנית ה-DSD לא צריכים הגנה כזו. - אל תעבדו מחדש את עץ הצללים בזמן ההידרציה. להתייחס לתגי העיצוב של השרת כסמכותיים ולצרף אליהם התנהגות.
אסטרטגיות חלופיות
במנועים שלא מנתחים את shadowrootmode, התבנית לא פעילה והתוכן שלה לא מצורף כשורש צל. צריך לזהות את זה (Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode')) ואם הוא לא קיים, לצרף את ה-shadow root באופן הכרחי מאותו תיוג במהלך השדרוג, כדי שהרכיב עדיין יעובד.