Edited at

Web Componentsに触れる


0. WebComponentsって?


Web Components は、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです。


引用元:https://developer.mozilla.org/ja/docs/Web/Web_Components

一連のテクノロジーの主な中身

- HTML Templates

- HTML Imports

- Shadow DOM

- Custom Elements

これらを一つづつ、さらっと表面をなぞっていく

※見やすくするためにhtmlでは、html要素とhead要素を使っていません


1. HTML Templates

再利用可能なHTML要素


index.html

<body>

<template>
<h1>Hello HTML Templates</h1>
<img src="">
</template>
<script src="main.js"></script>
</body>

この時点では、なにも表示されない

JSでtemplateをクローンしてbodyにappendChildする


main.js

const TEMPLATE = document.querySelector('template');

const CLONE = document.importNode(TEMPLATE.content, true);
document.body.appendChild(CLONE);



template自体を表示させるんじゃなくて、いったんcloneして生成するのがキモ。

img src(unknow)になってるのでsrcをつけてみる


main.js

const TEMPLATE = document.querySelector('template');

TEMPLATE.content.querySelector('img').src = 'https://dummyimage.com/99';
const CLONE = document.importNode(TEMPLATE.content, true);
document.body.appendChild(CLONE);

template要素.contentに対してquerySelectorする


2. HTML Imports

外部HTMLを読み込む


foo.html

<h1>Hello HTML Imports</h1>



index.html

<body>

<link rel="import" href="foo.html">
<script src="main.js"></script>
</body>


main.js

const IMPORT = document.querySelector('link[rel="import"]').import;

const HEADER = IMPORT.querySelector('h1');
document.body.appendChild(HEADER.cloneNode(true));

インポートしたfoo.htmlからh1を取り出してbodyにappendChildしている。


HTML TemplateをHTML Importsする


template.html*

<template>

<h1>Hello HTML Templates from template.html</h1>
<img src="">
</template>


index.html

<body>

<link rel="import" href="template.html">
<script src="main.js"></script>
</body>


main.js

//HTML imports

const IMPORT = document.querySelector('link[rel="import"]').import;

//HTML template
const TEMPLATE = IMPORT.querySelector('template');
TEMPLATE.content.querySelector('img').src = 'https://dummyimage.com/99';

//クローンを作成してbodyに生やす
const CLONE = document.importNode(TEMPLATE.content, true);
document.body.appendChild(CLONE);


※import元のhtmlの名前空間(idやclass)にはスコープが生成されるが、styleはそうでないので注意


template.html

<template>

<style>
h1 {
color: red;
}
</style>
<h1>Hello from template.html</h1>
</template>


index.html

<body>

<h1>Hello from index.html</h1>
<link rel="import" href="template.html">
<script src="main.js"></script>
</body>


main.js

const IMPORT = document.querySelector('link[rel="import"]').import;

const TEMPLATE = IMPORT.querySelector('template');
const CLONE = document.importNode(TEMPLATE.content, true);
document.body.appendChild(CLONE);

index.htmlにも、template.htmlのスタイルが適用される。

styleの名前空間を汚したくない!

→Shadow DOMで解決


3. Shadow DOM

カプセル化してくれるやつ


index.html

<body>

<h1>Hello</h1>
<script src="main.js"></script>
</body>


main.js

const SHADOW = document.querySelector('h1').createShadowRoot();

SHADOW.textContent = 'Hello Shadow DOM';



一見何の変哲もないが、

ShadowDOMは表示だけを担当し、JSから参照する場合は、元々の値が返される。

使いみちは?

→ShadowDOMはstyleを含めたスコープを持つ(カプセル化と言う)

ということで、


HTML TemplateをHTML ImportsしてShadowDOMで出力する


template.html

<template>

<style>
h1 {
color: red;
}
</style>
<h1>Hello from template.html</h1>
</template>


index.html

<body>

<h1>index.html</h1>
<div id='for_shadow'></div>
<link rel="import" href="template.html">
<script src="main.js"></script>
</body>  


main.js

//HTML imports

const IMPORT = document.querySelector('link[rel="import"]').import;

//HTML template
const TEMPLATE = IMPORT.querySelector('template');
const CLONE = document.importNode(TEMPLATE.content, true);

//Shadow DOM
const SHADOW = document.querySelector('#for_shadow').createShadowRoot();
SHADOW.appendChild(CLONE);

//DOMアクセス
console.log(document.querySelector('#for_shadow').shadowRoot);
console.log(document.querySelector('#for_shadow::shadow h1'));




無事、styleの名前空間を分離することができた


4. Custom Elements

タグを自作

いきなり、全部の合わせ技


template.html

<template>

<h1>Hello from template.html as x-card</h1>
</template>


index.html

<body>

<link rel="import" href="template.html">
<script src="main.js"></script>
</body>


main.js

//Custom Elementsを定義

const card = class extends HTMLElement {
connectedCallback() { //お約束
//html import
const IMPORT = document.querySelector('link[rel="import"]').import;

//html template
const TEMPLATE = IMPORT.querySelector('template');
const CLONE = document.importNode(TEMPLATE.content, true);

//Shadow DOM
this.createShadowRoot().appendChild(CLONE);
}
};

//x-cardタグにcardクラスを登録
window.customElements.define('x-card', card);

//bodyに生成
document.body.appendChild(new card());



5. ブラウザの対応状況

2018/10/14時点ではブラウザの対応状況がマチマチなので注意

HTML ImportsとShadow DOMにいたっては草案(Working Draft: WD)段階

https://caniuse.com/#feat=template



https://caniuse.com/#feat=imports



https://caniuse.com/#feat=shadowdomv1



https://caniuse.com/#feat=custom-elementsv1