LitはWebコンポーネントを開発するのに使用できるモダンなフロントエンドフレームワークです。
Webコンポーネント(Web Components | MDN)は、簡単に言えば以下のような感じで使用できる再利用可能なHTMLタグという感じです。
<html>
<script type="module" src="sample-web-components.js"></script>
<sample-web-components param="なんらかのパラメータ"></sample-web-components>
</html>
私は普段フロントエンド開発でAngularやVue等のフレームワークを使っているので、それらに対応したオープンソースの部品を見つけてきたり、自分で実装したりすれば事足りていました。そのため、特にWebコンポーネントを使う理由はあまり無いと思っていました。
しかし、ある案件でWebコンポーネントが良い感じにハマる構成があったので、本記事で紹介したいと思います。
アプリの概要
このアプリはいわゆる典型的なSpring Web MVCのマルチページアプリケーションで、JavaのロジックやボイラープレートコードとHTMLテンプレートで構成されていました。
このアプリの機能改修である画面を追加することになり、画面の要件が「サーバーから取得したデータをリッチなUIで色々と編集し、保存ボタンでサーバーに送信して保存する」という物でした。
「リッチなUI」にはCanvasでいい感じにデータを表示したり、Canvas上のマウス操作でデータを編集するといった機能が含まれていたため、JavaScriptのロジックが必要でした。
しかし、Spring自体はフロントエンド側の実装とは関係が無いため、JavaScriptを自分で書く必要があります。
昔はjQuery等のライブラリで頑張って実装したものですが、最近はVSCode/TypeScript/ESLintといった開発環境に慣れてしまったためもはや昔のような生JavaScriptを書きたくありませんでした。
そこで考えたのが、以下のような方式です。
- 編集対象のデータはJSONで表現する。
- 画面の表示時は、DBから取り出したデータをJSON化してViewに埋め込む。
-
<script>
タグ内にJavaScriptオブジェクトとしてグローバル定数にする。
-
- リッチなUI部分はWebコンポーネントで実装する。
- 実装したWebコンポーネントはHTMLテンプレート内で読み込んで使用する。
- フロントエンドからサーバーへの送信は通常のフォーム送信(formタグ)で行う。
- Webコンポーネントでは編集後のデータをJSON化→文字列化して
<input type="hidden">
要素のvalueに設定する。 - その状態でformをsubmitすることで、編集後のデータを文字列としてサーバーに送信する。
- Webコンポーネントでは編集後のデータをJSON化→文字列化して
- サーバーでは受信したJSONを適宜DBに格納する形に変換して保存する。
この方式には以下の利点があると思っています。
- リッチなUI部分をLitを使って実装できる(TypeScriptでコーディングできる!)
- バックエンド(Java)側はシンプルなデータ形式の変換ロジックのみで実装できる。
- 特殊な処理は必要なく、HTML内でWebコンポーネントのタグを書くだけで使用できる
Litコード
公式ドキュメントlit.devにはLit TypeScript starter projectが掲載されていますが、たまたま調べていたViteのテンプレートにLitの物があったため、こちらを使用します。
$ npm create vite@latest my-lit-component -- --template lit-ts
以下のようなディレクトリ構成が出力されます。
my-lit-component/
public/
vite.svg 生成されたサンプルページ内に表示される画像アセット(後で消す)
src/
assets/
lit.svg 生成されたサンプルページ内に表示される画像アセットその2(後で消す)
index.css index.htmlから読み込む用のCSS
my-element.ts Webコンポーネントのコード本体
vite-env.d.ts
.gitignore
index.html テスト用のメインHTML
package.json
tsconfig.json
以下のように実行することで、テスト用のメインHTML内にWebコンポーネントが埋め込まれた状態で起動します。
$ npm install
$ npm run dev
サーバーからの値を表示
サーバーからの値をLitコンポーネント側から参照する部分の概略です。
まず、テスト用のメインHTMLをSpring側のテンプレートHTMLを想定した物に書き換えます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Lit + TS</title>
<link rel="stylesheet" href="./src/index.css" />
<!-- 追加: サーバーでデータを埋め込む想定のscriptタグ -->
<script type="module">
window.myAppData = {
"data": [
{"name": "John", "age": 20},
{"name": "Charlie", "age": 35}
]
};
</script>
<script type="module" src="/src/my-element.ts"></script>
</head>
<body>
<my-element></my-element>
</body>
</html>
追加:
とある部分が追加した箇所です。
コンポーネント側(my-element.ts)からは以下のように参照できます。
data: any = (window as any).myAppData; // TODO 型定義
render() {
const dataElements = this.data.data.map(
(item: any) => html`<div>name = ${item.name}, age = ${item.age}</div>`
);
return html` <div>${dataElements}</div> `;
}
(型定義の部分は適宜d.tsで定義することが可能ですが今回は省略しています)
サーバーに値を送信
サーバーに値を送信する箇所は以下のような感じになります。
HTML側はformタグと、デバッグ用にformで送信されるはずの値を表示できるようにします。
...
<body>
<!-- 追加: ユーザー操作で送信するフォーム -->
<section class="debug-form">
<form id="debugForm" method="post">
<input id="dataOutput" name="data" type="hidden">
<input type="submit" value="送信">
</form>
</section>
<!-- 追加: デバッグ用に送信されたフォームのJSON値を表示する処理 -->
<div id="debugOutput" class="debug-output"></div>
<script type="module">
function debugSubmit(e) {
e.preventDefault();
// <input id="dataOutput">から値を取得して<div id="debugOutput">内に表示する
const jsonValue = document.getElementById("dataOutput").value
document.getElementById("debugOutput").textContent = jsonValue;
}
document.getElementById("debugForm").addEventListener("submit", debugSubmit);
</script>
<my-element></my-element>
</body>
リッチなUI部分は省きますが、コンポーネント側(my-element.ts)からformに値をセットする部分は以下のようなシンプルなコードになります。
private _setOutputValue() {
const jsonString = JSON.stringify(this.data); // this.dataはリッチなUIにより書き換え済み
const inputElement = document.getElementById("dataOutput") as HTMLInputElement;
inputElement.value = jsonString;
}
あとは、Litのコンポーネントで好きなだけリッチなUIのロジックを作るだけです。
まとめ
本記事では、LitコンポーネントをSpring Web MVCのマルチページアプリケーションに組み込んだらいい感じにできた話を紹介しました。
今後も、同様の構成ではLitコンポーネントの使用を検討したいと思います。