混合 Shadow モード (開発者プレビュー) でのコンポーネントの作成

混合 Shadow モードでは、合成 Shadow ポリフィルが適用される場合でもコンポーネントでネイティブ Shadow DOM を使用できます。

混合 Shadow モードは、開発者プレビューとして使用できます。この機能は、Salesforce がドキュメント、プレスリリース、または公式声明で正式リリースを発表しない限り、正式リリースされません。すべてのコマンド、パラメータ、およびその他の機能は、通知の有無に関わらずいつでも変更または廃止される可能性があります。これらのコマンドまたはツールを使用して開発した機能を実装しないでください。

Note

すべての主要なブラウザで Shadow DOM がサポートされるようになりました。Salesforce では、古いバージョンの Microsoft Edge などの従来のブラウザのために合成 Shadow ポリフィルが保持されています。

ポリフィルは、開発やテストを簡略化するために Shadow DOM をサポートするブラウザでも現在使用されます。混合 Shadow モードでは、各自のアプリケーションでネイティブ Shadow を使用したときの速度と効率をできる限り実現します。将来的には、ネイティブ Shadow を完全に使用するための移行をさらに迅速に行うことができるようになります。

ネイティブ Shadow と合成 Shadow には、カプセル化の実現方法 (スロットとスタイル設定) など、いくつかの違いがあります。合成 Shadow DOM の場合、アプリケーションでグローバルスタイルシートを使用することでスタイルが適用されますが、ネイティブ Shadow の場合、コンポーネントのスタイルはコンポーネントバンドルに追加されます。また、::part などの一部の Shadow DOM 機能は、合成 Shadow ではサポートされていません。最も重要なことは、ネイティブ Shadow コンポーネントは合成 Shadow よりも高速でパフォーマンスに優れているという点です。

混合モードコンポーネントが含まれるアプリケーションの例を次に示します。これには、c-native および c-synthetic 子コンポーネントが含まれる親 c-app コンポーネントがあります。Web コンソールでネイティブ Shadow コンポーネントを調査するときに、これらは、#shadow-root タグ内に表示されます。合成 Shadow ルートは、開発者ツールでは表示されませんが、次の例では、説明のために表示されています。

1<!-- c-app -->
2<c-app>
3    <c-native>
4     #shadow-root
5     |  <div>Your content for the native component</div>
6    </c-native>
7    <c-synthetic>
8        <div>Your content for the synthetic component</div>
9    </c-synthetic>
10</template>

この記事の目的に合わせて、親および子コンポーネントは上位コンポーネントおよび下位コンポーネントとも呼ばれます。一方、別のコンポーネントを拡張するコンポーネントは、サブクラスおよびそのスーパークラスと呼ばれます。

LWC では継承が許可されていますが、通常は構成のほうがより効果的なため継承は推奨されません。継承は名前空間を超えて機能せず、lightning 名前空間は拡張できません。

Note

合成コンポーネントにネイティブコンポーネントを含めることはできますが、ネイティブコンポーネントに合成コンポーネントを含めることはできません。

1<!-- c-app -->
2<c-app>
3  #shadow-root (synthetic)
4  <c-synthetic>
5    #shadow-root (synthetic)
6    <c-native>
7      #shadow-root (native)
8      <p>Content inside a native shadow root</p>
9    </c-native>
10  </c-synthetic>
11</c-app>

混合 Shadow モードの有効化 

デフォルトでは、混合 Shadow モードは組織で使用できません。Salesforce に連絡して、開発者プレビューに参加してください。

Note

コンポーネントで混合 Shadow モードを有効にするには、静的 shadowSupportMode プロパティを any に設定します。

1// native.js
2import { LightningElement } from "lwc";
3export default class MixedModeApp extends LightningElement {
4  static shadowSupportMode = "any";
5}

shadowSupportMode に使用できる値は次のとおりです。

  • any — ネイティブ Shadow DOM でコンポーネントサブツリー全体を表示します (可能な場合)。ブラウザで Shadow DOM がサポートされていない場合、サブツリーは合成 Shadow で表示されます。
  • reset — サブクラスがそのスーパークラスから shadowSupportMode 値を受け取ることをオプトアウトできるようにします。reset が適用されるのは、コンポーネントのスーパークラスが any を使用しており、親コンポーネントが any を使用していない場合のみです。

