Introducing "Lit" for Web Components
概要
2021/04/22に新しいWeb Componentsライブラリ、Lit
(Lit 2.0)がリリースされ、同日ローンチイベントもYouTubeで生配信されました。
それに伴いPolymer Project
がLit
に改名されロゴが刷新されました。
ウェブサイトも新しく公開されました。チュートリアルとPlaygroundが刷新され、非常に便利になりました。
実装は https://github.com/lit/lit に公開されています。
npm i lit
LitElementとlit-htmlのおさらい
旧Polymer Project(Lit 1.0)では、lit-html
(HTMLテンプレートライブラリ)とLitElement
(Web Componentsを実装するためのライブラリ)の2つのライブラリが提供されていました。
LitElement
にもテンプレート機能があったので、2つとも独立したライブラリとして提供されていました。
Lit(Lit 2.0)の特徴
Lit
はlit-html
やLitElement
などのWeb Componentsを開発するのに必要なライブラリを1つにまとめたものです。
コードも1つのリポジトリにまとめられmonorepoになっています。
Simple. Fast. Web Components. というフレーズからわかるようにシンプルかつスタンダードなWeb Componentsをより簡単に開発することにフォーカスしているのが特徴です。
詳しい機能についてはドキュメントを読んでください。
Lit 1.0からの改善点
軽量化とパフォーマンス向上
APIの再設計や不要な機能の削除、polyfillの分離などを行った結果、新機能を追加したにもかかわらず軽量化とパフォーマンス向上に成功しました。ローンチイベントで発表された具体的な数値は次のとおりです。
- minify時のサイズが30%減少(15KB)
- 初期レンダリング速度が5%〜20%改善
- レンダリング更新速度が7%〜15%改善
SSR対応
Lit
はDeclarative Shadow DOM
という仕様を利用してShadow DOMのSSRに対応しました。(おそらくWeb Components系ライブラリでは初。)
SSR対応のために内部実装の再設計やlit-html
に実装されていたマイナーな低レベルAPIを削除したとイベントの中で言っていました。
SSR対応はまだ正式にはリリースされていませんが、実装はすでにありGitHubでコードが公開されています。(後述のlit-labs/ssr
を参照。)
基本的な書き方
Lit
の基本的な書き方は次のとおりです。
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
// Define scoped styles right with your component, in plain CSS
static styles = css`
:host {
color: blue;
}
`;
// Declare reactive properties
@property()
name?: string = 'World';
// Render the UI as a function of component state
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
ドキュメントのサンプルコードはほぼ全てデコレーターを使って書かれていますが、デコレーターを使わずに書くことも可能です。
import {LitElement, css, html} from 'lit';
export class SimpleGreeting extends LitElement {
name?: string;
static styles = css`
:host {
color: blue;
}
`;
static get properties() {
return {
name: {}
}
}
constructor() {
super();
this.name = 'World';
}
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
customElements.define('simple-greeting', SimpleGreeting);
Lit 2.0の新機能
Lit 2.0で新しく追加された機能の中から注目度の高い3つの機能を紹介します。
ReactiveController
ReactiveController
は今回の目玉機能の1つです。
ReactiveController
は、コンポーネントのライフサイクルにアクセスできるオブジェクトです。この仕組を使うことでロジックや状態管理をコンポーネントの外側にまとめて定義でき、複数のコンポーネントで再利用できるようになります。
以下のサンプルコードでは時計を表示するコンポーネントをコントローラーを利用して構築しています。
import {ReactiveController, ReactiveControllerHost} from 'lit';
export class ClockController implements ReactiveController {
host: ReactiveControllerHost;
value = new Date();
timeout: number;
private _timerID?: number;
constructor(host: ReactiveControllerHost, timeout = 1000) {
(this.host = host).addController(this);
this.timeout = timeout;
}
hostConnected() {
// Start a timer when the host is connected
this._timerID = setInterval(() => {
this.value = new Date();
// Update the host with new value
this.host.requestUpdate();
});
}
hostDisconnected() {
// Clear the timer when the host is disconnected
clearInterval(this._timerID);
this._timerID = undefined;
}
}
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
import {ClockController} from './clock-controller.js';
@customElement('my-element')
class MyElement extends LitElement {
// Create the controller and store it
private clock = new ClockController(this, 100);
// Use the controller in render()
render() {
const formattedTime = timeFormat.format(this.clock.value);
return html`Current time: ${formattedTime}`;
}
}
const timeFormat = new Intl.DateTimeFormat('en-US', {
hour: 'numeric', minute: 'numeric', second: 'numeric',
});
ReactiveControllerHost
ReactiveController
の要はこのReactiveControllerHost
APIにあります。
ReactiveControllerHost
はコントローラーのコンポーネントへの追加や更新のリクエストを行い、Lit
のライフサイクルメソッドを呼び出す役割を持つオブジェクトです。
ReactiveControllerHost
はLitElement
である必要はなく、公開されているinterfaceに則ったオブジェクトであれば何でもOKです。
interface ReactiveControllerHost {
addController(controller: ReactiveController): void;
removeController(controller: ReactiveController): void;
requestUpdate(): void;
readonly updateComplete: Promise<boolean>;
}
そのため、このReactiveControllerHost
の役割を持ったオブジェクトを実装すれば他のフレームワーク中でもコントローラーを利用できるようになります。
後述するlit-labs/react
はReactとコントローラーを接続するための機能を提供するライブラリで、内部でReactiveControllerHost
が利用されています。現在、他のフレームワーク向けの実装も進められているみたいです。
ref()
ref
はLit
のBuilt-in directives
の1つで、DOM要素への参照を取得します。
下のサンプルコードではinput
要素への参照を取得しフォーカスさせています。
@customElement('my-element')
class MyElement extends LitElement {
inputRef: Ref<HTMLInputElement> = createRef();
render() {
// Passing ref directive a Ref object that will hold the element in .value
return html`<input ${ref(this.inputRef)}>`;
}
firstUpdated() {
const input = this.inputRef.value;
input.focus();
}
}
補足: ディレクティブとは?
ディレクティブとは、テンプレートのレンダリングをカスタマイズできる関数です。
様々なディレクティブがLit
に組み込まれている他、カスタムディレクティブを作ることもできます。
import { Directive, directive } from 'lit/directive.js';
// Define directive
class HelloDirective extends Directive {
render() {
return `Hello!`;
}
}
// Create the directive function
const hello = directive(HelloDirective);
// Use directive
const template = html`<div>${hello()}</div>`;
@state
これはInternal reactive state
を定義するためのデコレーターです。
@state
を使って定義されたプロパティはコンポーネントの外部から参照されず、コンポーネントのアトリビュートとして利用することはできません。
export class MyElement extends LitElement {
// Private. Doesn't have an attribute.
@state()
protected _active = false;
// Public. May have an assosiated attribute.
@property()
name: string;
}
また、デコレーターを使わなくてもInternal reactive state
を定義できます。
static get properties() {
return {
_active: { state: true }
}
}
constructor() {
this._active = false;
}
後方互換性
Lit
はLitElement
やlit-html
で書かれたほとんどのコードが動作するように設計されています。
APIのリネームやマイナーなBreaking Changeはありますが、大抵の場合はライブラリを置き換えるだけで問題ないでしょう。
1つ注意が必要な点は、IE11対応のためのPolyfillが別パッケージ(lit/polyfill-support.js
)として切り離されたことでしょう。LitElement
では本体に組み込まれていましたが、Lit
ではPolyfillを別途インポートする必要があります。(公式ドキュメントのサンプルコードではplatform-support.jsとなっていますがpolyfill-support.jsが正しいです。)
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js">
<script src="node_modules/lit/polyfill-support.js">
より詳しい変更点についてはアップグレードガイドを参照してください。
lit-labs
今回のリリースの重要なポイントの1つとしてこのlit-labs
があります。
lit-labs
はLit
の実験的なアイデアや機能を試す場所で、コミュニティにそれらを公開しフィードバックをもらうことで強力なエコシステムを構築するとともにコアの機能をより良くしていくことを目的にしているとローンチイベントで紹介されていました。
現在リポジトリにあるパッケージの中から注目度の高いものを2つ紹介します。
lit-labs/react
これはReact
のコンポーネントとLit
を接続するためのパッケージで、createComponent
とuseController
の2つの関数が用意されています。
実装は https://github.com/lit/lit/tree/main/packages/labs/react
createComponent
ReactでWeb Componentsを利用する際、Custom ElementsのプロパティとReactのpropsの接続が難しいという問題がありましたが、Custom ElementsをcreateComponent
でラップすることでpropsやイベントを簡単に接続できます。
createComponent
を使うときは、第1引数はReactモジュール、第2引数にCustom Elementsのタグ名、第3引数にCustom Elementsのクラス名(customElements.define
で使用しているもの)を渡します。
第4引数はこのコンポーネントが受け取ることのできるイベントをリストアップしたオブジェクトで、オブジェクトのキーはReactのpropsで渡されるイベントプロパティ名、オブジェクトの値はそれに対応するCustom Elementsで生成されるイベントの名前です。
下記の例では、MyElementComponent
のonactivate
を介してイベント関数が渡され、Custom Elementsのactivate
イベントが発生したときにその関数が呼び出されます。
import * as React from 'react';
import {createComponent} from '@lit-labs/react';
import {MyElement} from './my-element.ts';
export const MyElementComponent = createComponent(
React,
'my-element',
MyElement,
{
onactivate: 'activate',
onchange: 'change',
}
);
定義したコンポーネントは他のReactコンポーネントと同じように利用できます。
<MyElementComponent
active={isActive}
onactivate={(e) => (isActive = e.active)}
/>
useController
useController
はLit
のReactive Controller
をReactコンポーネント中で利用できるようにするためのReactフックです。
詳しいことはリポジトリのREADMEを読んでください。:pray:
import * as React from 'react';
import {useController} from '@lit-labs/react/use-controller.js';
import {MouseController} from '@example/mouse-controller';
// Write a React hook function:
const useMouse = () => {
// Use useController to create and store a controller instance:
const controller = useController(React, (host) => new MouseController(host));
// return the controller: return controller;
// or return a custom object for a more React-idiomatic API:
return controller.position;
};
// Now use the new hook in a React component:
const Component = (props) => {
const mousePosition = useMouse();
return (
<pre>
x: {mousePosition.x}
y: {mousePosition.y}
</pre>
);
};
lit-labs/ssr
これはLit
のテンプレートやコンポーネントをSSRするためのパッケージです。
Next.jsやNuxt.jsなどを中心にSSRが普及した現在、Web ComponentsをSSRしたいという需要も高まっており、それに応えるかたちになったと言えます。
まだプレリリースの段階だとREADMEには記載されていますが、ローンチイベントではEleventy
のプラグインとして利用しマークダウン内に埋め込まれたCustom ElementsをレンダリングするデモやKoa
のミドルウェアで利用するデモが披露されていました。
Lit
のSSRはDeclarative Shadow DOM
という仕様を使って実現されています。この仕様は2021年4月現在Chromeでのみ実装されており、他のブラウザではPolyfillを使ってSSRを実現するようです。
Declarative Shadow DOM
についてはこちらの記事で詳しく解説されています。
まとめ
Litの登場でますますWeb Componentsの利用が広まっていく予感がします。
まずはPlaygroundでいろいろ触ってみましょう!
Discussion