LitElement、使い方のキホン 第1回 ライブラリの概要

Polymer Projectによって提供されているWeb Componentsを簡単に作成できるライブラリLitElementとlit-htmlを紹介します。まずはライブラリ全体の概要からです。

発行

著者 宇野 陽太 フロントエンド・エンジニア
LitElement、使い方のキホン シリーズの記事一覧

はじめに

本シリーズでは、Web Componentsをより簡単に作成できるライブラリである、LitElementと、それと関連の深いテンプレートライブラリlit-htmlを紹介します。

Web Componentsとは、再利用可能なUIコンポーネントを作るための標準技術で、2020年6月現在、IE11を除くほとんどの主要ブラウザで利用することができます。

Web Componentsとは

LitElementの解説の前に、まずはLitElementを使うにあたって知っておいたほうがいい一般的なWeb Componentsの知識を簡単に説明しておきます*。

*注:Web Componentsについて

Web Componentsについて詳しく知りたい方は、以下のサイト・記事などを参照してみてください。

Web Componentsは、ReactやVue、Angularのように、UIを構築するためのHTML/CSS/JSを、ひとまとまりのコンポーネントとして作成するWeb標準技術です。

コンポーネント本体は、ES6から利用できるclass構文を用いたクラスで作成します。このクラスに、その要素で表示したいコンテンツや、その要素で提供したい振る舞いを定義し、最終的に独自の要素としてブラウザに認識させることで、HTMLからその要素を使うことができるようになります。

簡単にいうと、自分で定義した振る舞いや表示を持ったHTMLの要素を新たに作り出すことができます。

次に示すのは、Web Componentsの技術を使って定義した、独自要素の例です。

コード自体の詳しい解説は割愛しますが、ここでは「いろんなことを書かないといけないんだな」程度に思っていてもらってかまいません。

Web Componentsの例

class Greeting extends HTMLElement {
  static get observedAttributes() {
    return ['name'];
  }
// 属性へのプロパティの反映
  get name() {
    return this.getAttribute('name');
  }

  set name(value) {
    if (value) {
      this.setAttribute('name', value);
    } else {
      this.removeAttribute('name');
    }
  }
// コンポーネントの初期化
  constructor() {
    super();
    this.renderRoot = this.attachShadow({mode: 'open'});
    this.name = 'CodeGrid';
  }

  connectedCallback() {
      this.render();
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
      if (oldVal === newVal) {
        return;
    }
      this.name = newVal;
    this.render();
  }
// DOMの更新
  render() {
    this.renderRoot.innerHTML = `<p>Hello ${this.name}!</p>`
  }
}

customElements.define('x-greeting', Greeting)

このようにして定義した要素は、次のようにHTMLから使うことができます。

HTMLで要素として使う

<html>
  <head>
    <script src="/path/to.js" type="module"></script>
  </head>
  <body>
    <x-greeting></x-greeting>
  </body>
</html>

これから紹介するLitElementを使うと、このクラスの定義をより宣言的に記述することができます。その結果、コードの内容や意味を理解しやすくなり、より簡単にWeb Componentsを作成することができるようになります。

LitElementとは

LitElementは、Polymer Projectが提供しているライブラリです。

lit-htmlというテンプレートライブラリと、それを使ってHTMLの要素を作るのに必要な要素名や属性、リアクティビティの定義などをひとまとめにして、LitElementとして提供されています。

ライブラリの規模は小さく、比較的学習コストが低いのが特徴です。

主に提供しているのは、ベースクラスと呼ばれるLitElementクラスと、前述のlit-htmlが提供している関数を含むテンプレート用の関数です。さらに、この2種類に加えて、デコレータが提供されているので、必要に応じて利用します。デコレータについては後述します。

開発者は、これらを使いながら、コンポーネントクラスを定義していきます。冒頭で紹介したコンポーネントを、LitElementを使って書き直してみましょう。

LitElementを用いたコンポーネントの例(JavaScriptバージョン)

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

class Greeting extends LitElement {
  static get properties() {
    return {
      name: {
        type: String,
      },
    };
  }

  constructor() {
    super();
    this.name = 'CodeGrid';
  }

  render() {
    return html`<p>Hello ${this.name}!</p>`;
  }
}
customElements.define('x-greeting', Greeting);

冒頭のものと比べると、かなり読みやすくなっています。このコードを見ただけで、何をするためのコードなのかが、だいたいわかる方もいるのではないでしょうか?

プロパティの変更監視や、Shadow DOMの生成など、コンポーネントの作成に必要な低レベルな処理は、すべてLitElementクラスによって隠蔽されています。

LitElementを使うことで、開発者は必要なプロパティの定義や、データバインディング、イベントハンドリングなど、コンポーネントで提供したい機能の開発に、注力できるようになります。極端な話、Web Componentsに関して、あまり深い知識がなくてもLitElementを使うことはできるレベルになっています。

もちろん、LitElementで生成されるものは、標準仕様に準拠したWeb Componentsです。静的なHTMLから利用するだけでなく、Reactや、Vue、Angularなどのアプリに組み込んで、それらのコンポーネントと組み合わせて使うこともできます。Jamstack構成のアプリにおいて、動的なUIを構築するためのライブラリとしても有用です*。

*注: JamstackとLitElement

執筆時点(2020/06)での、新しいバージョンのCodeGridのWebアプリでは、動的なUIの実装にLitElementを使っています。

公式ドキュメントは、日本語に翻訳されており、必要な情報の多くは、このドキュメントから得ることができます。ただ、オリジナルの英語版と比べると、内容に大きな差はないものの、更新が遅くなっています。最新の情報を入手したい方は、英語版のドキュメントを参照してみてください。