shadowSupportModeany に設定して混合 Shadow モードを有効にする前に、サブツリーのすべてのコンポーネントがネイティブ Shadow DOM と互換性があることを確認することをお勧めします。たとえば、属性セレクタ [dir=""] は合成 Shadow DOM でのみ機能するため、ネイティブ Shadow DOM サブツリーでは使用できません。代わりに、ブラウザでネイティブ Shadow DOM の :dir() 疑似クラスがサポートされているかどうかを確認します。または、リーフコンポーネントからコンポーネントツリーを登っていき、混合 Shadow モードが期待どおりに動作していることを確認することもできます。

混合 Shadow モードの使用 

親コンポーネントで any が使用されている場合、サブツリー内のすべてのコンポーネントは、その shadowSupportMode の値に関係なく、ネイティブ Shadow で動作します。ネイティブ Shadow モードのコンポーネントには、ネイティブ Shadow モードの子コンポーネントのみを含めることができます。この制限は、サブツリー全体に適用される shadowSupportMode プロパティの副次的影響です。

デフォルトでは、スロットコンテンツは、ネイティブ Shadow で表示されます。スロットコンテンツは、ネストされているコンポーネントの下位になりません。このため、合成 Shadow コンテンツはネイティブ Shadow コンポーネント内にスロット化できます。これは、スロットを含むコンポーネントの #shadow-root がブラウザでどのように表示されるかの影響をスロットコンテンツが受けないことも意味します。たとえば、ブラウザでネイティブコンポーネントが合成 Shadow に表示される場合、そのコンポーネント内にスロット化されたネイティブコンテンツは、依然としてネイティブ Shadow に表示されます。

以下の <c-parent> には、子コンポーネント <c-child> とスロットコンテンツ <c-slotted> を含むネイティブ Shadow サブツリーがあります。<c-child><c-parent> の下位であるためネイティブ Shadow で機能しますが、<c-slotted><c-parent> の下位ではないため合成 Shadow に配置できます。

1<!-- c-app -->
2<c-app>
3  #shadow-root (synthetic)
4  <c-parent>
5    #shadow-root (native)
6    <p>Parent component</p>
7    <slot>
8      <c-slotted>
9        #shadow-root (synthetic)
10        <p>Slotted content</p>
11    </slot>
12    <c-child>
13      #shadow-root (native)
14      <p>Child component</p>
15    </c-child>
16  </c-parent>
17</c-app>

@lwc/synthetic-shadow ポリフィルを含めて、shadowSupportModeany または reset に設定したときに、Shadow DOM をサポートするブラウザで親および子コンポーネントがどのようにレンダリングされるのかを次に示します。

1<!-- c-parent -->
2<template>
3  <c-child></c-child>
4</template>
Shadow モード
--synthetic
resetresetsynthetic
anyanynative
anyresetnative
resetany親: synthetic、子: native
-any親: synthetic、子: native
-resetsynthetic

@lwc/synthetic-shadow ポリフィルを含めるときに、shadowSupportModereset に設定して Shadow モードをデフォルトの動作にリセットします。

  • ポリフィルが存在する場合、reset によって Shadow モードが合成 Shadow に設定されます。
  • ポリフィルが存在しない場合、shadowSupportMode の影響はなく、コンポーネントは、ネイティブ Shadow でレンダリングされます。

要素に合成 Shadow ルートがあるかどうかを判断するには、this.template.synthetic を使用します。

1export default class extends LightningElement {
2  renderedCallback() {
3    console.log("Synthetic?", !!this.template.synthetic);
4  }
5}

例: ネイティブ Shadow コンポーネントと合成 Shadow コンポーネントの使用 

アプリケーションには、ネイティブ Shadow コンポーネントと合成 Shadow コンポーネントの両方を含めることができます。この例では、アプリケーションで @lwc/synthetic-shadow ポリフィルが使用されていることを前提としています。

