Pluggable なブラウザアプリケーションの仕様を考える
npm を使わず、ブラウザ上のランタイム環境で任意のスクリプトをインポートし、それを実行出来る仕組み。プラグイン。エクステンション。 Chrome Extension や RPG ツクール MZ のプラグインがかなり近い。それらのプラグインを管理する側の実装についての仕様を考えたい。
プラグインの実例。
メニューバープラグイン
- メニューバー DOM 要素を埋め込む機能
- メニューを追加する API を他のプラグインに提供する機能
Babylon.js プラグイン
- canvas DOM 要素を埋め込む機能
- メニューバープラグインの API を使ってメニューバーにメニューを追加する機能
- canvas DOM 要素を使って Babylon.js のランタイムを動作する機能
- Babylon.js のランタイムオブジェクトを編集する機能
バンドルプラグイン
- ゲームランタイムをまとめて単体で実行可能なファイルにまとめる機能
同期プラグイン
- 各種プラグインやランタイムの状態をリモートと同期するサーバーを介した機能
安全(globalThis を汚染しない)に class
を評価する機能があれば作れそう?
プラグインは任意の npm package を読み込めるようにしたい(超難しそう)
Web Extensions は、 1 つの manifest.json ファイルから必要なファイルを読み込み、その場で実行する。このスタイルは良さそう。
任意のスクリプトはどう頑張っても汚染のリスクがある?サンドボックス化出来れば安全になるかも。
例えば最初のプラグインは console.log('Hello world');
を実行するプラグインのはず。完全に破壊的なプラグインになるので、そこは割り切ってもいいかも。
ServiceWorker ?など、ブラウザ側が持っている機能でスクリプトをサンドボックス化出来れば、その中で実行して結果を RPC すれば良い気がする。
WebWorker API がサンドボックス化したスクリプトを実行出来る可能性があるので調べてみる。
スクリプトの文字列を Blob を使って Object URL 化して、それを new Worker(blobUrl);
すれば任意の文字列をワーカーとして起動することが可能。
window.addEventListener("load", () => {
console.log("a");
document.body.addEventListener("drop", (e) => {
e.preventDefault();
if (e.dataTransfer?.items) {
[...e.dataTransfer.items].forEach((item) => {
if (item.kind === "file") {
const file = item.getAsFile();
console.log(file);
if (file) {
const worker = new Worker(URL.createObjectURL(file));
}
}
});
} else if (e.dataTransfer?.files) {
[...e.dataTransfer.files].forEach((file) => {
console.log(file);
});
}
});
document.body.addEventListener("dragover", (e) => {
e.preventDefault();
});
});
これでドラッグアンドドロップした JavaScript ファイルを Worker 内で実行することが出来た。
index.ts
を渡すと invalid type video/vnd.dlna.mpeg-tts
と mpeg のファイルタイプと認識されてしまう。
const worker = new Worker(URL.createObjectURL(file), {
credentials: "omit",
name: file.name,
type: "module",
});
console.log("Hello worker.js");
import("https://cdn.skypack.dev/lodash").then((lodash) => {
console.log("lodash", lodash);
});
module 形式にすることによって、 skypack から任意のパッケージを import 出来るようになった。
こっちを使ったほうが柔軟かも。
import { esm } from "https://esm.sh/build";
const text = await file.text();
const result = await esm(text);
console.log(result);
で TypeScript や jsx をトランスパイルして export された結果を取得出来る。
import { esm } from "https://esm.sh/build";
この記法が node.js だと多分まだサポートされていないので、 vscode 上でエラーが出る。ここから Developer Experience を上げていくために調べる。
とりあえず Deno を使えば良いらしい。
- エディタのコア部分(プラグインの管理)は vscode 上で行う。 deno でも node.js でも良い。
- プラグインの開発も vscode 上で行う。リロード機構がうまくできている方が良い。 TypeScript で書けると良い。 deno でも node.js でも良い。
- どちらも正しく型を解決できる必要がある。
一旦休憩