Web Componentsに触れる

# 0. WebComponentsって?

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

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

一連のテクノロジーの主な中身
- [HTML Templates](https://www.html5rocks.com/ja/tutorials/webcomponents/template/)
- [HTML Imports](https://www.html5rocks.com/ja/tutorials/webcomponents/imports/)
- [Shadow DOM](https://www.html5rocks.com/ja/tutorials/webcomponents/shadowdom/)
- [Custom Elements](https://www.html5rocks.com/ja/tutorials/webcomponents/customelements/)

これらを一つづつ、さらっと表面をなぞっていく
※見やすくするためにhtmlでは、html要素とhead要素を使っていません

# 1. HTML Templates

再利用可能なHTML要素

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

この時点では、なにも表示されない
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539508287816_image.png)

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

```javascript:main.js
const TEMPLATE = document.querySelector('template');
const CLONE = document.importNode(TEMPLATE.content, true);
document.body.appendChild(CLONE);
```

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539507035410_image.png)
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1512976292020_image.png)

template自体を表示させるんじゃなくて、いったんcloneして生成するのがキモ。
img src(unknow)になってるのでsrcをつけてみる

```javascript: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する
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539508248540_image.png)

# 2. HTML Imports


外部HTMLを読み込む


```html:foo.html
<h1>Hello HTML Imports</h1>
```

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

```javascript: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している。

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539508836002_image.png)

## HTML TemplateをHTML Importsする

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

```html:index.html
<body>
  <link rel="import" href="template.html">
  <script src="main.js"></script>
</body>
```
    
```javascript: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);
```

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539509071011_image.png)


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

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

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

```javascript: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);
```

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539509894482_image.png)


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

styleの名前空間を汚したくない！
→Shadow DOMで解決


# 3. Shadow DOM


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

```html:index.html
<body>
  <h1>Hello</h1>
  <script src="main.js"></script>
</body>
```

```javascript:main.js
const SHADOW = document.querySelector('h1').createShadowRoot();
SHADOW.textContent = 'Hello Shadow DOM';
```

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539510760491_image.png)
一見何の変哲もないが、
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539510919972_image.png)

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539511018834_image.png)

ShadowDOMは表示だけを担当し、JSから参照する場合は、元々の値が返される。
使いみちは？
→ShadowDOMはstyleを含めたスコープを持つ(カプセル化と言う)
ということで、

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

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

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

```javascript: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'));
```

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539512841203_image.png)
無事、styleの名前空間を分離することができた


# 4. Custom Elements


タグを自作

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

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

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

```javascript: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());
```

![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539513878920_image.png)

# 5. ブラウザの対応状況


2018/10/14時点ではブラウザの対応状況がマチマチなので注意
HTML ImportsとShadow DOMにいたっては草案(Working Draft: WD)段階

https://caniuse.com/#feat=template
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539514095739_image.png)
https://caniuse.com/#feat=imports
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539514126047_image.png)
https://caniuse.com/#feat=shadowdomv1
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539514237257_image.png)
https://caniuse.com/#feat=custom-elementsv1
![](https://d2mxuefqeaa7sj.cloudfront.net/s_2DAEA40B10D7CD0BC867F66D74B2266921463141871BDD2BA2941A20F0A64C24_1539514362608_image.png)