🦅

?vscode-coi=on を使わないで WebContainer を利用できる拡張機能を作ったけど、デフォルトが変わりそうで少し涙目な話

2023/06/12に公開

vscode.dev でもターミナルを使いたくなったので、WebContainer を利用する拡張機能を作ろうと思ったのが 4 月の末頃。

WebView を利用すれば対応できるかと考えていたが、WebContainer は Cross Origin isolation(coi) を必要とするのが難点。

  • vscode.dev のデフォルトでは coi が有効になっていない
  • クエリーパラメーターに ?vscode-coi=on を指定すると coi になるが、多くの場合で外部の画像などが表示できなくなる

外部の画像が表示されないのでは困りそうなので試行錯誤してみたところ、回避する方法がわかってきたので拡張機能として実装してみた。

図 1 こういう感じで利用できている

vscode.dev で拡張機能を利用し、vite サーバーを動かしているスクリーンショット

そして、当初の予定ではマーケットプレイスに publish した後、「いやー、苦労したっすよ(ドヤ顔)」って記事を書くつもりでいたら、Run WebAssemblies in VS Code for the Web によるとデフォルトが ?vscode-coi=on へ変わるのかもという話。

?vscode-coi=on を使わないで WebContainer を利用する方法

今回は Nodebox を利用することにより coi を有効化した外部タブを作りました。

Nodebox もブラウザー上で Node.js 的なランタイムを提供するものですが、こちらは coi を必要としません[1]

  1. WebView で Nodebox のランタイムを動かす
  2. Nodebox のランタイムで ViteWebSocket のサーバーを動かす
    • Vite サーバーは coi を有効にしておき WebContainer ランタイムを動かすページを配信する
  3. 外部タブを開き Vite サーバーへ接続し WebContainer のランタイムを動かす
  4. 外部タブと WebView は WebSocket と Nodebox の API(stdio)経由で通信する

このようにして coi が有効化された外部タブを作ることにより、coi 無しの vscode.devWebContainer が通信できる状態になります。 (実際には postMessage とのすり合わせとかいろいろあるのですがその辺は割愛します)

使ってみる

上記を実装したのが Start WebContaner 拡張機能となります。

リポジトリを開いてコマンドパレットから Start WebContainer: Start を実行すると外部タブが開き WebContainer が開始されます。

Workspace のファイルは WebContaner 側へロードされているので、あとは、Jsh 用のターミナルで普通に npm ci してからファイルを編集するような感じです。

図 2-1 WebContaine を開始し npm ci を実行

Start WebContainer 拡張機能でコンテナを開始し、Jsh 用ターミナルで npm ci したスクリーンショット

図 2-2 エディターでの編集は WebContainer 側へ反映される

エディターで編集した内容が、WebContainer が動かしている開発サーバーへ反映されているスクリーンショット

リポジトリを用意するのは面倒という場合、ローカルのフォルダーも利用できます。空のフォルダーを開いた後に Start WebContainer: Start でターミナルを開き、npm create next-app@latest などでプロジェクトを作成するといった感じです。

図 2-3 空のフォルダーを開き、npm create を実行

空のフォルダーを開き、`nom create` を実行しているスクリーンショット

作ったプロジェクトを編集する場合は、コマンドパレットから Start WebContainer: Pick up all files from a container を実行します。これで各ファイルが Workspace に取り込まれます。

図 2-4 作成したファイルを取り込み、エディターで開く

WebContainer 上で作成したプロジェクトを取り込み、ファイルをエディターで開いているスクリーンショット

と、ここまでは良い感じですが、いくつかやりにくいところもあります。

  • NodeboxWebContainer のランタイムを展開するので重い

  • バックグラウンドでのファイル同期がない

  • NPM スクリプトやタスクを UI から利用できない

  • Platform 別のバインディングが必要な場合は動かないこともある[2]

  • node_modules が同期されてないなどから定義の参照などができない

  • 時々ターミナルが反応しなくなってしてしまう

こんな感じで少し扱いにくいところもありますが、気楽にファイルを編集しながらターミナルで npm も使えるのでわりと気に入っています。

Insiders 版のデフォルトは on になっている

