こんにちは。サービス開発統括部 エンジニアリング部のYuto SAGAWAです。*1
最近、自分が取り組んでいるコンポーネントの実装や作成について紹介します。
概要
Webフロントエンド開発において、利用されているライブラリやフレームワークは数多く存在します。
移り変わりも早く日々進化しているように思えましたが、最近では落ち着いてきたように感じています。
そんなフロントエンド開発ですが、コンポーネントの粒度や変更に強いコンポーネントの実装など
コンポーネント作成に関して課題があるように感じています。
本記事では、変更に強いコンポーネントを作れるように、
WebComponents
と LitElement
の紹介と、それらを使ったコンポーネント作成をしてみたいと思います。
WebComponentsとは
WebComponents
とは、WebブラウザのAPI群で、下記のような特徴があります。
- StyleやScriptを含みHTMLの要素をカプセル化できる
- 再利用可能なパーツを作れる
- ライブラリなしでコンポーネントが作れる
- VirtualDOMではなくWeb標準
- HTMLElementを拡張する
WebComponentsサンプル
例として、WebComponents
で <button>
をカスタマイズして <my-button>
を作成してみます。
class MyButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ` <style> button { border-radius: 3px; padding: 16px; } </style> <button type="button"> <slot name="label" /> </button> `; } } customElements.define("my-button", MyButton);
HTMLElement
にStyleを適用し customElements
として定義します。
これで WebComponents
の作成は完了です。使う側で、作成した my-button
をimportして使用します。
import MyButton from 'my-button' /* または <script type="module" src="my-button.js" /> */ <my-button> <span slot="label">My Button</span> </my-button>
実行結果が下記になります。
ライブラリを使わずにWeb標準でコンポーネントを実装することができました。
Web標準でのコンポーネント
Web標準を使うことで、ReactやVue.jsなどをはじめ、BootstrapやBULUMAなどのCSSライブラリなども含めて フレームワーク論争から脱却することが期待できます。
...と期待はできますが、Web標準を利用することでの辛さももちろん存在します。
- Dom APIベース
- 差分更新がない
- レンダリングパフォーマンスが低い
- IE11対応じゃない
Dom APIベースであるため、覚えにくいし、読みづらい印象を受けます。 また、ReactやVue.jsにあるような差分更新がないため、レンダリングパフォーマンスが低下します。
Polymer Projectの誕生
GoogleのChrome開発チームによってPolymer Projectが発足されました。 Polymer Projectのミッションは「Webをより良くすること」。
About the Polymer Project As front-end engineers in the Chrome team, out mission is to make the web better.
refs: https://www.polymer-project.org/
Polymer Libraries
Polymer Projectとして、現在3つのライブラリが公開されています。 それぞれ簡単に特徴を見ていきます。
Polymer
Polymerは WebComponents
におけるpolyfillの役割を果たし、さらに下記のような特徴があります。
- WebComponents Sugaring
- 双方向データバインディング
- Material Designを採用
- YouTube, Google Earth, Netflix などで利用されている
また、将来的には後述するLitElementが主流になりそうです。
refs: https://www.polymer-project.org/blog/2018-05-02-roadmap-faq#polymer-3.0-or-litelement
lit-html
テンプレートからDOMの生成及び更新する役割を果たし、さらに下記のような特徴があります。
- TypeScriptで書かれている
- 単方向バインディング
LitElement
LitElement
は WebComponents
のためのベースクラスで、下記のような特徴があります。
- TypeScriptで書かれている
- レンダリングにlit-htmlを使用
- 素早くWebComponentsを開発できる
LitElementについて
LitElement
についてさらに見ていきます。
LitElement
は前述したように、素早く WebComponents
を開発するためのライブラリです。
LitElement Starter
TS, JSそれぞれに対応した LitElement Starter
があります。
- TS: https://github.com/PolymerLabs/lit-element-starter-ts
- JS: https://github.com/PolymerLabs/lit-element-starter-js
コンポーネントカタログ
LitElement Starter
には Storybook
のようなコンポーネントカタログが含まれています。
- Markdownで記述できる
- Layoutも変更できる
LitElement Sample
簡単なCounterコンポーネントを実装します。
import { LitElement, html, customElement, property, css } from 'lit-element'; @customElement('my-element') export class MyElement extends LitElement { static styles = css` :host { display: block; padding: 16px; border: 1px solid black; width: 400px; } `; @property({type: Number}) count = 0; render() { return html` <h1>Counter</h1> <h2>${this.count}</h2> <button @click=${() => {this._onClick(-1)}} part="button"> - </button> <button @click=${() => {this._onClick(1)}} part="button"> + </button> `; } private _onClick(num: number) { this.count += num; } } declare global { interface HTMLElementTagNameMap { 'my-element': MyElement; } }
カウントの追加と削減を機能を持ち、カウント数を表示するコンポーネントを my-element
として定義します。
使う側で、作成した my-element
をimportして使用します。
import { my-element } from 'my-element; <my-element />
実行結果が下記になります。
LitElement Docs Sample
LitElement Starter
のコンポーネントカタログを利用して、コンポーネントのドキュメントを作成します。
デフォルトでは下記のようなドキュメントが作成されます。
LitElementの使い所
LitElement
は WebComponents
標準に準拠しているので、共通のコンポーネントとして力を発揮します。
任意のフレームワークで動作するため、ReactやVue.jsなどを使用しているプロジェクトにも導入することができます。
React
import { my-element } from 'my-element; export const Counter = () => { return <my-element /> }
Vue.js
<template> <my-element /> </template> <script> import { my-element } from 'my-element; export default { comopnents: { MyElement } } </script>
プロジェクトの垣根を超えて使われるような、汎用的な処理などをカプセル化に含めてしまい、
WebComponents
として作っておくのもいいかもしれません。
LitElementの辛いところ
- 双方向でのデータのやり取りが複雑
- クリックなどのイベントハンドリングを使う側で設定する必要がある
Reactの公式ドキュメントにも WebComponents
についての記述があります。
Events emitted by a Web Component may not properly propagate through a React render tree. You will need to manually attach event handlers to handle these events within your React components.
refs: https://reactjs.org/docs/web-components.html
最後に
ここまで執筆しましたが、WebComponents
と LitElement
で本当に変更に強いコンポーネントが作れたとは言い切れません。本当に変更に強いコンポーネント開発の旅はまだまだ続きそうです。
現在、私達サービス開発統括部では、複数のサービスが存在しますが、 Vue.jsで書かれているプロジェクトだけではなく、Reactで書かれているプロジェクトもあります。 さらに、将来的には「24サービスの同時開発できる組織」を目指しています。 そのためにも、使い回せる汎用的なコンポーネントを作ることができれば開発速度もあがるかもしれません。 複数サービスがあり、異なったフレームワークやライブラリを使っているチームには汎用的なコンポーネントを WebComponentsとして作ってみてはいかがでしょうか。
おまけ
その他 WebComponents
ライブラリ
Yuto SAGAWAさんのプロフィール
サービス企画開発本部 サービス開発統括部 エンジニアリング部 サービスアーキテクトグループ
現在は退職
※2020年7月現在の情報です。
*1:※Yuto SAGAWAは退職していますが、本人の同意を得て掲載を継続しています。