1<!-- c-app -->
2<c-app>
3  <c-native>
4    #shadow-root 
5    |  <div>Your content for the native component</div>
6  </c-native>
7  <c-synthetic>
8    <div>Your content for the synthetic component</div>
9  </c-synthetic>
10</c-app>

アプリケーションには、異なるモードで実行される 2 つのコンポーネントが含まれます。

1<!-- c-app -->
2<template>
3  <c-native></c-native>
4  <c-synthetic></c-synthetic>
5</template>

以下はネイティブコンポーネントです。

1<!-- c-native -->
2<template>
3  <div>Your content for the native component</div>
4</template>
1// native.js
2import { LightningElement } from "lwc";
3export default class Native extends LightningElement {
4  static shadowSupportMode = "any";
5}

以下は合成コンポーネントです。

1<!-- c-synthetic -->
2<template>
3  <div>Your content for the synthetic component</div>
4</template>
1// synthetic.js
2import { LightningElement } from "lwc";
3export default class Synthetic extends LightningElement {}

ネイティブ Shadow と合成 Shadow の比較 

ここでは、ネイティブ Shadow と合成 Shadow の違いをいくつか説明します。

要素とスロット 

ネイティブ Shadow DOM では、要素がコンポーネントの Light DOM でレンダリングされ、子コンポーネントの Shadow DOM のスロットに割り当てられます。一方、合成 Shadow DOM では、子コンポーネントに渡される要素はスロットに割り当てられていなければ LWC でレンダリングされません。

コンポーネント c-parentc-child が含まれている場合、c-child にはスロットに割り当てられない span があります。ネイティブ Shadow では、この span が DOM でレンダリングされます。一方、合成 Shadow では、span が DOM に存在しません。

1<!-- c-parent -->
2<template>
3  <c-child>
4    <span>This is some text in a span tag</span>
5  </c-child>
6</template>
1<!-- c-child -->
2<template>
3  <p>child</p>
4</template>

要素が割り当てられているスロットを判断するには、assignedSlot API をコールします。以前は、合成 Shadow ポリフィルが読み込まれると、ネイティブ Shadow コンポーネントのスロットに割り当てられた要素の場合に assignedSlotnull が返されていましたが、ネイティブ Shadow コンポーネントのスロット化された要素の場合に API で <slot> 要素が返されるようになりました。

指定された座標のすべての要素の配列を返すには、elementsFromPoint API を使用します (DOM に表示されない合成 Shadow DOM 要素の場合も含む)。次の例では、<c-inner> は非表示 (幅と高さが 0) ですが、それに含まれる <div> は表示されます。

1<c-outer>
2  #shadow-root
3  <c-inner>
4    #shadow-root
5    <div></div>
6  </c-inner>
7</c-outer>

合成 Shadow では、outer.shadowRoot.elementsFromPoint() をコールすると [<div>, <c-outer>, <html>] が返されます。ネイティブ Shadow では、[<c-inner>, <c-outer>, <html>] が返されます。

スタイル設定 

合成 Shadow では、アプリケーションはグローバルスタイルシートを使用して DOM 全体にスタイルを適用します。ネイティブ Shadow では、コンポーネントのスタイルがコンポーネントバンドルに追加されるため、上記の作業を実行できません。

ただし、合成 Shadow では、ドキュメントの最上位レベルの共有スタイルシートでページのすべてのコンポーネントのスタイルが設定される可能性があります。最上位のスタイルが子コンポーネントに適用されないようにするには、@import を使用して、必要とするコンポーネントに共有スタイルシートを組み込みます

同じスタイルシートを複数のコンポーネントにインポートしても、CSS の重複排除は LWC で処理されるため、パフォーマンスへの影響はありません。

Note

CSS モジュールのインポートは、ほとんどの共有 CSS ライブラリで機能しますが、シャドウ境界をトラバースするセレクタが CSS に含まれている場合は機能しません。この CSS を含む親コンポーネントと子コンポーネントがあるとしましょう。

1.parent .child {
2  color: red;
3}

.parent セレクタと .child セレクタが別々のコンポーネントにある場合、この CSS は機能しません。その場合は、CSS カスタムプロパティまたは別の手法を用い、条件に応じて、子をその親に基づいて表示します。