今回作った拡張機能はわりと気に入ったので、未実装な機能の補完や安定化を進めようと思っていました。ですが README を記述するために vscode-coi 関連のことを検索していると vscode.dev で coi がデフォルトになりそうな記事がヒットしました。

Run WebAssemblies in VS Code for the Web によると insiders.vscode.dev では既にデフォルトが ?vscode-coi=on に変更されたようです[3](wasm-wasi のために変更したのか不明ですが、他に記述が見つからないのでここから引用しています)。

VS Code's WASI implementation

The only difficulty with this approach is that SharedArrayBuffer and Atomics require the site to be cross-origin isolated, which, because CORS is very viral, can be an endeavor by itself. This is why it is currently only enabled by default on the Insiders version insiders.vscode.dev and must be enabled using the query parameter ?vscode-coi=on on vscode.dev

実際に試してみてもそのような挙動になっています。

下記の検証は Cross-Browser support with Cross-Origin isolation で利用されている画像を用いて行っています。

図 3-1 https://insiders.vscode.dev/ では外部リソースに cors corp などの対応が必要

insider.vscode.dev では通常の外部画像が表示されない状態のスクリーンショット

図 3-2 https://insiders.vscode.dev/?vscode-coi=off で off にすると普通に表示される

`?vscode-coi=off` を指定して開いた insider.vscode.dev では画像が表示されているスクリーンショット

そして、Start WebContainer 拡張機能では従来との互換性を優先して Nodebox を利用していますが、Nodebox はランタイムを iframe 内に展開しています。このとき iframeCross-Origin-Embedder-Policy が設定されていないリソースを指しているので、coi が有効になっていると逆に動作しません。

図 3-3 Nodebox のリソースは coi では読み込めない

デベロッパーツールで Cross-Origin-Embedder-Policy に関する警告が表示されているスクリーンショット

実際のところ、vscode.dev でも on になるのかはわかりませんが、on になれば Start WebContainer 拡張機能はおしまいといった感じになります [4]

その他

coi には直接関係ないのですが、今回の拡張機能を作るときに作った Rollup のプラグインについても少し。

Nodebox 内にファイルを配置するには、ファイルツリーをオブジェクトとして記述し nodebox.fs.init API へわたすことになります。

リスト 4-1 nodebox.fs.init のサンプル(ドキュメントより引用)

await nodebox.fs.init({
  'index.js': `
import { greet } from './greet'
greet('Hello world')
  `,

  'greet.js': `
export function greet(message) {
  console.log(message)
}
  `,
});

これは面倒そうだったので Rollup のプラグインを作って外部のファイルツリーを差し込めるようしました。

テキストファイルだけでなく画像などのバイナリーファイルも扱えたりします。Nodebox でちょっと込み入ったコードを扱うとき、よければ試してみてください。

おわりに

デフォルトの設定を尊重して拡張機能を作ったら、デフォルト設定が変わって動かなくなるというピタゴラ的展開で少し涙目になったので、次に作るときは coi が on のことも想定していこうかと思っています。とほほ。

とか言いながらも、vscode.dev でも WASIX は気にしているぽいので、内心では wasm-wasi 用拡張機能を試してみたくてウキウキなんですけどね。

What comes next?

脚注
  1. だったら Nodebox 使えばいいんじゃね?となりそうですが、Nodebox は Node.js との互換性が低いことと、シェル的なものがないので今回の用途には微妙に合致しませんでした。 ↩︎

  2. swc は binding がないのでエラーになりました。Next.js では swc を使っているようですが、こちらは専用(?)の wasm バインディングがあります。この辺は StackBlitz の WebContainer による IDE 環境でも同じようになりそうですが、何かノウハウ的なものがあるのでしょうか? ↩︎

  3. 最初に試したときは Insiders 版でも off だったと思うのですが、きちんとメモしていなかったので少しあやふやです。4 月末より前から on だったかもしれません。 ↩︎

  4. Nodebox が coi 対応するかもしれません。しかし、coi がデフォルトになったら WebView で直接 WebContainer を扱えるのはずなので、どちらにしても Nodebox なしで作り直した方がシンプルでよいのかなと。 ↩︎

GitHubで編集を提案

Discussion