「他所の庭」にUIを植え付ける苦労
「この予約フォーム、別チームが運用しているキャンペーンサイトにもそのまま埋め込みたいんだけど」。開発現場に長く身を置いていると、数年に一度は必ずこの手の相談が舞い込む。要件としては極めてシンプルに聞こえるが、我々エンジニアの脳裏には即座にいくつもの厄介な課題がよぎるはずだ。
相手のサイトでReactは動いているのか? CSSがバッティングしてレイアウトが崩れないか? 画像やフォントの参照パスはどうなる? こうした「環境の不確実性」から逃れるため、我々は長らくiframeという古き良き、しかしリサイズや通信の面で扱いにくい技術に頼るか、あるいはWeb Componentsの複雑なビルド設定に何日も溶かすかという二択を迫られてきた。
隔離と梱包を同時にこなすアプローチ
そんな「UIの出張」につきまとう面倒事を、極めて洗練された手法で片付けようとするのが「isolet」だ。特定のフレームワークに依存せず、任意のコンポーネントを完全に隔離された一つの自己完結型ウィジェットとしてパッケージングする。
使い方は拍子抜けするほど簡単だ。コアとなるcreateIsolet関数に、マウント用の関数とCSSを渡すだけでいい。
import { createIsolet } from "isolet-js";
import { react } from "isolet-js/react";
import { Hello } from "./Hello";
const widget = createIsolet({
name: "hello-widget",
mount: react(Hello),
css: "h1 { color: tomato; }",
});
widget.mount(document.body, { name: "World" });
たったこれだけで、コンポーネントはShadow DOMの中にレンダリングされる。親サイトの巨大なリセットCSSに汚染されることもなければ、こちらのスタイルが外に漏れ出す心配もない。Reactだけでなく、SolidやSvelte、あるいは素のJavaScriptであっても、薄いアダプターを噛ませるだけで同じようにウィジェット化できるのが美しい。
| アプローチ | スタイルの隔離 | アセットの取り回し | 導入の手軽さ |
|---|---|---|---|
| iframe | 完璧 | ホスティング環境が別途必要 | △ (高さ調整や親との通信が泥臭い) |
| 素のWeb Components | Shadow DOM | 自前で複雑なバンドル設定が必要 | × (学習コストとビルド構築の壁) |
| isolet | Shadow DOM | 全てインライン化(単一ファイル) | ◎ (専用CLIで一発ビルド) |
泥臭いアセット管理からの解放
だが、私がこのライブラリの思想に真の価値を見出したのは、ランタイムのAPIよりもむしろ同封されているCLIの振る舞いだ。ウィジェットの配布において最大の壁となるのは、実はJavaScriptのロジックではなく「画像やフォントなどの静的アセットをどう解決するか」である。
isoletのCLIはビルド時、CSS内のurl()参照や静的アセットのインポートを検知すると、それらをすべてData URIとして変換し、最終的なJavaScriptファイルの中に力技でインライン化してしまう。これによって出力されるのは、正真正銘の「ポータブルな単一ファイル」だ。配布先のドメインでCORSエラーに悩まされたり、画像がリンク切れのアイコンになったりする、あの忌まわしいウィジェットあるあるをビルドパイプラインの層で完全に封じ込めている。
出力形式もIIFE(スクリプトタグ用)、ESM、CommonJSと網羅されており、相手の環境がWordPressのベタ書きHTMLだろうが、最新のNext.jsアプリケーションだろうが、単にファイルを読み込ませるだけでポンとUIが出現する。
かつてiframeで切り抜けてきた数々の妥協と、コンポーネントを配るためだけに書いてきた複雑怪奇なWebpackのオレオレ設定に、ようやく現代的な終止符を打てるかもしれない。
参考リポジトリ: millionco/isolet
Photo by Ferenc Almasi on Unsplash