[ドキュメント翻訳] Polyfills

前回のWeb componentsとはなにかに引き続き、Web componentsのPolyfillsについての翻訳になります。

Polyfills

一部のブラウザは、Webコンポーネントの標準をサポートするために更新中です。
その間、Polyfillは、欠けているブラウザの機能をできるだけ近くシミュレートします。
Polyfillをロードする前に、必要なブラウザ機能をフィーチャー検出することができます。
つまり、Webコンポーネントの標準を実装するブラウザが増えれば、アプリケーションや要素を実行するpayloadが減少します。

PolyfillはGitHubで入手できます:https://github.com/WebComponents/webcomponentsjs ポリフィルをインストールするには、次のコマンドを実行します。

bower install --save webcomponents/webcomponentsjs

検出機能を使用するには:

(function() {
  if ('registerElement' in document
      && 'import' in document.createElement('link')
      && 'content' in document.createElement('template')) {
    // platform is good!
  } else {
    // polyfill the platform!
    var e = document.createElement('script');
    e.src = '/bower_components/webcomponentsjs/webcomponents-lite.min.js';
    document.body.appendChild(e);
  }
})();

Shadow DOM polyfill

Shadow DOM polyfillは、それをネイティブにサポートしていないブラウザでshadow DOM v0の機能を提供します。詳細は互換性表を参照してください。
Shadow DOM polyfillは非常に強力ですが、かなり邪魔になり、大幅なパフォーマンスオーバーヘッドを追加する可能性があります。
このため、PolymerのようなWebコンポーネントベースのライブラリの多くは、このポリフィルを使用する必要がなくなり、軽量の代替品を提供します。
これらのライブラリは、完全なWeb Componentsポリフィルを読み込む必要はありませんが、代わりにshadow domを削除したポリフィルの「ライト」バージョンを使用します。

webcomponents/webcomponents-lite.min.js

Wrappers

Polyfillはラッパーを使用して実装されています。
ラッパーはネイティブDOMノードをラッパー・ノードにラップします。
ラッパー・ノードは、ネイティブ・ノードと同じように見え、動作します(バグと既知の制限はありません)。例えば:

var div = document.createElement('div');
div.innerHTML = '<b>Hello world</b>';
assert(div.firstChild instanceof HTMLElement);

しかし、divは実際にはブラウザが通常あなたに与える要素のラッパーです。
このラッパーは、ブラウザーで提供されたエレメントと同じインターフェースを持っています。

これにはinnerHTMLのセッターがあります。
これはネイティブのinnerHTMLと同じように動作しますが、作成されたツリーを処理する代わりに、ローカルDOM上で動作します。

このように論理DOMツリーを変更すると、作成されたツリーを再レンダリングする必要が生じる可能性があります。これはすぐには起こりませんが、必要に応じて後で起こります。

ラッパー・ノードにはfirstChildゲッターもあり、このgetterは論理DOMで再び機能します。

instance ofは、グローバルなHTMLElementコンストラクタをカスタムのコンストラクタに置き換えたので、引き続き動作します。

論理DOMをより深く

wrappers.Nodeオブジェクトは、論理的な(ライトだけでなくシャドウでも、合成されていない)DOMを追跡します。
内部的には、parentNode、firstChild、lastChild、nextSibling、およびpreviousSiblingの5つの基本ノードポインタがあります。
DOMツリーが操作されると、これらのポインタは常に論理ツリーを表すように更新されます。
shadow DOMレンダラがビジュアルツリーをレンダリングする必要がある場合、これらの内部ポインタは必要に応じて更新されます。

全てのオブジェクトをラップする

DOMツリーと対話するすべてのDOMオブジェクトをラップすることです。
このpolyfillを完全に透明にするには、多くのAPIをラップする必要があります。
ノードまたはノードに間接的に接触するオブジェクトを受け取ったり返すメソッド、アクセサまたはコンストラクタはすべてラップする必要があります。
あなたが想像しているように、これらの多くがあります。
現時点では、最も一般的なコードを実行しましたが、コードでこれを使用しようとするとすぐに行方不明になります。

Wrap and Unwrap

あなたがラッピングをしていない場合があります。
そのような場合は、ラップを使用してネイティブオブジェクトのラッパーを作成したり、ラップして元のネイティブオブジェクトをラップすることができます。
これらの2つの関数は、ShadowDOMPolyfillオブジェクトで使用できます。例えば:

wrap(document.body)
// or get body of the wrapped document
wrap(document).body

unwrap(div).firstChild instanceof HTMLElement

繰り返しラップする必要がある要素を扱う場合は、ラップされた要素のバージョンを直ちに呼び出される関数式に渡してみてください。

(function(document) {
  // Now a library like jQuery can add
  // listeners to the wrapped document
  $(document).on('click', function(e) {
    console.log('Clicked on', e.target);
  });
})(wrap(document));

Event Retargeting

shadow DOMの重要な側面は、shadow DOMをlight DOMに公開しないようにイベントを再ターゲット化することです。例えば:

var div = document.createElement('div');
div.innerHTML = 'Click me';
var shadow = div.createShadowRoot();
shadow.innerHTML = '<b><content></content></b>';

