開発者として、私たちはコンポーネント化、標準化、コードの再利用の重要性を知っています。フロントエンドもフロントエンドのコンポーネント化の試みを止めたことはなく、Vue や React などのフロントエンドフレームワークから、webpack のような全サイトパッケージツールまで、さまざまなコンポーネント化技術が生まれました。
しかし、フロントエンドは常にこのようなモジュール化の標準とブラウザレベルのネイティブコンポーネント化のソリューションが不足しています。
Web Components は WHATWG と W3C が試みている Web コンポーネント化のソリューションで、コンポーネント化されたフロントエンド開発にブラウザレベルのサポートを提供します。これは、Shadow DOM、Custom Elements、HTML Import、HTML Template の 4 つの主要技術で構成されています。
Polymer プロジェクトは Google の Web Components メカニズムに基づいたフレームワークで、シンプルな Polyfill と使いやすさのラッピングを目的としています。データバインディング、テンプレート宣言、イベントシステムなどが含まれています。Google は昨年、すでにこれを YouTube に適用しました。
Polymer 3.0 は 20 日前にリリースされ、ちょうど Bilibili のプレーヤーがすべての UI コンポーネントを再構築する必要があったため、こうした調査を行いました。以下のすべてのデモはpolymer-demosにホストされています。これらの小さなデモは簡単な体験としてのみ提供されており、Polymer の完全な機能を理解したい場合は公式文書を読むことをお勧めします。
ブラウザサポート#
現在、Web Components を使用する最大の障害はブラウザのサポートが低く、Polyfills のサイズが比較的大きい(90+kb)ことです。
現在、新しい Chrome、Opera、Safari のみが完全なネイティブサポートを提供できます。具体的なサポート状況はcaniuse.comを参照してください。 Polyfillsを使用すると、Edge、IE11+、Firefox、Safari9 + をサポートできます。
Polyfills には 3 つの主要なファイルがあります:
webcomponents-bundle.js
: すべての polyfills を含んでいますwebcomponents-loader.js
: ブラウザのサポート状況を検出し、対応する polyfills を読み込むことができます。ネイティブサポートのあるブラウザでは不必要な浪費を減らすことができますcustom-elements-es5-adapter.js
: Custom Elements を登録する際に ES6 構文を使用する必要があるため、ブラウザが ES6 をサポートしていない場合は追加の処理が必要です。このファイルを参照すれば大丈夫です。
全体的に、最も多くのブラウザに対応するためのベストプラクティスは次のようになります:
<scirpt src="webcompoments-loader.js"></scirpt>
<scirpt src="custom-elements-es5-adapter.js"></scirpt>
<script src="index.js"></script>
ここで、webcompoments-loader.js
は単独で参照する必要があります。custom-elements-es5-adapter.js
はpolymer
とあなたのコードを Webpack で一緒にまとめることができますが、custom-elements-es5-adapter.js
を追加のコンパイルしないように注意してください。他のコードは babel で ES5 にコンパイルします。完全な実践についてはpolymer-demosを参照してください。
カスタム要素#
以下では、最もシンプルなカスタム要素を定義し、PolymerElement
からクラスを継承し、window.customElements.define
に渡します。
効果
{% raw %}
{% endraw %}
HTML コード
<demo-custom-elements></demo-custom-elements>
JS コード
import { PolymerElement } from '@polymer/polymer';
class DemoCustomElements extends PolymerElement {
constructor() {
super();
this.textContent = `私はカスタム要素です。`;
}
}
window.customElements.define('demo-custom-elements', DemoCustomElements);
シャドウ DOM#
シャドウ DOM は隠された独立した DOM であり、その HTML、CSS、動作は通常の DOM ツリーから分離されています。これにより、異なる機能が混在せず、内外の CSS も互いに影響を与えません。
シャドウ DOM は新しいものではなく、ブラウザはそれを使用して要素の内部構造をカプセル化してきました。<video>
要素を例にとると、あなたが見ることができるのは<video>
タグだけですが、実際にはそのシャドウ DOM 内に一連のボタンやコントローラーが含まれています。
以下の例では、シャドウ DOM 内の p タグが CSS プロパティcolor
を定義しており、それは外部に漏れません。
効果
{% raw %}
私はdemo-shadow-domの外にいます。カプセル化のため、demo-shadow-domのスタイルは私に漏れません。
{% endraw %}HTML コード
<style>
html {
--my-background: #eee;
}
</style>
<demo-shadow-dom></demo-shadow-dom>
<p>私はdemo-shadow-domの外にいます。カプセル化のため、demo-shadow-domのスタイルは私に漏れません。</p>
JS コード
import { PolymerElement, html } from '@polymer/polymer';
export class DemoShadowDom extends PolymerElement {
static get template () {
return html`
<style>
p {
color: #F5712C;
background-color: var(--my-background);
}
</style>
<p>私はDOM要素です。</p>
<p>これは私のシャドウDOMです!</p>
`;
}
}
window.customElements.define('demo-shadow-dom', DemoShadowDom);
HTML テンプレート#
<template>
と<slot>
を使用してシャドウ DOM を構成します。
効果
{% raw %}
私はカスタムスロットです。
{% endraw %}
HTML コード
<demo-html-template>
<p>私はカスタムスロットです。</p>
</demo-html-template>
JS コード
import { PolymerElement, html } from '@polymer/polymer';
import '@polymer/polymer/lib/elements/dom-repeat.js'
import { DemoShadowDom } from './demo-shadow-dom';
class DemoHTMLTemplate extends DemoShadowDom {
constructor() {
super();
this.employees = [
{
name: 'ブログ',
link: 'https://diygod.me'
},
{
name: 'GitHub',
link: 'https://github.com/DIYgod'
},
];
}
static get template () {
return html`
<strong>テンプレート:</strong>
<template is="dom-repeat" items="{{employees}}">
<p><a href="{{item.link}}">{{item.name}}</a></p>
</template>
<strong>スロット:</strong>
<slot></slot>
<strong>スーパーテンプレート:</strong>
${super.template}
`;
}
}
window.customElements.define('demo-html-template', DemoHTMLTemplate);
データバインディング#
双方向のデータバインディングをサポートしており、以下の入力ボックスを編集したり、コンソールでプロパティdocument.querySelector('demo-data').owner1 = 'DIYgay'
を直接変更したりできます。プロパティの変更は DOM に即座に反映されます。
効果
{% raw %}
{% endraw %}
HTML コード
<demo-data owner1="DIYgod1"></demo-data>
JS コード
import { PolymerElement, html } from '@polymer/polymer';
import '@polymer/iron-input';
class DemoData extends PolymerElement {
constructor() {
super();
this.owner3 = 'DIYgod3';
}
static get properties () {
return {
owner1: {
type: String,
value: 'DIYgod',
},
owner2: {
type: String,
value: 'DIYgod2',
}
};
}
static get template () {
return html`
<p>これは<b>[[owner1]]</b>の要素です。</p>
<p>これは<b>[[owner2]]</b>の要素です。</p>
<p>これは<b>{{owner3}}</b>の要素です。</p>
<iron-input bind-value="{{owner1}}">
<input is="iron-input" placeholder="あなたの名前をここに...">
</iron-input>
`;
}
}
window.customElements.define('demo-data', DemoData);
カスタムイベント#
以下では、私たちのカスタム要素にdiygod
という名前のイベントを定義し、イベントコールバックのバインディング方法は通常のイベントと同じです。
効果
{% raw %}
{% endraw %}
HTML コード
<demo-events></demo-events>
<script>
document.querySelector('demo-events').addEventListener('diygod', function (e) {
alert(e.detail.msg);
});
</script>
JS コード
import { PolymerElement, html } from '@polymer/polymer';
export class DemoEvents extends PolymerElement {
static get template () {
return html`
<button on-click="handleClick">私を蹴って</button>
`;
}
handleClick(e) {
this.dispatchEvent(new CustomEvent('diygod', {
detail: {
msg: 'diygodイベントが発火しました'
}
}));
}
}
window.customElements.define('demo-events', DemoEvents);