Web Components と vue-custom-elements

ブラウザ対応が着々と進んできて、 Web Components の利用が現実味を帯びてきました。
そんななか、ざっと Web Components のおさらいと `vue-custom-element` に着目してみたいと思います。


## Web Components とは?

- Custom Elements
- Shadow DOM
- HTML imports
- HTML Template

の4つの大きな仕様から構成される Web プラットフォーム API です。
`Custom Elements` で自由にカスタム要素を生成することができ、 `Shadow DOM` でカプセル化を促します。
Web Components の利用を簡潔に行うためのライブラリ [Polymer](https://www.polymer-project.org/) なども存在しています。
これらの詳しい情報に関しては [WEBCOMPONENTS.ORG](https://www.webcomponents.org/) にまとめられています。


### Vue と Web Components の相性

Vue のテンプレート構文自体は HTML を拡張したような構文になっているので、Web Components はかなり親和性の高いものだと思います。
[Custom Elements Everywhere](https://custom-elements-everywhere.com/) でも、 Vue は満点評価になっています :heart_eyes:


### Vue with Web Components

せっかくなので、Vue に Web Components を乗せてみました（[DEMO](https://github.com/doutori/web-components-vue)）

![sample.png](https://qiita-image-store.s3.amazonaws.com/0/166794/eb4d787d-b3a8-f9ca-200a-e66293f4f64d.png)

ただ、 Web Components の現行の仕様の1つの HTML imports は [ブラウザの実装状況](https://caniuse.com/#search=html%20imports) がよくなく、[ES Modules](https://ics.media/entry/16511) ( [HTML Modules](https://github.com/w3c/webcomponents/issues/645) ) に取って代わられるであろうとも云われています :disappointed_relieved:
（今回は ES Modules を利用していますので、Chrome などで閲覧願います！）

どちらもクリックをすると、カウントアップさせるようにしているのですが、

- **Vue Component** ： 通常通り `vue-loader` を利用した単一ファイルコンポーネント
- **Web Components** ： ボタンの見た目だけは `Web Components`、クリックイベント発火時の処理に関しては Vue に任せるような形

にしています。
開発者ツールでソースを確認していただけると、作りが全然異なっていることがわかるでしょう。
見た目だけの薄い作りにしておくことで、万が一 Vue 以外のフレームワークなどでもコンポーネントを利用したい( Web Components 部分のみ流用する形 )となったときに、融通が利きやすそうです。

```js
Vue.config.ignoredElements = [
  'wc-button-red'
];
```
また、Vue のコンポーネントとして認識されないように、Web Components として登録しているコンポーネントを ignore 指定しておくことがポイントです。


## Vue Costom Elements とは?

こちらは Vue コンポーネントのラッパーライブラリになります。
Web Components の Custom Elements 機能を利用（Polyfillも勿論あります）しており、**Vueコンポーネントをカスタム要素として抽出・登録** をすることができます。
素の状態で利用したり、Vue.js 以外のコンポーネント指向フレームワーク（ライブラリ）などでも使用したりなどが可能になります（そんなユースケースはあまり存在しないかもしれませんが...）

https://github.com/karol-f/vue-custom-element


## vue-custom-elements に触れてみる

ここから先は特に DEMO を作成していません。
スクショを貼っているので、何となく雰囲気を感じていただければ...

### Vueコンポーネントをカスタム要素に登録

`button.vue` というコンポーネントがあったとして、 それを `vce-button` というカスタム要素として登録してみます。

```html:button.vue
<template>
  <button>{{text}}</button>
</template>

<script>
export default {
  name: 'button',
  data () {
    return {
      text: 'ボタンです'
    }
  }
}
</script>

<style lang="scss">
button {
  font-size: 16px;
  cursor: pointer;
  color: #fff;
  padding: 1em 2em;
  background: royalblue;
}
</style>
```

```js:main.js
import Vue from 'vue'
import vueCustomElement from 'vue-custom-element'
import Button from './components/button.vue'
Vue.use(vueCustomElement);

Vue.customElement('vce-button', Button); // ここで定義!!
```

`<vce-button>` のカスタム要素としてレンダリングが行われていますね！

![sample01.png](https://qiita-image-store.s3.amazonaws.com/0/166794/df70d222-d742-5676-a6ef-be6b88eaccb7.png)

CSSの展開はいつも通り `style-loader` での展開になるので、 `scope` を付けてClass名をハッシュ値に変換しておいた方がよさそうです。
この辺は `vue-loader` のお作法がそのまま活かされるようです。


### ShadowDOM の利用

Optional で ShadowDOM を利用することもできます。 
ShadowDOM を利用する場合は、 `vue-loader` の `<style>` 要素部分は無視されます。

```js:main.js
Vue.customElement('vce-button', Button, {
  shadow: true,
  shadowCss: `
    button {
    font-size: 16px;
    cursor: pointer;
    color: white;
    padding: 1em 2em;
    background: royalblue;
  `
});
```

![sample02.png](https://qiita-image-store.s3.amazonaws.com/0/166794/fe34eb22-7f76-8242-d030-208ad33c7c57.png)

### Props の利用

コンポーネント間は勿論のことですが、カスタム要素として登録する際に、その子どもにあたるコンポーネントに `Props` を渡すこともできます。

```html:index.html
<body>
<!-- props で suffix を渡す -->
<vce-button suffix="か?" />
<script src="./dist/build.js"></script>
</body>
```

```html:button.vue
<template>
  <button>{{text}}{{suffix}}</button>
</template>

<script>
export default {
  name: 'button',
  props: {
    'suffix': String,
  },
  data () {
    return {
      text: 'ボタンです'
    }
  }
}
</script>
```


![sample04.png](https://qiita-image-store.s3.amazonaws.com/0/166794/aa480c88-f3bb-a41a-0428-2d81af0814c2.png)

### Slot の利用
いつもの感覚で slot を利用することもできます。

```html:index.html
<body>
<vce-button>
  <span vue-slot="prefix">私は</span>
  ボタンです
</vce-button>
<script src="./dist/build.js"></script>
</body>
```

```html:button.vue
<template>
  <button>
    <!-- いつもの事ながら、名前付きslotの初期値を入れておける -->
    <slot name="prefix">これは!!!</slot><slot></slot>
  </button>
</template>
```
![sample03.png](https://qiita-image-store.s3.amazonaws.com/0/166794/2ec1033a-393d-918c-effb-ad3e1a7dcaab.png)


### Vue の UI ライブラリを入れてみる

ライブラリの [DEMO](https://karol-f.github.io/vue-custom-element/#/demos/basic) のところを見てみると、
しれっと当然のように、Vue の UI ライブラリである [Element](http://element.eleme.io/#/en-US) が内包されたカスタム要素を作成していたりします。
原理的には、Vue のコンポーネントで有りさえすればどのような形でもカスタム要素は生成できるみたいです。
ただ、その分当たり前ですが JS のファイルサイズが跳ね上がるので、様子見ながらと云ったところでしょうか?


# さいごに

Web Components が普及することで、 UI ライブラリの選択肢も Vue のものに限る必要がなくなってきますし、ライブラリ自体の製作者側の負担も減ってきそうです。
Web Components に触れる機会はこれからどんどん増えていくでしょうし、 Vue ももちろんですが今後の動向が気になるところですね！