ユーザーがdivをクリックすると、クリックイベントの実際のターゲットは<b>要素になります。しかし、その要素はlight DOMでは表示されないので、ターゲットはdiv要素自体にリターゲットされます。ただし、<content>、<b>またはshadow rootにevent listenerがある場合、ターゲットはevent listenerに表示される必要があります。

mouseoverやmouseouteventsのrelatedTargetでも同様の問題が発生します。

この種類の動作をサポートするには、ブラウザでイベントをディスパッチするには、ポリフィルによって再実装する必要があります。

既知の制限事項

  • CSSのカプセル化には限界がある
  • Object.prototype.toStringはネイティブオブジェクトと同じ文字列を返しません。
  • live Node Listsはありません。すべてのノードリストは、読み込み時にスナップショットされます。
  • document、window、document.body、document.headなどは設定できず、上書きすることはできません。
    これらの作業を可能な限りシームレスに行うよう努めていますが、問題が生じるケースは間違いありません。
    そのような場合は、ラップアンドアンラップを使用してブロックを解除することができます。
  • クロスウィンドウ/フレームアクセスは実装されていません。
  • CSS:host()ルールは引数セレクタにネストされたカッコを1レベルしか持てません。
    たとえば:host(.zot)と:host(.zot:not(.bar))はどちらも動作しますが、host:.zot:not(.bar:nth-child(2)))は動作しません。

Custom Elements polyfill

カスタムエレメントポリフィルは、カスタムエレメント仕様のv0をサポートします。
v1のポリフィルはwebcomponents/custom-elementsで進行中です。
カスタム要素polyfillは、要素のアップグレードを非同期に処理します。
ポリフィルは、DOMContentsLoaded時間まで要素をアップグレードします。
パフォーマンスの最適化としてこれを行います。
最初のアップグレードパスに続いて、突然変異観測器を使用して新しい要素を発見する。
ポリフィルが起動タスクのすべてを完了したときを知るには、ドキュメントまたはウィンドウ上のWebComponentsReadyイベントを待ち受けます。例えば:

<script>
  // hide body to prevent FOUC
  document.body.style.opacity = 0;
  window.addEventListener('WebComponentsReady', function() {
    // show body now that everything is ready
    document.body.style.opacity = 1;
  });
</script>

カスタム要素仕様はまだ議論中です。
ポリフィルは、仕様に先立って特定の機能を実装します。
特に、要素プロトタイプに実装されている場合に呼び出されるライフサイクルコールバックメソッド

  • カスタム要素が作成されると、createdCallback()が呼び出されます。
  • attachmentCallback()は、カスタム要素がDOMサブツリーに挿入されたときに呼び出されます。
  • detachedCallback()は、カスタム要素がDOMサブツリーから削除されたときに呼び出されます。
  • attributeChangedCallback(attributeName)は、カスタム要素の属性値が変更されたときに呼び出されます。

createdCallbackは要素のインスタンス化と同期して呼び出され、他のコールバックは非同期に呼び出されます。
非同期コールバックは一般にMutationObserverタイミングモデルを使用します。
つまり、レイアウト、ペイント、またはその他のトリガイベントの前に呼び出されるため、開発者はコールバックが変更に反応する前に発生しているコンテンツやその他の悪いことを心配する必要はありません。

HTML Imports Polyfill

インポートされたドキュメントでは、
HTMLのhrefとsrc属性とCSSファイルのurlpropertiesは、メインドキュメントではなく、インポートされたドキュメントの相対位置です。

HTMLインポートポリフィルは、DOMContentLoadedイベントが発生したときにリンクタグの処理を開始します。
読み込みが完了したら、ドキュメントまたはウィンドウでHTMLImportsLoadedイベントを待ち受けます。
例えば:

<script>
window.addEventListener('HTMLImportsLoaded', function(e) {
  // all imports loaded
});
</script>

ポリフィルは、リンクされたスタイルシート、外部スクリプト、およびネストされたHTMLインポートをロードしますが、ロードされたリソースのデータは解析しません。
インポートを解析するには、HTMLインポートとカスタム要素を組み合わせます。 HTMLインポートが最初にロードされる限り、カスタムエレメントポリフィルはそれを検出し、HTMLImportsLoadedイベントが発生するとすべてのインポートを処理します。

The WebComponentsReady event

ネイティブインポートでは、メイン文書の&lt;script&gt;タグがインポートの読み込みをブロックします。
これは、インポートがロードされ、その中の登録された要素が確実にアップグレードされたことを確認するためです。

このネイティブの動作はpolyfillするのが難しいので、HTML imports polyfillは試しません。
代わりに、WebComponentsReadyイベントは、この動作をサポートしています。

<script>
 window.addEventListener('WebComponentsReady', function(e) {
    // imports are loaded and elements have been registered
  });
</script>

ネイティブHTMLインポートでは、document.currentScript.ownerDocumentはインポートドキュメント自体を参照します。
ポリフィルでは、document._currentScript.ownerDocumentを使用します(アンダースコアに注意してください)。