Web Componentsの使いどころってどこだろう?


Web Componentsは結構昔から仕様策定は進んでいるものの、未だにWeb開発のメインストリームに乗れていない印象の仕様です。とはいえ実は結構身近な部分で使われていたりします。例えばelectronなんかではv1.6くらいから普通に使用できるみたいです。

atomの検証を行うとこんな感じになります。

右側を見てもらえればわかると思うんですが、atom-workspaceとかatomから始まるタグがあり、しっかりと使われています。

先に結論

個人的な見解ですが、現状Web Componentsの使い所はスタイルの隠蔽くらいかなとなりました。

Web Componentsを学ぶ前の自分の考え

今までReactやVue.jsと言ったライブラリを使用していたので下記のように、HTML Importsのようなものを妄想していました。

このソースコードはあくまで妄想で動きません。

btn-primary.html

<template>
  <a class='btn-primary'>
    <slot name='text'>
  </a>
  <style>
  .btn-primary {
     /* ここにボタンのスタイルが入ります */
   }
  </style>
</template>

index.html

<link rel='import' href='btn-primary.html'>
<btn-primary>
  <span slot='text'>Submit</span>
</btn-primary>

しかしHTML Importsは消え、ES modulesになり、想像していたHTMLとCSSの世界ではなく、JavaScriptでゴリゴリ頑張る世界でした。

とはいえ上記と同じような事を実装したいと思います。

Web Componentsを使ってみる

Custom Elements

class btnPrimary extends HTMLElement {}
customElements.define("btn-primary", btnPrimary);
<btn-primary>ボタン</btn-primary>

customElements.define()で定義します。第一引数は任意のタグ名、第二引数はclassを渡しています。

shadow dom

class btnPrimary extends HTMLElement {
  static get template() {
    return `
      <style>
        .btn {
          display: inline-block;
          font-weight: 400;
          text-align: center;
          white-space: nowrap;
          vertical-align: middle;
          user-select: none;
          border: 1px solid transparent;
          padding: .375rem .75rem;
          font-size: 1rem;
          line-height: 1.5;
          border-radius: .25rem;
          color: #fff;
          background-color: #007bff;
          border-color: #007bff;
        }

        .btn:hover {
          color: #fff;
          background-color: #0069d9;
          border-color: #0062cc;
        }
      </style>
      <div class='btn'>
        <slot></slot>
      </div>
    `;
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = btnPrimary.template;
  }
}
customElements.define('btn-primary', btnPrimary);

文字列として定義して、attachShadowで文字列からshadow domへ変換しているという事みたいです。これを複雑なスタイルとして定義していくのは辛いです。同じようなバッククォートを使うスタイルの記述方法ではstyled-componentsの方が幾分も洗礼されている印象です。

次は<slot>を複数持って任意のテキストを流し込んでいきます。
今回は<btn-primary>にsub-textとmain-textを読み込めるようにします。

class btnPrimary extends HTMLElement {
  static get template() {
    return `
      <style>
        * {
          margin: 0;
          padding: 0;
        }

        .btn {
          display: inline-block;
          font-weight: 400;
          text-align: center;
          white-space: nowrap;
          vertical-align: middle;
          user-select: none;
          border: 1px solid transparent;
          padding: .375rem 1.75rem;
          font-size: 1rem;
          line-height: 1.5;
          border-radius: .25rem;
          color: #fff;
          background-color: #007bff;
          border-color: #007bff;
        }

        .btn:hover {
          color: #fff;
          background-color: #0069d9;
          border-color: #0062cc;
        }

        .sub-text {
          font-weight: bold;
        }

        ::slotted([slot="sub-text"]) {
          font-size: .8rem;
          margin-bottom: .1rem;
        }

        ::slotted([slot="main-text"]) {
          font-size: 1.4rem;
          font-weight: bold;
        }
      </style>
      <div class="btn">
        <slot name="sub-text"></slot>
        <slot name="main-text"></slot>
      </div>
    `;
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = btnPrimary.template;
  }
}
customElements.define("btn-primary", btnPrimary);
<btn-primary>
  <div slot='sub-text'>subscription</div>
  <div slot='main-text'>購読する</div>
</btn-primary>

<slot>name=""<btn-primary>の中で定義している属性のslot=""がマッピングします。今回はslot='sub-text'と記述した要素と<slot name="sub-text">がマッピングしています。
このため<btn-primary>内での記述の順番は準拠されません。そのため下記のように書いても問題ありません。

<btn-primary>
  <div slot='main-text'>購読する</div>
  <div slot='sub-text'>subscription</div>
</btn-primary>

ライフサイクル

詳しくは書きませんがWeb ComponentsにはconnectedCallbackdisconnectedCallbackといったライフサイクルコールバックを持っています・・・。がこれ使って頑張るならやっぱりライブラリ使った方がよくないかなと感じています。

まとめ

先にも述べたように今回Web Componentsに触れてみて、スタイルの隠蔽を行う際はCSS設計で頑張るのではなくshadow domで隠蔽してしまうのは有用だと感じました。ただ現状ロジックをWeb Componentsに押し込むのであれば、ReactやVue.js、Angularのようなフレームワークを使うのが良いと思います。

個人的にはライブラリやJavaScriptを用いず同じような事ができればスマートですし、HTMLとCSSで完結できる世界がくるとWeb Componentsもメインストリームに乗ってくるのかなと感じていましたが、現状そのような未来はなさそうです・・・。