たとえば、color プロパティを子コンポーネントに渡すことができます。

1class Parent extends LightningElement {
2  myColor = "red";
3}
4
5class Child extends LightningElement {
6  @api color;
7  get myStyle() {
8    return `color: ${this.color}`;
9  }
10}

子コンポーネントで表示される色は、親コンポーネントによって決定されます。

1<!-- c-parent -->
2<template>
3  <c-child color={myColor}></c-child>
4</template>
5
6<!-- c-child -->
7<template>
8  <h1 style={myStyle}>My color is determined by my parent</h1>
9</template>

コンポーネントでカプセル化が不要の場合は、Light DOM を代用するようにコンポーネントを移行することを検討してください。

アクセシビリティ 

合成 Shadow では、親コンポーネントと子コンポーネントの両方で属性を動的に設定してシャドウ境界を越える参照を作成できます。ネイティブ Shadow では、要素 ID が特定のコンポーネントに範囲設定されるため、上記の作業を実行できません。

次のオプションをお勧めします。

  • 要素で別の要素の ID を参照する場合、同じコンポーネントに両方とも配置する。
  • aria-labelledby を使用する場合、aria-label を使用して各要素で文字列を複製する。

カプセル化が不要なコンポーネントを操作する場合は、Light DOM を使用することを検討してください。たとえば、同じ Shadow DOM の親コンポーネント内に兄弟として 2 つの Light DOM コンポーネントがあり、ID が兄弟間で共有されている場合などです。

ライフサイクルフック 

合成 Shadow では、スロット化された要素はスロットに割り当てられている場合にのみレンダリングされます。スロット化された要素がスロットに割り当てられていない場合、ライフサイクルフックが呼び出されることはありません。

また、合成 Shadow のライフサイクルフックは、割り当てられた後の表示順に呼び出されます。一方、ネイティブ Shadow では、テンプレートの表示順に呼び出されます。

非構成イベント 

合成 Shadow では、リスナーはサブツリーの非 LWC コンポーネントからのイベントの場合にルート LWC ノードの外部の非構成イベントを処理できます。ただし、Shadow DOM ではこの動作はサポートされていません。

Lightning 基本コンポーネント 

現在、基本コンポーネントは混合 Shadow モードではサポートされていません。Salesforce は、Web Components の標準に準拠したネイティブ Shadow DOM の基本コンポーネントを準備しています。混合 Shadow モードとネイティブ Shadow DOM を将来サポートするための取り組みがなされているため、基本コンポーネントの内部構造は変化し続けています。現在、基本コンポーネントは、ネイティブ Shadow コンポーネント内に配置されたときにスタイル設定が正しく表示されない場合があります。ネイティブ Shadow が合成 Shadow を使用せずにすべての子コンポーネントを表示します。

その他の考慮事項 

Salesforce のベンチマークでは、同じシナリオでネイティブ Shadow コンポーネントが合成 Shadow コンポーネントよりも最大 50% 高速であることが示されています。合成 Shadow ポリフィルを削除すると、LWC の全体的な JavaScript のサイズも半分に削減されます。混合 Shadow モードでネイティブ Shadow への完全移行に近づけて、そこから LWC からポリフィルを完全に削除できます。

合成 Shadow では、::part など、いくつかの Shadow DOM 機能がサポートされていません。

ネイティブ Shadow と合成 Shadow の次の違いも考慮してください。

合成 Shadowネイティブ Shadow
スロット化可能な対象コンポーネントで定義された順序でスロットが作成されます。スロット化を呼び出すコンポーネントで定義された順序でスロットが作成されます。
innerText プロパティでシャドウコンテンツは非表示になりません。innerText プロパティでシャドウコンテンツは非表示になります。
this.template.firstChild でテンプレートの最初の要素が返されます。一部のブラウザでは、ネイティブ Shadow の場合に this.template.firstChild<style> 要素が返されます。

The Japanese Summer '24 guide is now live

日本語の Summer '24 ガイドが公開されました! 「Component Reference (コンポーネントリファレンス)」は、以前と同様にコンポーネントライブラリにあります。