預先算繪自訂元素

網頁功能: 宣告式 Shadow DOM

預先轉譯是指從伺服器傳送自訂元素的標記 (包括其陰影樹狀結構),因此元件會在任何 JavaScript 執行之前設定樣式和結構。這項功能的主要平台原始碼是宣告式 Shadow DOM (DSD):剖析器只會從 HTML 附加 Shadow Root,而元素稍後會由其類別升級 (「凍結」) (如有註冊)。這個模式適用於伺服器端轉譯、串流,以及避免未套用樣式的內容閃爍 (FOUC)。

編寫宣告式 Shadow Root

在主機的第一個子項中,巢狀結構 <template> 搭配 shadowrootmode 屬性。剖析器會將範本內容附加為 Shadow Root,並移除範本,不需使用指令碼。

<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>

飲水時間

註冊相符的類別後,瀏覽器會升級主體。由於 Shadow Root 已存在,建構函式不得盲目建立第二個:

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 進行補充。如要在伺服器或測試中,將現有的 Shadow Root 序列化回 HTML,請使用 getHTML({ serializableShadowRoots: true }),並使用 serializable: true 附加根目錄。

避免 FOUC

只有在樣式位於宣告式範本內時,DSD 才會在首次繪製時正確算繪。如要保留這項設定,請按照下列步驟操作:

  • 將元件 CSS 放在 <template shadowrootmode> (<style> 標記或 <link>) 內,而非放在由指令碼載入的獨立樣式表中。
  • 只有在樣式必須等待 JS 時,才與 my-card:not(:defined) { … } 搭配使用,通常是網頁層級的防護規則,例如在用戶端算繪的例項上淡出,而這些例項未通過 SSR。這些樣式位於 light-DOM (網頁) 樣式表中,因為 shadow root 尚未存在,且 :host 選取器不會從外部套用。DSD 範本中的結構樣式則不需要這類防護。
  • 請勿在凍結狀態還原時重新轉譯 Shadow Tree。將伺服器標記視為權威,並將行為附加至該標記。

備用策略

在不剖析 shadowrootmode 的引擎中,範本會處於閒置狀態,且其內容不會附加為陰影根。偵測這個 (Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode')),如果不存在,請在升級期間從相同的標記強制附加 shadow root,讓元件仍可算繪。