TypeScriptでデコレータを使う

LitElementは、TypeScriptにも対応しています。TypeScriptで書いた場合、JavaScriptで書いたときと比べて、より宣言的に書くことができます。

JavaScriptバージョンのサンプルコードを、TypeScriptで書き直したものを見てみましょう。

LitElementを用いたコンポーネントの例(TypeScriptバージョン)

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

@customElement('x-greeting')
export default class Greeting extends LitElement {
  @property({type: String}) name = 'CodeGrid';

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}

TypeScriptで書いた場合、JavaScriptとは大きく異なる点として、@customElement('x-greeting')と、@property() name = 'CodeGrid'という記述が挙げられます。ここではTypeScriptで利用可能な、デコレータという機能*を使って、Greetingクラスに処理を追加しています。

*注:デコレータの使用

現在、デコレータはTypeScriptやBabelプラグインで有効にした場合のみ利用できますが、ECMAScriptの仕様として、TC39で仕様策定中(執筆時点でステージ2)です。将来的にはJavaScriptでもトランスパイルすることなく利用できるようになることが、期待できそうです。

@customElement('x-greeting')では、カスタム要素として登録する処理であるcustomElements.define('x-greeting', Greeting)を行っています。@customElement()の引数に、セレクタとして使いたい文字列x-greetingを渡すだけで、デコレータを指定したクラスを、カスタム要素として登録できるようになっています。

@property({type: String})では、プロパティ宣言(JavaScriptバージョンの4〜10行目)を隠蔽しています。

隠蔽されたプロパティ宣言(JavaScriptバージョンの該当箇所)

// 略
  static get properties() {
    return {
      name: {
        type: String,
      },
    };
  }
// 略

上記のコードは、Greetingクラスのプロパティであるnameを、x-greeting要素のname属性に反映するためのコードです。

get properties()を宣言する代わりに、クラスのプロパティに対して@property()デコレータで装飾することで、そのプロパティがカスタム要素のプロパティであることを伝えることができます。カスタム要素のプロパティ宣言については、次回以降、詳しく解説します。

ここではTypeScriptを例に解説しましたが、Babelなどのトランスパイラを使うことでも、デコレータを利用することはできます。デコレータが利用可能になれば、ここで紹介した書き方ができるようになります。どちらの方法でもかまいませんので、プロジェクトにマッチした方法を選択してみてください。

LitElementとビルド環境

次にLitElementの開発環境(ビルド環境)について触れておきたいと思います。

LitElementをビルドする方法は、大きく分けて次の2通りの方法があります。

  • Polymer CLIを使ったビルド
  • バンドルツールを使ったビルド

Polymer CLIを使ったビルド

Polymer Projectが提供しているPolymer CLIが、npmで公開されています。

npxコマンドで実行するか、お使いのPCにインストールすることで、polymerコマンドが利用可能になります。たとえば、次のように実行すると、./index.htmlをエントリポイントとしてローカルサーバーが立ち上がり、LitElementで構築したアプリケーションを実行することができます。

npxコマンドで実行

$ npx -p polymer-cli -c 'polymer serve ./index.html'

npmコマンドで実行

$ npm install -g polymer-cli
$ polymer serve ./index.html

Polymer CLIのコマンドについて詳しく知りたい方は、公式ドキュメントを参照してください。

バンドルツールを使ったビルド

TypeScriptやBabelなどのトランスパイラを利用する場合は、webpackやRollupなどのバンドルツールを利用してビルドしてください。

webpackの使い方は、CodeGridにもシリーズがあります。詳しい使い方を知りたい方は、これから始めるwebpackシリーズやキャッチアップwebpackシリーズをご覧ください。

次はTypeScriptを使う場合の、webpack.config.jsの例です。

webpackの設定例(webpack.config.js)

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './ts/index.ts',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js',
  },
  devtool: 'source-map',
  devServer: {
    contentBase: path.resolve(__dirname, './dist'),
    port: 3000,
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: ['ts-loader'],
      },
    ]
  },
  resolve: {
    extensions: ['.ts', '.js', '.json'],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HTMLWebpackPlugin({
      template: path.resolve(__dirname, './ts/index.html'),
    }),
  ]
}

LitElementのために特別なことをする必要はありません。

また、Rollupの設定例は、公式ドキュメントに記載があります。

LitElementを気軽に試す

ここで紹介した方法でも簡単にLitElementの開発環境を構築することができますが、もっと気軽に試したい場合は、オンラインエディタを使うという方法があります。

世の中にはいろいろなオンラインエディタがありますが、筆者はStackBlitzを使うことが多いです。トップ画面からTypeScriptの開発環境を選択すると、そのままブラウザ上でTypeScriptのアプリを書き始めることができます。また、依存モジュールのインストールも容易に行うことができ、LitElementも例外ではありません。

TypeScriptの他にも、ReactやAngularなどのライブラリのプリセットも用意されています。ちょっとしたコードを試したいときなどにオススメです。

なお、公式サイトにも別途インストールすることなく使える「お試し用」のページがあります。このページのコードはStackBlitzエディタ用に書かれています。

まとめ

Web Componentsの一般的な知識と、どのようにLitElementがWeb Componentsの実装を簡便にしてくれるのか、その概要を解説しました。

Web Componentsの知識がないと、まったくLitElementを使えないかと言えば、そうではありません。ただ、素のWeb Componentsをイチから書くよりは、だいぶ楽そうだなという感覚を持ってもらえばと思います。

次回はlit-htmlの使い方を詳しく紹介します。