Snowpackのapp-template-lit-elementでかんたんWeb Compoments作成

UI開発者 古川

Snowpackはフロントエンド開発環境を構築するためのビルドツールです。

Snowpackには、単体でCreate Snowpack App(CSA)と呼ばれる各開発環境のスターターセットが用意されています。今回はCreate Snowpack Appのひとつであるapp-template-lit-elementを利用して、かんたんなWeb Componentsを作成する手順を紹介します。

app-template-lit-elementのインストール

app-template-lit-elementをインストールするには、まず以下のコマンドを任意のディレクトリで実行します。

npx create-snowpack-app [dirname] --template @snowpack/app-template-lit-element

インストールが完了したら、任意のディレクトリに移動して以下のコマンドのようにnpm startを実行してください。

cd [dirname]
npm start

http://localhost:8080にアクセスし、ブラウザで次のキャプチャのように表示されたら環境構築は成功です。

ブラウザでローカルサーバーが立ち上がったときの画面キャプチャ

ボタンのコンポーネントを作成

以下のGifアニメーションのように、ON/OFFを切り替えるトグルボタンを作成してみます。

ボタンをクリックしたときにONとOFFの表示が切り替わるトグルボタンのGifアニメーション

カスタムエレメントを定義

まずはコンポーネント用のファイルを作成します。

app-template-lit-elementで生成したテンプレートでは、コンポーネントとなるファイルはすべてsrc/ディレクトリ配下に格納されています。

そこで、トグルボタン用のファイルをsrc/Components/ToggleButton.jsという名前で作成し、以下のようにコードを記述します。

import { customElement, LitElement, html } from 'lit-element';

@customElement('toggle-button')
export class ToggleButton extends LitElement {
  render() {
    return html`
      <button class="c-button">ボタン</button>
    `;
  }
}

はじめに、コンポーネントに必要なLitElementライブラリを読み込みます。app-template-lit-elementではWeb Componentsの生成にLitElementを使用しています。LitElementはWebComponentsを生成するためのクラスライブラリで、内部的にはlit-htmlを利用しています。

続いてLitElementの@customElementデコレーターでカスタムエレメントを定義します。今回は<toggle-button>というコンポーネントを定義してみましょう。

デコレーターは本来まだネイティブのJavaScriptでは使えませんが、Babelのプラグインを使って利用が可能です。app-template-lit-elementでは、あらかじめデコレーターを利用する設定が完了しています。

カスタムエレメントの定義の次は、コンポーネントのShadow DOMテンプレートを定義します。テンプレートを定義するには、LitElementクラスのrender()関数を実装します。lit-elementのhtml関数をタグ付きテンプレートリテラルで呼び出し、render()関数がその結果を返すようにします。

  // ...
  render() {
    return html`
      <button class="c-button">ボタン</button>
    `;
  }
  // ...

ここまで記述が完了したら、画面の要素を表示している./src/app-root.jsで、作成したコンポーネントをimport文で読み込みます。次に./src/app-root.jsrender()関数内にさきほど定義した<toggle-button>を記述します。

import { customElement, property, LitElement, html, css } from 'lit-element';
import './Components/ToggleButton'; // コンポーネントの読み込み

// (中略)
// ...
  render() {
    return html`
      <div class="wrapper">
        <h1>LitElement + Snowpack</h1>
        <p>Edit <code>src/app-root.js</code> and save to reload.</p>

        <toggle-button></toggle-button>

        <a
          class="link"
          href="https://lit-element.polymer-project.org/"
          target="_blank"
          rel="noopener noreferrer"
        > ${this.message}</a>
      </div>
    `;
  }
// ...

ここまでのコードをブラウザで実行すると以下のキャプチャのようになります。

トグルボタンコンポーネントを追加したブラウザの画面キャプチャ

プロパティを定義

クリックイベントをトリガーにボタンのスタイルとテキストを切り替えたいため、@propertyデコレーターでデータバインディング用にpressedという名前のプロパティを定義したいと思います。

まずはじめにlit-elementライブラリから@propertyデコレーターを読み込み、以下のコードのように記述します。

import { customElement, property, LitElement, html } from 'lit-element';
@customElement('toggle-button')
export class ToggleButton extends LitElement {
  @property({
    type: Boolean,
    reflect: true
  })
  pressed = false;
  // (省略)...
}

LitElementでは属性の振る舞いなどをカスタマイズすることができ、その内容はオプションで設定できます。今回ポイントとなるのはreflectという設定です。このreflectはプロパティの変更を、カスタム要素に属性として反映させることができます。今回はプロパティをBooleanで設定しているため、trueのときは属性が付与され、falseのときは属性が削除されます。このように、LitElementではプロパティ自体の振る舞いをカスタマイズすることが可能です。

続いて、定義したpressedプロパティを利用して、ボタンの文言を切り替える式をカスタム要素内に記述します。また、テンプレート内に@clickプロパティを記述し、pressedプロパティの真偽値を切り替えるtoggle()関数をバインディングします。

@customElement('toggle-button')
export class ToggleButton extends LitElement {
  @property({
    type: Boolean,
    reflect: true,
  })
  pressed = false;

  toggle() {
    this.pressed = !this.pressed;
  }

  render() {
    return html`
      <button class="c-button" @click="${this.toggle}">
        ${this.pressed ? 'ON' : 'OFF'}
      </button>
    `;
  }
}

こうすることで、クリックするたびにtoggle()関数で属性が更新され、pressedプロパティがtrueの値の場合、HTML内にpressed属性が付与されます。

ここまでのコードをブラウザで実行すると以下のキャプチャのようになります。

クリックしたときに、Chrome DevToolsで属性が切り替わっている画面キャプチャ

スタイルを設定

最後にボタンのスタイルを付与しましょう。スタイルに必要なLitElementライブラリを読み込み、さきほど属性として付与した際に押したボタンのスタイルを設定します。

import { customElement, property, LitElement, html, css } from 'lit-element';

@customElement('toggle-button')
export class ToggleButton extends LitElement {
  @property({
    type: Boolean,
    reflect: true,
  })
  pressed = false;

  static get styles() {
    return css`
      :host {
        --base-color: #c7f8f9;
        --hover-color: #6ab1c9;
      }
      .c-button {
        background-color: transparent;
        color: var(--base-color);
        font-size: 1.5em;
        letter-spacing: 0.1em;
        border: 3px solid var(--base-color);
        margin-bottom: 30px;
        padding: 1rem 3rem;
        width: 200px;
        display: inline-block;
        cursor: pointer;
        border-radius: 5px;
      }
      :host([pressed]) .c-button {
        background-color: var(--base-color);
        color: var(--hover-color);
      }`;
  }
  // (省略)...
}

ここまでのコードをブラウザで実行すると以下のキャプチャのようになります。以上で、かんたんなコンポーネントの完成です。

スタイルが適用され完成したトグルボタンの画面キャプチャ

ビルド

最後にビルドをしましょう。ビルドコマンドは以下です。

npm run build

ビルド時、Snowpackはアプリケーションの依存ライブラリをESM importsで読み込めるよう変換します。コンポーネントはbuild/_dist_/フォルダに、依存パッケージはbuild/web_modules/にそれぞれ出力されます。

Snowpackはモジュールバンドラーをデフォルトでは備えておらず、公式プラグインとして提供されている@snowpack/plugin-webpack@snowpack/plugin-parcelを導入することで、ESM importsに対応していないレガシーブラウザに対応することが可能です。ただし、バンドラーを導入していても、developコマンドではES Modulesで出力されるため注意が必要です。