今日はHTMLをコンポーネント化するWeb Componentsという新しいHTMLの仕様と、
その機能を補完するPolymerというライブラリについてお話させていただきます。
Web Components
Web Componentsについては、2013年のHTML5 ConferenceでGoogleの夷藤さんがセッションされていました。夷藤さんはChromeチームで、Web Componentの周りの実装をされていたり、Shadow DOMの仕様の編集をされています。
セッション中で夷藤さんは、Web Componentsの役割を以下のように述べています。
「ブラウザが標準で用意しているタグと同じような本質的なパワーを持ったエレメントを開発者が独自に作れるようにする。」
ブラウザが標準で解析出来るタグは簡潔ですが、ひとたび凝ったUIになったりすると、たちまちHTMLとCSS、およびそれらをコントロールするJavaScriptが複雑化してしまうのは周知の事実でしょう。HTMLとCSSには他のプログラミング言語に備わっているようなスコープが存在しないため、複雑なUIを構成するためのCSSは命名を工夫し、他のパーツとの衝突を避けなければなりません。また、JavaScriptも全てのDOMにアクセス可能な状態から指定のUIのみを操作するというリスクを抱えたまま実行されています。また、スコープがない故に再利用の難しさも生み出しています。
これらの問題を解決してくれるのがWeb Componentです。Web Componentを学習する上で、いくつか覚えておきたいポイントがあります(以下の3つに加えてデコレータという概念がありますが、今回は割愛します)。
Shadow DOM
今までのDOMツリーは前述の通りスコープの概念が存在していませんでした。例えば、div {font-size:
20px;}
というCSSを新たに定義すればそのHTMLに存在する全ての<div>
が影響されます。正確に言えばiframe
と<style scoped>
だけはスコープ形成します。iframe
によってロードされたHTMLに対しては、ロード元からアクセスが不可能であり、iframe
内の要素もインクルード元に対して干渉することもありません。しかしコンポーネント化を考えると、iframe
は拡張性に欠けています。
そこでShadow DOMの登場です。Shadow
DOMの仕様がHTMLに追加されることによってHTMLはスコープを利用することが可能になります。通常のDOM
Treeに属するElementは、DOM Treeの他にShadow Treeを持つことが可能になりました。形成されたShadow
Rootを除いて、CSSとJavaScriptでShadowTreeにアクセスすることは出来ないようになっています。
Custom Elements
Custom Elementsの概念が生まれたことで、開発者側で自由に要素を定義することが可能になりました。ブラウザが解釈出来ないタグ
はHTMLUnknownElement
インターフェースを継承しますが、後述のdocument.registerElement()
を使ってきちんと(?)定義された要素は、HTMLElement
インターフェースを継承します。
HTML Import
別のHTMLドキュメントをロードし、再利用するための仕組みです。ここでは作ったカスタムエレメントをインポートするために使っています。
これらのうちCustom Elementsと前述のShadow DOMを合わせてコンポーネントを作り、つくったコンポーネントをHTML Importをして利用するという流れになります。
カスタムエレメントの作成
それでは実際にカスタムエレメントを作成してみましょう。こちらのコードは、Google Chrome Canary 36.0.1924.0で動作確認をしています。
x-element
タグの作成
まず、何も機能を持たないXElementというカスタムエレメントを作ります。HTMLElementを継承するオブジェクトを生成し、それをdocument.registerElement()
に渡しています。第一引数にはタグ名とし
てx-element
を、第二引数にはx-element
の性質を指定するパラメータオブジェクトを渡します。カスタムエレメント名には、x-element
やcustom-tag
のように、 ハイフンを含んでいる
必要があります。
x-element
の他にyelement
というハイフン無しのカスタムエレメントを定義しようとしていますが、コンソールで Uncaught
InvalidCharacterError: Failed to execute ‘registerElement’ on
‘Document’: Registration failed for type ‘yelement’. The type name is
invalid. とエラーが出力されているのが確認出来ます。
パラメータのprototype
に対してHTMLElementのプロトタイプを渡すことで
x-element
はそれを継承します。その他のパラメータに関してはw3cのCustom
Elementsの頁を参照してください。さっそくXElementをHTMLに追加してみましょう。
追加した後にHTMLを見てみると、x-element
というタグが追加されているのが確認出来ます。以前まではdocument.register()
というAPIでしたが、document.registerElement()
に変わったようです。恐らくregister()
という名称が、短く汎用的過ぎたのでしょう。
既存のHTML要素の継承
パラメータにextends
という属性に対しタグ名を指定することで、既存のHTML要素を継承することが可能です。この場合のカスタムエレメントは、指定したタグ名になります。
また、document.registerElement()
でカスタムエレメントを登録する時に、extends
に指定するタグのプロトタイプを指定することにも注意してください。button
を拡張するのであれば、HTMLButtonElement.prototype
をプロトタイプに指定します。
このYElementがHTMLに追加されると、<button is='y-element'>
という形式で生成されます。
カスタムエレメントの各種イベントの捕捉
カスタムエレメントでは、以下の4つのタイミングでイベントが発生し、それぞれにコールバック関数を指定することが可能です。
createdCallback
- カスタムエレメントが生成された時attachedCallback
- カスタムエレメントのインスタンスがHTMLに追加された時detachedCallback
- カスタムエレメントのインスタンスがHTMLから削除された時attributeChangedCallback
- インスタンスの属性が追加・削除・更新された時
先程定義したXElementで、これらのイベントを捕捉してみましょう。
XElementに対して初期値を指定する場合には、createdCallbackの中で設定します。例えばこの場合はHTMLElement
ですが、HTMLInputElement
(input
タグ)を継承して、valueの値を予め設定したい場合であったり、HTMLButtonElement
(buttonタグ)を継承してクリック時のイベントを予め設定したい場合等にはここで行うようにします。また、attributeChangedCallback
では、変更された属性名と、変化前後の値を取得することが可能です。
template
とShadow DOMでカスタムエレメントを作成
今度は<template>
タグを使ってShadow DOMを生成し、カスタムエレメントを更に拡張をしていきます。<template>
タグもHTML5で新たに定義された要素です。テンプレートの明示的な宣言に利用します。template要素の仕様についてはこちらを参照してください。
これをHTMLに記述し、カスタムエレメントのShadow Rootとして使います。<template>
タグは描画されませんが、HTMLの中に実体として残ることを認識しましょう。例えばCSSのセレクタ(:first-child)などには干渉するので、考慮したHTMLとCSSを組む必要があります。
Shadow DOMはChromeのDevToolsから確認できますが、Shadow DOM Visualizerという可視化ツールもあります。
カスタムエレメントを複数追加してみる
先程のXElementを幾つか生成してdocumentに追加してみます。もちろん、HTMLの中に<x-element>
と記述することでも利用可能です。
// XElementを生成する
document.body.appendChild(new XElement());
document.body.appendChild(new XElement());
ここまで実装したサンプルが以下になります。
グローバルな空間で、inputをCSSで
input { color: red; }
p { font-size: 12px; }
といったようにスタイリングをしています。しかし、カスタムエレメント内のinput要素には適用されず、カスタムエレメント内のCSSも外部に干渉することはありません。JavaScriptからdocument.getElementsByTagName()
等でinput要素を探索しても、カスタムエレメント内のinputを取得することは出来ません。
Polymerを使ったカスタムエレメントの作成
非常に便利なWeb Componentsですが、ブラウザの実装が進んでおらず現状で導入するのが難しいです。その問題を解決してくれるのがGoogleが開発しているPolymerです。
Polymerは実装されていないブラウザに対しWeb Componentsの機能を保管するとともに、更に便利に利用するためのフレームワークです。大きくわけて3つのパーツに分類されます。
- Platform - カスタムエレメントのロード等のPolyfill
- Polymer - Polymerのコアライブラリ
- Others - Polymerから提供される様々なコンポーネント(ajax機能やUIパーツ等)で、Polymerコアを継承している
from Polymer/docs
Web Componentsもまだ仕様に動きがありますので、その変化する仕様のギャップなどを吸収する役目も今後担っていくでしょう。
Web Componentsによって変わるWeb
UIパーツやライブラリを、作成する時はもちろん、Web Componentsで導入する時に現状存在しているパーツ達に干渉しないので、安全に利用することが出来ます。現状のフロントエンドの開発形態は、
- デザインする人
- マークアップする人
- JSを書いてインタラクションを付ける人
のように分かれていることが多いと思います。がWeb Componentの登場によって、2と3がより密接になり 機能単位でコンポーネントとして実装するというフローにシフトしていくのではないでしょうか。
また、今までのHTMLとCSSの仕組みでは難しかった再利用も飛躍的に向上するので、Webの実装コスト・メンテナンスコストはきっと軽減されていくでしょう。
おわり
以上、簡単ではありますが、Web Componentsのお話をさせていただきました。HTML/CSSにスコープが作れるのは非常にアツいですね。近い未来、Web ComponentsがWeb開発のスタンダードになると思っていますので、私もキャッチアップを続けていけたらと思います。
最後まで見て頂いてありがとうございました!