最初に
Web Components 入門シリーズへまたようこそ。このシリーズであなたとあなたの運命の Web Components ライブラリーの出会いをプロデュースしてみています!
前の記事で Web Components の標準仕様や Web Components を作る場合ライブラリーを使う方がいい理由について話しました。
今回は Web Component を作るためのライブラリーが使うパターンの一つ、Class ベースのパターンについて説明したいと思います。
しかし、その前に記事の後半に出てくる bundle サイズについてのお知らせです。
各ライブラリーの minify + gzip 後の bundle データの元としてBundlePhobiaを使います。
ですが、各ライブラリーのコードの書き方によってこれらを実際にアプリの一部として使う場合でのサイズの影響は大きく変わる場合はあります。
WebComponents.dev のみなさんはこういうデータを細かく分析していますので興味があればそれも読んでみてください。(このシリーズでカバーするライブラリーはだいたいこのサービスの Web IDE で試せます。)
さて、そろそろ始めましょう。
Class ベースのパターンは何ですか?
このシリーズの最初の記事でも言った通り、Web Component を作るためにはまずHTMLElementを継承する class を作ってそれをCustomElementRegistryに登録しなければなりません。
もちろん、HTMLElement を継承する class を自分の class で継承することでも可能です。
このパターンのライブライーはこれを利用して、HTMLElementを継承する class を作って、それにコンポーネントを作りやすくするコードを入れます。
例えば、下記にあるSuperAwesomeElementと言う class を使えばHTMLElementを継承しているときよりコンポネントの属性の変更がある場合でのアプデートがしやすくなります。
コードのデモはここにあります
export class SuperAwesomeElement extends HTMLElement {
constructor() {
super();
this.state = {};
}
static get attributes() {
return {};
}
static get observedAttributes() {
return Object.keys(this.attributes);
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) {
return;
}
// 属性をタイプごとにパーズしてステートを更新します
const type = this.attributes[name].type;
if (/array|object/i.test(type)) {
this.state[name] = JSON.parse(newValue);
} else if (/number/i.test(type)) {
this.state[name] = parseFloat(newValue);
} else {
this.state[name] = newValue;
}
this.update();
}
}
この class を使ってコンポーネントを作るとこういう風になります
import { SuperAwesomeElement } from "super-awesome-element";
const template = document.createElement("template");
template.innerHTML = `
<p>Text: <span class="text"></span></p>
<p>Number: <span class="int"></span></p>
<p>Object: <span class="obj"></span></p>
<p>Array: <span class="arr"></span></p>
`;
export class MyComponent extends SuperAwesomeElement {
constructor() {
super();
this.state = { text: "", int: 0, obj: {}, arr: [] };
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
this._textNode = this.shadowRoot.querySelector(".text");
this._intNode = this.shadowRoot.querySelector(".int");
this._objNode = this.shadowRoot.querySelector(".obj");
this._arrNode = this.shadowRoot.querySelector(".arr");
}
static get attributes() {
return {
text: { type: "string" },
int: { type: "number" },
obj: { type: "object" },
arr: { type: "array" },
};
}
update() {
this._textNode.textContent = this.state.text;
this._intNode.textContent = this.state.int;
this._objNode.textContent = JSON.stringify(this.state.obj);
this._arrNode.textContent = JSON.stringify(this.state.arr);
}
}
customElements.define("my-component", MyComponent);
もちろん、これはものすごく簡単な例で実際にプロジェクトに使えるというレベルではありません。
しかし、こういった簡単なものでもコンポーネントの作成に必要なコードがかなり縮小されます。
ちゃんとしたライブラリーでできることは段違いで多くなります。 💪
良し悪し
Class ベースのライブラリーを使うと作るコンポーネントは標準に近いものになります。
これはそのままいくつかのいいところと悪いところはあります。
いいところ
- マイグレーションが簡単 - バニラ JS や他の class ベースのライブラリーにマイグレーションすることになったら他のパターンのライブラリーを使っているときよりスムーズにできます。
- 拡張可能 - ライブラリーにないフィーチャーが複数のコンポーネントに必要になったら mixin class を通してコンポーネントに追加できます。しかも、mixin class の作り方によっては、このパターンのどのライブラリーにも使えることもあります。
- 標準仕様を学べる - このパターンのライブラリーを使うと標準仕様への理解が深まる。
悪いところ
-
ボイラープレートが若干多い - このパターンのライブラリーを使うとコンポーネントのコードがかなり縮小されますが、JS の class の性質のせいで他のパターンより若干多いです。
- 属性が変更後に副作用を入れるときなどに見られます。
- ですが、これでビルドサイズが多くなるとは限りません、あくまで自分の書くコードの話です。
このパターンを使うライブラリー
スターやバージョンやサイズのデータは投稿時の最新です。
CanJS
| スター | ライセンス | 最新バージョン | TS サポート | バンドルサイズ | テンプレートシステム |
|---|---|---|---|---|---|
| 1.8k+ | MIT | 1.1.2 (2020/06) | 見つけられなかった | 66kB | can-stache (mustache みたい) |
豆知識
CanJS はこのシリーズで紹介するライブラリーの中ではかなり大きいサイズのものです。
なぜかというと、他のライブラリーと違って、CanJS はコンポーネントを作るためのライブラリーというより Web Components ベースのフレームワークからです。
なので、自分のアプリを全て CanJS で作るならいいですけど、単に色々なプロジェクトで利用できるコンポーネントを作るなら他のライブラリーを使った方がいいです。
HyperHTML Element
| スター | ライセンス | 最新バージョン | TS サポート | バンドルサイズ | テンプレートシステム |
|---|---|---|---|---|---|
| 0.1k+ | ISC | 3.12.3 (2020/03) | あり | 8.7kB | hyperHTML (JS テンプレート文字列) |
豆知識
このライブラリーは主に hyperHTML でレンダーされた Web Components を作るためのヘルパーです。
ちなみに、hyperHTML はパーフォマンスで言えばトップクラスのレンダリングライブラリーです。⚡️
LitElement
| スター | ライセンス | 最新バージョン | TS サポート | バンドルサイズ | テンプレートシステム |
|---|---|---|---|---|---|
| 3.5k+ | BSD 3-Clause | 2.3.1 (2020/03) | あり、decorator 含む | 7.1kB | lit-html (JS テンプレート文字列) |
豆知識
Polymer v3 が存在する中 LitElement も Polymer Project のチームが作っているということは色々とややこしいですけど。
簡単に言うと、LitElement は Polymer v4 です、ですが、作り方もパフォーマンスも劇的に変わったのでライブラリーの名前も帰られました。
なので「Polymer」を使いたいのなら LitElement を使うといいです。😉
ちなみに LitElement の最初のリリースは v2.0.0 でした、なぜかというと lit-element の npm package はもともと別の人が持っていましたので、所有権はもらえたものの、v1.0.0 はすでにあったのでそれは使えませんでした。
LitElement の姉妹ライブラリーである lit-html は先ほどメンションした hyperHTML と色々と共通点はあります、トップクラスのパフォーマンスの点も含めてです。 ⚡️
Omi
| スター | ライセンス | 最新バージョン | TS サポート | バンドルサイズ | テンプレートシステム |
|---|---|---|---|---|---|
| 11.1k+ | MIT | 6.19.3 (2020/05) | あり、decorator 含む decorators | 8.3kB | JSX (Preact) |
豆知識
Omi はこの記事のライブラリーの中で唯一公式ドキュメントが複数言語対応しています。
ドキュメントが全部英語と中国語にあって、一部は韓国語もあります。🇬🇧🇨🇳🇰🇷
SkateJS
| スター | ライセンス | 最新バージョン | TS サポート | バンドルサイズ | テンプレートシステム |
|---|---|---|---|---|---|
| 3.1k+ | MIT | 0.0.1 (2018/12) | 見つけられなかった | 1.8kB + レンダーライブラリー | hyperHTML/lit-html (JS テンプレート文字列), JSX |
豆知識
SkateJS は結構独特なライブラリーでして、公式なテンプレートシステムを持っていません。
それに変わって、JS テンプレート文字列か JSX 系なレンダリングライブラリーと一緒に使うように設計されています。
ただ、コアチームはどうも今は SSR 周りの作業だけをしていてコンポーネントを作成するためのライブラリーは最近アプデートされていない様子です。
SlimJS
| スター | ライセンス | 最新バージョン | TS サポート | バンドルサイズ | テンプレートシステム |
|---|---|---|---|---|---|
| 0.7k+ | MIT | 4.0.7 (2019/04) | あり、decorator 含む | 3.3kB | 自分のライブラリー (mustache みたい) |
豆知識
名前の通り SlimJS はかなり小さいライブラリーです、この記事の中では一番小さめでシリーズ全体的でもかなり小さめの方です。
一つだけ注意点ですが、プロジェクトはここ一年ぐらい前から更新されていません。☹️
次は?
今回は class ベースパターンについて色々と話しました。
この記事で紹介したライブラリーの中で気に入ったものがあれば幸いです。 🎉
まだ見つけていないのならご安心ください、このシリーズで紹介するパターンはまだありますので、次の記事をぜひ読んでみてください。
ご意見やご感想もぜひコメントしてください。