Astro を使って Lit を SSG する

こんにちは、アドベントカレンダー4日目を担当する吉川です。

個人的な興味で WebComponents にまつわる技術を日々触っています。

今回は Astro というツールで WebComponents の SSG を可能にする方法について紹介します。

対象読者として、WebComponents に少しでも触れたことがある方を想定しています。これからの Web の少し新しい技術に「こんなのもあるんだ」のように思っていただければ嬉しいです。

## Astro とは

Astro とは React や Vue など複数の UI ライブラリに対応した SSG ツールです。

.astro のような独自ファイルに JSX のようにマークアップして、ページをビルドすることができます。

ビルド時には JavaScript を可能な限り除いて結果に出力されます。これはクライアント側でのスクリプトの実行を最小限に抑えるためです。これによってページ読み込みについて高いパフォーマンスを実現します。

ビルドして出力されたページは MPA（マルチページアプリケーション）として動作します。クライアント側では History API を用いたページルーティングは行わず、Astro によって生成されたそれぞれの HTML ファイルを読み込みます。

今回は Astro を用いて WebComponents を使用した Web ページを生成してみようと思います。

Astro は v0.18 で Lit という WebComponents 用の UI ライブラリの SSR に対応したので、その実装方法についても紹介します。

## Lit とは

Lit はテンプレートタグを使って宣言的 に WebComponents の UI を構築できるようにするライブラリです。Google が今まで Polymer という名前で開発していましたが、名前が変わり Lit になりました。JavaScript の標準 API のみを使用して構築するので、Babel などでのコンパイルも不要でそのままほかのフレームワークやライブラリの中でも使用できます。Lit では現在 @lit-labs/ssr というパッケージ名でコンポーネントを SSR したりハイドレートするライブラリが開発されています。

## 宣言的 Shadow DOM

WebComponents を SSR するために現在 宣言的 Shadow DOM という仕様が提案されています。これは、 <template> 要素に新しく shadowroot という属性を追加することで、 Shadow DOM な HTML であることを宣言的に定義することができるものです。shadowroot 属性がなければ、 Shadow DOM は JavaScript に記述したコンポーネントの実行がされるまでは描画されないので初期描画の遅延の要因になります。

宣言的 Shadow DOM によって ブラウザの HTML の評価時点で Shadow DOM 内部の DOM の構造を伝えられるので、パフォーマンス改善につながるのではないかと期待されています。

今回は Astro と Lit を組み合わせて 宣言的 Shadow DOM のレンダリングをしてみます。

残念ながら現時点で 宣言的 Shadow DOM に対応しているブラウザは Chrome のみとなっています。

## Astro で Lit の SSG

まずは astro のプロジェクトを作成します。

astro プロジェクトのテンプレートは今回は Minimal を使用していきます。

```bash
$ npm init astro

Welcome to Astro! (create-astro v0.6.8)
If you encounter a problem, visit https://github.com/withastro/astro/issues to search or file a new issue.

> Prepare for liftoff.
> Gathering mission details...
✔ Which app template would you like to use? › Minimal
> Copying project files...
✔ Done!
```

### 必要パッケージのインストール

開発に必要なパッケージをインストールしていきます。

Lit を SSG するためのレンダラーを astro が用意しているので Lit 本体と共にインストールします。

```bash
$ npm i lit
$ npm i -D @astrojs/renderer-lit typescript
```

### レンダラーの設定

インストールした @astrojs/renderer-lit を astro のレンダラー設定に登録します。

```js:stro.config.mjs
// Full Astro Configuration API Documentation:
// https://docs.astro.build/reference/configuration-reference

// @type-check enabled!
// VSCode and other TypeScript-enabled text editors will provide auto-completion,
// helpful tooltips, and warnings if your exported object is invalid.
// You can disable this by removing "@ts-check" and `@type` comments below.

// @ts-check
export default /** @type {import('astro').AstroUserConfig} */ ({
  // Comment out "renderers: []" to enable Astro's default component support.
  renderers: ["@astrojs/renderer-lit"], // レンダラーを登録
});
```

## コンポーネントの作成

SSG する対象となる Lit コンポーネントを作成します。

今回は、src ディレクトリ配下に新しく components ディレクトリを作成し、そこにコンポーネントを作成しました。

通常の Lit コンポーネントの実装とは違い、 astro のビルド用にtagName 変数としてカスタムタグ名を export する必要があります。 

```jsx:src/components/my-element.ts
import { html, LitElement } from "lit";

export const tagName = "my-element"; // tagName としてカスタムタグ名を export、ビルド時に使用される。

class MyElement extends LitElement {
  render() {
    return html` <p>Hello world!</p> `;
  }
}

customElements.define(tagName, MyElement);
```

作成したコンポーネントはページで import して配置することでレンダリングされるようになります。

```jsx:src/pages/index.astro
---
import "../components/my-element.ts"
---

<html lang="ja">

<head>
	<meta charset="utf-8" />
	<link rel="icon" type="image/x-icon" href="/favicon.ico" />
	<meta name="viewport" content="width=device-width" />
	<title>Welcome to Astro</title>
</head>

<body>
	<my-element></my-element>
</body>

</html>
```

開発サーバを立ち上げて確認してみます。

```bash
$ npm run dev
```

Chrome の 「ページのソースを表示」から HTML を確認すると、宣言的 Shadow DOM として 生成されていることが確認できます。

```html
...
<my-element><template shadowroot="open"><!--lit-part U3BiJ93Yz8Y=--> <p>Hello world!</p> <!--/lit-part--></template></my-element>
```

このように、astro を使うことで簡単に WebComponents の SSG の開発をすることができます。気になる方はぜひ試してみてください。

今回作成したサンプルプロジェクトは[こちら](https://github.com/yutaroyoshikawa/astro-lit)でも確認いただくことができます。
[CodeSandbox上](https://codesandbox.io/s/reverent-lewin-0i00l)でも実際に動作を見てみることもできるのでぜひ。