Web Components 入門 〜基礎編〜

# 最初に

これを読んでいるなら Web Components という言葉に耳覚えがあると思います。

Web Components は全ての[モダンブラウザーが対応している](https://twitter.com/polymer/status/1217578939456970754)Web の標準仕様です。

これらの仕様で[どんな Web アプリでも使える](https://custom-elements-everywhere.com/)UI コンポーネントを作ることができます。

ここまで知っていても、どこから作ればいいかわからないかもしれません。しかし、それはある程度仕方のないことです。

現代のフロントエンド開発にの素晴らしさであり、また大きな問題となる要素に、は「API のあるところにはそれを『もっとシンプルにする』方法は無限に出てくる」という部分があります。

Web Components も例外ではなく、私の知る限り、Web Components を作るためのライブラリーは 20 以上あります。

そして豊富な選択肢があるからこそ、Web Components 開発を始めたばかりの人は、そもそもどこから始めればいいか迷ってしまいます。

そういった悩みのある人のための記事になります。

この記事と側扁になる記事を通して下記のテーマをカバーします

1. Web Components という標準仕様の基礎 - この記事です 😉
2. Web Components を作るためのライブラリーのアプローチ - 先ほど言っていた 20 以上あるライブラリーは大体似たようなアプローチを使っているのでそれぞれのパターンを別々の記事で説明してそのパターンを使うライブラリーを軽く紹介します。

一つだけ注意点ですが、この記事のシリーズでは各ライブラリーでどういう風に Web Components を作るかまではカバーしません、それぞれの公式ドキュメントはそのためにあると思います。

この記事のシリーズはあくまで Web Components を始めたばかりのエンジニアに自分にあった Web Components の作り方を見つけ出す手助けになればと思います。😊

Web Components の標準仕様の推進に大きく影響したと言える [Polymer プロジェクト](https://www.polymer-project.org/)のメンバー、[Justin Fagnani](https://twitter.com/justinfagnani) さんの名セリフを借りると

> [...] the big idea with web components is that it shouldn't matter which or even if an approach (to create Web Components) becomes more popular. As long as components interoperate we all win.
>
> [...] Web Components のいいところは作るためのアプローチの中でどれが一番流行っても一緒です。コンポーネントを一緒に使えることができればみんなが得します。

さて、退屈なことはこれで終わりです、コードに移りましょう。

---

# Web Component を作るに必要なもの

Web Components の標準仕様をよくカバーする記事は色々とありますので、ここではあえて説明はしませんがちょっとしたおさらいが必要であればこちらの [MDN の記事](https://developer.mozilla.org/ja/docs/Web/Web_Components)がおすすめです。

さて、標準仕様を読むのはいいんですけど、実際の標準な Web Component のコードってどういったものでしょう？

こちらはシンプルな「Hello World」コンポーネントのコードです、全てを理解できなくても大丈夫です、後でもう少し詳しく説明します。😉

```javascript
const template = document.createElement("template");
template.innerHTML = `<div>Hello <span class="name"></span></div>`;

class MyGreeting extends HTMLElement {
  constructor() {
    super();
    this.name = "World";
  }

  // Start - 標準ライフサイクルのcallback
  // これはコンポーネントがdocumentに初めて追加される時に呼ばれます
  connectedCallback() {
    if (!this.shadowRoot) {
      this.attachShadow({ mode: "open" });
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
    this._nameSpan = this.shadowRoot.querySelector(".name");
    this._nameSpan.textContent = this.name;
  }
  // ここでどの要素を変更したらcallbackが発生するかを定義します
  static get observedAttributes() {
    return ["name"];
  }
  // 上のArrayの要素が変更された時に呼ばれるcallback
  attributeChangedCallback(attr, oldVal, newVal) {
    if (oldVal !== newVal) {
      this[attr] = newVal;
    }
  }

  // End - 標準ライフサイクルのcallback

  set name(value) {
    this.safeSetAttribute("name", value);
    if (this._nameSpan) {
      this._nameSpan.textContent = value;
    }
  }

  get name() {
    return this.getAttribute("name");
  }

  // 要素変更での無限ループを回避するための関数
  safeSetAttribute(attr, value) {
    if (this.getAttribute(attr) !== value) {
      this.setAttribute(attr, value);
    }
  }
}

window.customElements.define("my-greeting", MyGreeting);
```

このシンプルなコードで全ての Web Components の標準仕様がわかります。

1. コンポーネントが使う **`<template>`**を作成します。
2. 標準の`HTMLElement`を継承する**class**を定義してそれを window レベルの`CustomElementRegistry`に登録します。これでブラウザーは`<my-greeting>`を見たら定義した class でレンダリングします。
3. 定義したクラスには　**custom elements の標準ライフサイクルの callback**を使っていつ自分のコンポーネントを設定やアプデートすればいいかわかります。
4. `attachShadowRoot`関数でコンポーネントに使う**Shadow DOM 木構造を作ります**。

このコードを見て「実際にできていることに対してコードが多すぎない？」って思っているかもしれません。

私もそれに同感です、現在の Web Components の標準仕様はあくまで基礎的な部分しかカバーしないから色々なケースで必要になるコードを自分で書かないといけないです。

---

# もっとシンプルにしよう

ここで前述した、シンプルにする方法に戻ります。

これらの方法は標準仕様の一番痛いところをこういう風に解決してくれます。

1. レンダリングエンジンを使って手動な DOM の操作の必要を無くします。
2. 継承かラッピングかコンパイルで`CustomElementRegistry`に登録できる class を作成します。
3. 標準ライフサイクルの callback をエクステンドしてライブラリーによってはもっと色々なケース（例えばステート管理）に役立つ callback を追加します。
4. Shadow DOM 作りをして、Shadow DOM が使えない場合のための polyfill や fallback を追加します。

こう言ったことで作る側として楽に Web Components を作ることができるようになります。

そして、最終的の動作の一番重いところは完全に標準仕様任せになっているのでこの記事のシリーズでカバーするライブラリーのほとんどはなんと 10kB 以下です (minify と gzip 後)！💪

---

# 次は？

ここまでは「どうすれば Web Component を作れる？」と「どうして作るのにライブラリーを使った方がいい？」という質問の答えを出せたなら幸いです。

しかし、本来の目的を忘れていてはいけません、ここで私はあなたとあなたの運命のライブラリーとの出会いをプロデュースしなけらばなりません。💘

そしてそのライブラリーたちがしてくれることをいくつか並べましたが結局のところ一番「作る側が楽する」ことに繋がるのは class がどう定義されるかだと思います。

上記に書いた通り、ほとんどのライブラリーはこの三つのパターンのどれかになります。

1. `HTMLElement`を継承する class にそのライブラリーのいいところを足してその新しい class を`HTMLElement`の代わりに継承します。
2. 関数を通して登録できる class を作ってもらえます、自分のコードはその関数のパラメターになります。
3. 非標準 Javascript コードをライブラリーのツールを通して class にコンパイルします。

次の記事からは、各パターンをもっと詳細に話て、そのパターンを使っているライブラリーをできるだけ多く紹介します。

ここまで読んでくれてありがとうございます、この記事を気に入ってくれたなら次の記事もぜひ読んでみてください。

「ライブラリーのこういう情報がみたい」などの質問や提案があればぜひコメントをしてください。

[次の記事をみる](https://qiita.com/alangdm/items/fca988aab65ed2660198)