Reactの代わりにWeb Componentsを使おうとした時の所感

こんにちは！　エンジニアの　きみどりはるか です！
Web Componentsを触ってみたので報告するね！

# まとめ
- HTML Templateは現状ちょっと使えない
- JavaScriptファイルにHTMLも書き込んでコンポーネント化するのが良さそう
- Reactとの組み合わせに期待

# まずWeb Componentsの紹介
### 概要(公式翻訳+要約)
- 新しいカスタム、再利用可能な、カプセル化されたHTMLタグを作成することができるウェブプラットフォームAPIのセット
- 既存のWeb標準がベース

### 4つの特徴
- Custom Elements
- Shadow DOM
- ES Modules
- HTML Template

具体的な説明はこちらを参照[
WEBCOMPONENTS.ORG](https://www.webcomponents.org/introduction)

### ブラウザサポート
もっとも対応が進んでいないCustom Elementsを代表してみてみよう
2022年１月現在は以下のような感じ
Safariで完全対応していないけど、Chromium系だと対応している
![スクリーンショット 2022-01-29 19.19.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459831/d3bcd512-67f7-d909-416a-1f81953717c6.png)


# Try① HTML Templateを使ったコンポーネント
チュートリアル的に作ろうと思えば、HTML Templateとslotを用いた方法でコンポーネントを作ることになるかも。

```html:HTML
<template id="hello-web-components">
  <style>
    slot[name="hello-text"] {
      text-align: center;
      font-weight: bold;
    }
  </style>

  <slot name="hello-text"></slot>
</template>

<hello-web-components>
  <h1 slot="hello-text">こんにちわ　xxxxさん</h1>
</hello-web-components>
```

```javascript:JavaScript
class HelloWebComponents extends HTMLElement {
  // 要素がドキュメントに挿入された時にコールされる
  connectedCallback() {
    const 
      shadow = this.attachShadow({ mode: 'closed' }),
      template = document.getElementById('hello-web-components').content.cloneNode(true)

    shadow.append( template );
  }
}

// カスタム要素の登録
customElements.define('hello-web-components', HelloWebComponents);
```
HTMLの方は`<template></template>`とCustom Elementの`<hello-web-components></hello-web-components>`から成っている。
JavaScriptの方のコードは`<hello-web-components>`を定義している。
少しややこしいが、Custom Elementの定義の際に`<template>...</template>`を使用していて、出来上がったCustom ElementをHTMLで使用するという関係になっている。
私は上記のことを完全に理解すると、さっそくコンポーネントの作成に取り掛かった。

#### 難しい問題に行き当たる
さっそくテキストフォームのコンポーネントを作成しようとし、`components`ディレクトリ直下に、`text-form.html`ファイルを作成

```
project_root/
├── index.html
├── components/
│   └── text-form.html
└── src/
```

`components`配下には他にもたくさんコンポーネントを作っていって、`index.html`で読み込んで使っていけばよさそうだ
`text-form.html`の中身はこんな感じを考えた

```html:text-form.html
<template id="text-form">
  <style>
    ...
  </style>

  <div id="ok-label">OK</div>
  <div id="cancel-label">Cancel</div>
  <input type="text" id="input" placeholder="入力してください" />

  <script>
    const input = document.getElementById('input');
    input.addEventListener('blur', (e) => {
      ...      
    }
  </script>
</template>

<script>
class TextForm extends HTMLElement {
   ...(上記のテンプレートを使ってカスタム要素を作る処理)...
}

customElements.define('text-form', TextForm)
</script>
```
ここまでやって、コンポーネントを`index.html`にインポートする手段が無いことに気づいた
実はWeb Componentsには`HTML Imports`という、htmlファイルを他のhtmlファイルにインポートする仕組みがあった。
しかし紆余曲折あり、その機能は提供されなくなった。
そうなるともう、このHTMLコンポーネントをどう取り込めばいいのか頭を悩ませることになった。
先人の中には、webpackのhtml-loader等でバンドルしている方もいたが、それだと使用しないモジュールも含めてバンドルすることとなり、実用的でない気がする。
一旦この方式は諦める

# Try② JavaScriptにHTMLも書いちゃう
`HTML Template`を諦めれば、Custom ElementsとShadow DOMを利用してコンポーネントを作成していくことが可能。
すなわち、こういう感じにしてしまうこと（百聞は一見にしかず）

```js:text-form.js
class TextForm extends HTMLElement {
  connectedCallback() {
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = this.template();
  }

  template() {
    return `
      <div id="ok-label">OK</div>
      <div id="cancel-label">Cancel</div>
      <input type="text" id="input" placeholder="入力してください" />

      <script>
        const input = document.getElementById('input');
        input.addEventListener('blur', (e) => {
          ...
        }
      </script>
    `;
  }
}

customElements.define('text-form', TextForm);
```
ご覧のようにCustom Elementの定義の中にHTMLを書き込んでいる
こうすれば`text-form.js`をimportすることで`<text-form>`要素を使用することができるようになる

#### この方式の欠点
今のところ、vs codeのシンタックスハイライトが対応してなくて、HTMLがめちゃくちゃ書きにくいぐらいしかない
もっと作っていくと色々見えてくるかもしれない

#### webcomponents.orgで公開されているコンポーネントはほぼTry②を採用
[webcomponents.org](https://www.webcomponents.org/)には有志がWeb Componentsで便利コンポーネントを作成し、公開しておられます。
もちろん全数調査したわけではありませんが、私の観測範囲ではすべてTry②方式でした。
バンドラーを前提としない形での配布になるのは、あたりまえっちゃ当たり前かもしれませんが…

# 感想
正直Reactは使った方がいいなと思いました
ただ、ReactやVueなどのフレームワークとShadow DOMを組み合わせるとかなり良さそうなので、次回はそっちを試してみたいです
以上！きみでりはるかでした！