プリレンダリングとは、カスタム要素のマークアップ(シャドウツリーを含む)をサーバーから送信することを意味します。これにより、JavaScript が実行される前にコンポーネントのスタイルと構造が設定されます。このための主なプラットフォーム プリミティブは、宣言型シャドウ DOM(DSD)です。パーサーは HTML のみからシャドウルートをアタッチし、要素は後でクラスによってアップグレード(「ハイドレート」)されます(登録されている場合)。このパターンは、サーバーサイド レンダリング、ストリーミング、スタイルが適用されていないコンテンツのフラッシュ(FOUC)の回避に使用します。
宣言型シャドウルートの作成
shadowrootmode 属性を持つ <template> をホストの最初の子としてネストします。パーサーは、そのコンテンツをシャドウルートとしてアタッチし、テンプレートを削除します。スクリプトは必要ありません。
<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>
水分補給のタイミング
一致するクラスが登録されると、ブラウザはホストをアップグレードします。シャドウルートはすでに存在しているため、コンストラクタは 2 つ目のシャドウルートを無条件に作成してはなりません。
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 が最初の描画で正しくレンダリングされるのは、スタイルが宣言型テンプレート内で提供されている場合のみです。これを維持するには、次の操作を行います。
- コンポーネントの CSS は、スクリプトで読み込まれる別のシートではなく、
<template shadowrootmode>(<style>タグまたは<link>)の内部に配置します。 my-card:not(:defined) { … }と組み合わせるのは、JS を待つ必要があるスタイルのみにしてください。通常は、SSR パスを逃したクライアント レンダリング インスタンスのフェードアウトなど、ページレベルのガードルールです。これらは、light-DOM(ページ)スタイルシートにあります。シャドウルートがまだ存在せず、:hostセレクタが外部から適用されないためです。DSD テンプレート内の構造スタイルには、このようなガードは必要ありません。- ハイドレーション時にシャドウツリーを再レンダリングしないでください。サーバー マークアップを信頼できるものとして扱い、動作を関連付けます。
代替戦略
shadowrootmode を解析しないエンジンでは、テンプレートは不活性であり、そのコンテンツはシャドウルートとしてアタッチされません。これを検出し(Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode'))、存在しない場合は、アップグレード中に同じマークアップからシャドウルートを強制的にアタッチして、コンポーネントがレンダリングされるようにします。