Kristján Oddsson氏は、Web Components SFミートアップで、GitHubにおけるWebコンポーネントの使用状況と、フロントエンドコンポーネントをより読みやすく、高パフォーマンスで、アクセス性のよいものにするためにGitHubが見出したパターンについて詳説した。
最初にOddsson氏は、GitHubにおいて、単純な動作をバニラJavaScriptで実装している方法について説明した。
on('click', 'js-hello-world-button', function (event) {
const button = event.currentTarget;
const container = button.closest('js-hello-world');
const input = container.querySelector('js-hello-world-input');
const output = container.querySelector('js-hello-world-output');
output.textContent = `Hello, ${input.value}`
}
これがページ上では、次のようなHTMLになる。
<div class="js-hello-world">
<input class="js-hello-world-input" type="text">
<button class="js-hello-world-button">
Greet
</button>
<span class="js-hello-world-output"></span>
</div>
上記のコードでは、標準的なWeb APIを越えた抽象化は使用されていないので、要求された動作(ボタンがクリックされたらウェルカムメッセージを表示する)を記述する上で最も効率的な方法のひとつであると言えるだろう。しかしOddsson氏は、このアプローチにはいくつかの問題があると指摘する — ネーミングスキームが必要であること、DOMクエリが明示的であること、クラスにスコープが設定されていないため、意図しないオーバーロードが行われる可能性のあること、などだ。この結果として、開発者側でさらに作業が必要になる。
そのためGitHubのチームでは、上記のような動作を、漸進的に拡張されたカスタムエレメントに転換する作業を進めている、とOddsson氏は説明した。GitHubのカスタムエレメントは、組み込みエレメントの動作に可能な限り近いものになっている。さらにGitHubのカスタムエレメントでは、シャドウDOMを使用していない。
次にOddsson氏は、ユーザのタイプ入力中にリポジトリ名のアベイラビリティを自動的にチェックする<auto-check>
カスタムエレメントを使って、Webコンポーネント記述に関する一般的パターンを説明した。
最初に、カスタムエレメントでは、エレメントのライフサイクルの各ステージに対応する4つのメソッド(connectedCallback
、disconnectedCallback
、adoptedCallback
、attributeChangedCallback
)を含むインターフェースを定義する。変更を通知する属性を指定するために、observedAttributes<
メソッドも使用される。さらに、通常のカスタムエレメントでは、コンポーネントユーザに対して公開されるインターフェースを構成する属性の取得と設定を行うメソッドも定義する。
レジストリに登録されていなければ、その追加も行う必要がある(c0>window.customElements.defineメソッド)。動作に関連するDOMエレメントのクエリも必要だ — 例えばthis.querySelector('input')
では、ユーザがリポジトリ名を入力するinput子エレメントを取得する。さらに、GitHubではTypeScriptを使用しているので、関連する型を生成する必要がある。
declare global {
interface Window {
AutoCheckElement: typeof AutoCheckElement
}
interface HTMLElementTagNameMap {
'auto-check': AutoCheckElement
}
}
Webコンポーネントの記述に関わるこのような定型コードによって、バニラJavaScriprtで動作を実装する場合にあった先程の問題のうち2つを解決することができた、とOddsson氏は説明した。すなわち、ネーミングスキームは不要になり、スコープが簡略化されている。
次にOddsson氏は、残った問題の解決手段として、テクノロジのコレクションであるCatalystを紹介した。Catalystはカスタムエレメント、TypeScript、ヘルパデコレータに関与する。Catalystを使うことで、最初の例(ウェルカムメッセージの表示)は以下のようなHTMLで実装されるようになる。
<hello-world>
<input data-target="hello-world.input" type="text">
<button data-action="click:hello-world#greet">
Greet
</button>
<span data-target="hello-world.output"></span>
<hello-world>
コードは次のとおりだ。
import {controller, target} from "@github/catalyst"
@controller
class HelloWorldElement extends HTMLElement {
@target input : HTMLInputElement
@target output: HTMLElement
greet() {
this.output.textContent = `Hello, ${this.input.value}`
}
}
Catalystの結果として、定形コードの量がオリジナルバージョンよりも著しく削減されている。Oddsson氏も言うように、このテクニックはstimulus
フレームワークから着想を得たものだ。Stencil Webコンポーネントコンパイラも、定形コードの制限と最適化されたコードの生成のためにデコレータを使用している。LitElementやMicrosoftのFastElementも、デコレータを使ってアノテーションやコードの単純化を行っている。
GitHubでは、DOM上の抽象化(仮想DOMやレンダリングライブラリ)は使用せず、DOMを直接操作しているので、コンポーネントサイズが小さく、パフォーマンスに優れている。auto-check
コンポーネントは1.3KBのサイズで、依存関係も400バイトのひとつのみだ。GitHubのカスタムエレメントはすべて3KB以内に収まっている。2KBを越える数少ないコンポーネントのひとつは2.5KBのauto-complete入力エレメントである。周辺コンテンツ(アイコンやCSS)と抽象化(DOM diffライブラリなど)をコア機能にパッケージ化するという、他のカスタムエレメントのディストリビューション戦略とは対照的だ。この方法では、13KBのカスタムボタンエレメントや、20KBに達するカスタム入力エレメントを扱うことになる。
GitHubの17のカスタムエレメントはオープンソースで、オンラインで公開されている。より多くのコード例や技術的詳細、参加者とのQ&Aを含む講演の全体も、オンラインで公開されている。