Web Componentとは～

# はじめに
Web Componentについて、今理解している範囲で書きます。仕事ではまだ使う機会がなく、勉強の目的でネットに調べて色々作って遊んでいるレベルです。間違いなどありましたらご指摘頂ければ幸いです。

## Web Componentとは
Webコンポーネントとは、HTMLの要素を**カプセル化して再利用可能なパーツにする**ためのインタフェースです。(パーツ=Component)

[公式サイト](https://www.webcomponents.org/introduction)にはとても分かりやすく書いてあります。
>Web components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps. Custom components and widgets build on the Web Component standards, will work across modern browsers, and can be used with any JavaScript library or framework that works with HTML.

>Web components are based on existing web standards. Features to support web components are currently being added to the HTML and DOM specs, letting web developers easily extend HTML with new elements with encapsulated styling and custom behavior.

要約すると、WebコンポーネントとはAPIのセットであり、以下の３つの機能を提供する
1. 新しいHTML要素の作成
2. 再利用可能な要素の作成
3. カプセル化したHTML要素の作成

Webコンポーネントは、既存のWebの仕組みに基づいています。そして、開発者が簡単に利用できるようにするために、現在HTMLの標準の仕様の中に盛り込んでいるところです。

## Web Componentsを構成するAPI群
Web Componentsを構成するAPI群は以下の4つです。
1. Custom Element
2. Shadow DOM
3. ES Modules
4. HTML Template

一つずつ簡単に説明します。
### Custom Element
HTMLには、div、p、table等のようにタグ名が決まっています。カスタム要素とは、HTML要素のタグ名を自分で定義できる機能です。カスタム要素を使用して、新しい HTMLタグ（要素）を作成したり、既存の HTMLタグを拡張したり、他のデベロッパーが作成したコンポーネントを拡張したりすることができます。
では、実際に書いてみましょう。以下の例では、標準のボタンを拡張し、クリックしたら背景の色が変わるボタンを作るJSコードを書いています。

```mybutton.js
class MyButton extends HTMLElement {
    constructor() {
        super();
        this.addEventListener('click', e => {
            this.querySelector("button").classList.toggle("active");
        });
        this.innerHTML = `
        <style>
            button {
                border: none;
                border-radius: 3px;
                padding: 10px 20px;
            }
            .active{
                background: #ffcc34;
            }
        </style>
        <button type="button">${this.innerHTML}</button>
        `;
    }
}

window.customElements.define('my-button', MyButton);
```
このJSファイルをHTMLページに読み込めば、通常は「Button」タグを使いますが、「my-button」タグを使えます。しかも、通常のボタンの場合は、クリックやスタイルについて別当記述する必要ありますが、今回はそれらを記述することなく実現できます。

```mybutton.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
</head>

<body>
    <my-button>Click</my-button>
    <script src="./mybutton.js"></script>
</body>

</html>
```
今後似たようなボタンを作る必要があれば、mybutton.jsを読み込むだけで「my-button」をどのページにも表示する事ができます。

<p class="codepen" data-height="355" data-theme-id="dark" data-default-tab="html,result" data-user="_dakc_" data-slug-hash="poveyBX" style="height: 355px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="poveyBX">
  <span>See the Pen <a href="https://codepen.io/_dakc_/pen/poveyBX">
  poveyBX</a> by dakc (<a href="https://codepen.io/_dakc_">@_dakc_</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

### Shadow DOM
Shadow DOMとは、外界へ影響を与えないように要素を隠しこんでしまう機能です。自己完結型ウェブ コンポーネントと言ったりしているようです。 カプセル化されたDOMツリーを要素に紐付けます。そのDOMツリーをShadow DOMツリーといいます。Shadow DOM ツリーは、メインドキュメントの DOM とは別にレンダリングされるため、ドキュメントの他の部分との重複を恐れることなく、JSやCSSを適用する事ができます。
>例えば、HTMLページに一つボタンがあり、button{background:red;}をCSSとして書いたとします。今後別のボタンを設置する必要が出た時に、そのボタンも最初と同じデザインになってしまします。これは、HTML、CSS、および JS がグローバルであるという性質を持つから生じる問題です。ボタンのデザインを同じしたいときはいいのですが、このボタンには、特殊なデザインまたは、特殊な処理をさせたい。そして、ほかのところのCSSやJSの影響を受けたくないケースもたくさんあると思います。その時に役に立つのがShadow DOMです。

実際にコードを書いてみましょう。
button要素のスタイルを記述した要素を作ります。

```mybutton.js
class MyButton extends HTMLElement {
    constructor() {
        super();

        this.attachShadow({ mode: "open" });
        this.shadowRoot.innerHTML = `
        <style>
            button {
                border: none;
                border-radius: 3px;
                padding: 10px 20px;
            }
        </style>
        <button type="button">${this.innerHTML}</button>
        `;
    }
}

window.customElements.define('my-button', MyButton);
```
そして、複数ボタンを持つHTMLページを用意します。

```shadowdomtutorial.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
</head>

<body>
    <my-button>Custom Button</my-button>
    <button>Default Button 1</button>
    <button>Default Button 2</button>
    <script src="./mybutton.js"></script>
</body>

</html>
```
ブラウザでshadowdomtutorial.htmlを開いたらわかりますが、上記でbutton{..}としてCSSを書いていたにも関わらず、そのスタイルは最初のボタンのみに適用されています。
開発者ツールで最初のボタンの中身を見てみるとほかのボタンとは違う構造しているのが分かります。

<p class="codepen" data-height="382" data-theme-id="dark" data-default-tab="html,result" data-user="_dakc_" data-slug-hash="LYEWZpj" style="height: 382px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="LYEWZpj">
  <span>See the Pen <a href="https://codepen.io/_dakc_/pen/LYEWZpj">
  LYEWZpj</a> by dakc (<a href="https://codepen.io/_dakc_">@_dakc_</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

### ES Modules
これは、一つのJSファイルの中身を別のJSファイルからアクセスできるようにするための機能です。通常の場合は、JSファイルをすべてHTMLページのscriptタグで指定しないとその機能を利用できないのですが、ES　Modulesを使うと一つのファイルだけscriptタグで指定し、ほかのファイルはその一つ目のファイルでインポートする事で利用可能になります。
では、実際にコード書いてみましょう。

まずは、二つのJSファイルを用意します。

```alert.js
export function sayHello() {
    var message = "Hello";
    say(message);
}

function say(msg) {
    alert(msg);
}
```

```script.js
import { sayHello } from "./alert.js";

window.addEventListener("load", e => {
    sayHello();
})
```
そして、一つだけJSファイルをHTMLファイルのscriptタグで指定し、二つのJSファイルの処理を実行するHTMLを記述します。

```esmodulestutorial.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <script type="module" src="./script.js"></script>
</head>

<body>
    <h1>ES Modules</h1>
</body>

</html>
```

### HTML Template
こちらは、HTML要素をテンプレートとして扱う機能です。Webページで同じ構造を繰り返し使用する必要がある場合、同じ実装を繰り返し書くよりも、テンプレートのようなものを作って利用するのが合理的です。このような場面でTemplateを使います。この要素と中身は DOM 上ではレンダリングされませんが、JavaScript から参照することができます。

では、実際のコードみてみましょう。

<p class="codepen" data-height="640" data-theme-id="dark" data-default-tab="html,result" data-user="_dakc_" data-slug-hash="XWJMKjj" style="height: 640px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="XWJMKjj">
  <span>See the Pen <a href="https://codepen.io/_dakc_/pen/XWJMKjj">
  XWJMKjj</a> by dakc (<a href="https://codepen.io/_dakc_">@_dakc_</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 最後に
Web componentの基礎の基礎を紹介しました。何かの役に立てれば幸いです。