ざっくりWeb Components

今年の2月、Fukuoka Engineers Day 2018というイベントで「Web Componentsの現在地」というタイトルで発表しました。(まとめはこちら

そこから半年、Firefoxでもバージョン63でついにすべての仕様がサポートされるらしいので、あらためてWeb Componentsの仕様のおさらいメモ。

Web Componentsとは

Web ComponentsはWeb標準の技術のみで再利用可能な部品を作るためのWeb APIです。JSフレームワークを導入して実現していた独自のコンポーネントを標準APIのみで実現できるようになります。

(とはいえ、Web ComponentsとJSフレームワークは競合する考え方ではなく、組み合わせて利用可能なものです。Web Componentsが使えるようになるからJSフレームワークは不要になる!という類のものではないです。念のため。)

Web Componentsそのものが仕様を指すわけではなく、

  • HTML templates
  • Custom Elements
  • Shadow DOM

の3つの仕様をまとめてそう呼んでいます。

HTML templates

HTMLのtemplateタグのことです。ページの初期表示時にはレンダリングされず、必要に応じてJavaScriptから操作してDOMに追加します。

<template>
  <h1>HTML templates Demo
</template>

<div id="container"></div>
const template = document.getElementById('demo').content;
const container = document.getElementById('container');

const node = template.cloneNode(true);
container.appendChild(node);

Custom Elements

HTMLに独自のタグを追加するAPIです。customElements.defineを使って定義します。

Shadow DOM

グローバルなDOMツリーから分離された、カスタム要素に閉じたDOMツリーを持つことができます。CSSなど、グローバルに影響を与えずに、カスタム要素内で独自にスタイリングしたり、処理を行ったりできるようになります。

対応ブラウザ

今のところ、上記の仕様をすべて実装している(=既にWeb Componentsが使える)ブラウザはGoogle ChromeSafari(デスクトップ、モバイル)です。Firefoxはバージョン63で対応予定、EdgeはHTML templatesのみ対応、IEは全ての仕様に未対応です。

課題はEdgeとIEですが、webcomponents.jsというPolyfillを利用することでShadow DOM以外の仕様をエミュレートすることができます。(Shadow DOMはパフォーマンスの問題でoffになっています。)

とはいえShadow DOMはすごく魅力的な仕様なので、これが使えないのは惜しいと言わざるを得ないです。(実際の動作はこの後のサンプルを確認して下さい。)

書いてみる

最小のサンプル

とりあえず最小のサンプルです。

class MyElement extends HTMLElement {
    connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'open' });

        const h1 = document.createElement('h1');
        h1.textContent = 'Web Components Test';

        shadowRoot.appendChild(h1);
    }
}
customElements.define('my-element', MyElement);
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="index.js"></script>
</head>
<body>
    <my-element></my-element>
</body>
</html>

上記をそれぞれindex.html, index.jsと言う名前で保存して開いてみてください。(当然ですがWeb Componentsの仕様に対応したブラウザで確認して下さい)

手順としてはHTMLElementを継承したクラスを定義して、connectedCallbackイベントでShadow DOMを有効化(attachShadow({ mode: 'open' }))し、通常のDOM操作同様にappendChildして要素を追加する。定義したクラスをcustomElements.defineに渡してCustom Elementsを定義する、という流れです。

Shadow DOMによるScoped CSS

Shadow DOMによってCSSの影響が分離されていることを確認してみます。JavaScriptはそのままで、index.htmlを以下のように変更します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="index.js"></script>
    <style>
        h1 {
            color: red;
        }
    </style>
</head>
<body>
    <my-element></my-element>
    <h1>DOM Tree</h1>
</body>
</html>

このHTMLを表示すると、以下のように表示されます。

f:id:ponday:20180915223245p:plain

通常のh1要素は文字色が変更されていますが、カスタム要素のh1要素には効いていないことが分かります。

これは逆も同様です。

class MyElement extends HTMLElement {
    connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'open' });

        const styles = document.createElement('style');
        styles.textContent = 
`
h1 {
    color: red;
}
`;
        shadowRoot.appendChild(styles);

        const h1 = document.createElement('h1');
        h1.textContent = 'Web Components';
        shadowRoot.appendChild(h1);
    }
}
customElements.define('my-element', MyElement);
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="index.js"></script>
</head>
<body>
    <h1>DOM Tree</h1>
    <my-element></my-element>
</body>
</html>

このように変更して結果を確認すると以下のようになります。

f:id:ponday:20180915223929p:plain

見ての通り、h1タグを指定してスタイルを適用しても、カスタム要素の外には影響がありません。

ChromeのDeveloper Toolで確認すると以下のようにレンダリングされています。

f:id:ponday:20180915224859p:plain

このように、styleタグがshadow-rootの中にレンダリングされており、影響範囲がカプセル化できていることが分かります。

まとめ

  • Web ComponentsはWeb標準APIを利用して再利用可能な部品を作るための仕組み
  • Web Components自体がAPIを指すのではなく、いくつかの仕様の総称
  • そろそろWeb ComponentsがFirefoxでも使えるように!
  • Shadow DOMによってWeb標準の要素によってScoped CSSが使える未来も近い
  • Edgeは実装はよして
  • IEはサポート打ち切りはよして

Web ComponentsとJSフレームワークについては別の記事で書ければ。では。