みなさんこんにちは。アメーバ事業本部の泉水(@1000ch)です。

今日は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-elementcustom-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ですが、HTMLInputElementinputタグ)を継承して、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コアを継承している

Diagram

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開発のスタンダードになると思っていますので、私もキャッチアップを続けていけたらと思います。

最後まで見て頂いてありがとうございました!