VanillaJSで始めるWebComponents 

WebComponentとはなんなのかを少し動かしながら確認したいと思いました。

# 先ずは簡単な説明を読む

https://liginc.co.jp/web/html-css/html/58267
http://postd.cc/the-state-of-web-components/
https://www.webcomponents.org/introduction
https://developer.mozilla.org/en-US/docs/Web/Web_Components
# いつ誰が提唱したのか

2011年にAlex Russellが発表した概念です。
https://fronteers.nl/congres/2011/sessions/web-components-and-model-driven-views-alex-russell

が、まだベンダー同士の合意には至っておらずChromeに実装されているのみです。コアな部分なので合意は難しいのでしょうか。。。
現在の状況 http://caniuse.com/#search=component

またこの記事にあるコードのAPIは仕様策定段階ということをご理解ください。今後仕様は変更される可能性があります。

# どんな仕様なのか

## Custom Element

カスタム要素はそれだけで完全に独自のDOM要素を作成できます。
またその要素固有の振る舞いやスタイリングも定義できます。

イメージは以下のような感じのCustomの構築を目指します。

```html
<hello-world></hello-world>
```

Custom Elementには以下のようなAPIが存在します。

- constructor()
  - Element作成時と更新時に呼ばれる
- connectedCallback()
  - Elementがdocument(shadow domの中も含む)に挿入された時に呼ばれる。
- disconnectedCallback()
  - DocumentからElementが消された時に呼ばれる
- attributeChangedCallback(attributeName, oldValue, newValue, namespace)
  - 監視されているAttributeが更新、削除、追加、置き換えられた時に呼ばれる。
adoptedCallback(oldDocument, newDocument)
  - Elementが新しいDocumentに取り入れられた時に呼ばれる

さてcustom elementを登録するJSはHTMLElementを継承したclassを用いるとのことなので以下のようなclassを用意します。
このclassはdefineというAPIで登録することでDOMから参照できるようになります。
https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define

```js
class HelloWorld extends HTLMElememt{
    constructor(){
        //常に最初に呼ばなければなりません
        super()
        this.textContent = "Hello world!!"
    }
}

customElements.define('hello-world', HelloWorld);
```

CSS はこんな感じで定義できます。

```css
hello-world {
   font-weight: bold;
}
```

デモはこんな感じ
http://codepen.io/knhr__/pen/dNKZep

参考
https://w3c.github.io/webcomponents/spec/custom/
https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements

## Shadow DOM

Shadow DOMはWeb ComponentのDOMとCSSをカプセル化してくれる技術です。
iframeの様にメインのDOMから切り離されたDOM空間を作成します。Shadow DOM自体はWeb Componentとは切り離して使えます。
Chromeのinspector画面見てるとMainのDocumentにもShadow DOMがありますね。こんな感じでCustom Component配下などにShadow DOMのRootが作られその中にDOMを構築します。

さてWeb Componentを本当にいろんなページで使用するためにはCSSのバッティングは避けたいです。
そのためにCSSを注意深く設計しなければなりません。しかしサイトがスケールするには設計だけで回避できる様な簡単な問題では無くなります。
そこでShadow DOMを使用することでサイト全体のCSSを設計しやすくします。

attachShadowを使ってShadow DOMを作成することができます。
https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow

先ほどのHello WorldをShadow DOMにして見ましょう。

```js
class HelloWorld extends HTMLElement{
    constructor(){
        //always first
        super()
        const shadow = this.attachShadow({mode:'open'});
      shadow.innerHTML = 
`
<style>.content { font-weight: bold; }</style>
<div class="content">Hello world</div>
`
    }
}

customElements.define('hello-world', HelloWorld);
```

デモ
http://codepen.io/knhr__/pen/RKJxbx

デモを見ていただければわかりますが、mainのCSSに影響されていることなくレンダリングできていることがわかると思います。
CustomElement内にShadowDOMを作成してカプセル化しました。CSSの感じが少し扱いにくい気がしたんですが、ここら辺は今回特に気にしてないです。

https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM

## HTML imports

HTML importsはWeb Componentをパッケージングする仕組みです。もちろん単独でも作用します。

```html
<link rel="import" href="myfile.html">
```
こんな感じでhtmlをimportできます。

デモ
http://codepen.io/knhr__/pen/xgzpmP?editors=1010

FireFoxはHTML importsは実装する気がないとのことでした。Mozilla的にはJSのモジュール機能で全然代用できるとの考え方ということです。
https://hacks.mozilla.org/2014/12/mozilla-and-web-components/

## HTML Template

TemplateはHTMLがロードされてもレンダリングされません。JavaScriptによってインスタンス化することができます。


```html
<div id="one">
</div>
<div id="two">
</div>

<template id="strong">
  template <strong></strong>
</template> 
```

```js
var t = document.querySelector('#strong'),
st = t.content.querySelectorAll("strong");
st[0].textContent = "Hello";


var one = document.getElementById("one");
var clone = document.importNode(t.content, true);

one.appendChild(clone);

var two = document.getElementById("two")
st[0].textContent = "World";

var clone = document.importNode(t.content, true)
  
one.appendChild(clone);
```

demo
https://codepen.io/knhr__/pen/VPdQwV

テンプレートとなるComponentだけを用意して置いてJSでifやforをさせてレンダリングをよりやりやすくする仕組みです。