Visual Studio Code v1.82 以降で `TypeError: Failed to parse URL from ...` が発生する場合の回避策

# 不具合の内容

Visual Studio Code v1.82 から Visual Studio Code を動作させている
Node.js のバージョンが 18 系にアップグレードされた。

これにより、拡張機能から `TypeError: Failed to parse URL from ...` という
エラーが発生する場合がある。
ほとんどの場合に、このエラーが発生した拡張機能は正常に動作しなくなる。

# 実際に遭遇したエラー

Ruby 拡張機能 (https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby)
を使っているプロジェクトで次のエラーが表示されていた。

```
TypeError: Failed to parse URL from <VSCODE_DIR>/extensions/rebornix.ruby-0.28.1/dist/server/tree-sitter.wasm
TypeError: Failed to parse URL from <VSCODE_DIR>/extensions/rebornix.ruby-0.28.1/dist/server/tree-sitter.wasm

stacktrace ...

abort(TypeError: Failed to parse URL from <VSCODE_DIR>/extensions/rebornix.ruby-0.28.1/dist/server/tree-sitter.wasm). Build with -s ASSERTIONS=1 for more info.
(Use `node --trace-uncaught ...` to show where the exception was thrown)

Node.js v18.15.0
```

> **Tip**
> Ruby 拡張機能 `rebornix.Ruby` は 2022年の4月で開発が止まっている。
> Ruby LSP 拡張機能 (https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp)
> への移行が推奨されているが、この拡張機能に必要な `ruby-lsp` gem は Ruby 3.0 を要求している。
> 残念ながらメンテナンスしていたプロジェクトは Ruby 2 系で動いている上、
> Ruby のアップグレードも難しかった。

## 原因の調査

エラーメッセージから `tree-sitter.wasm` という WASM ファイルがエラー原因に関わっている。

tree-sitter (https://tree-sitter.github.io/tree-sitter/) は構文解析ツールで、
複数の実行環境をサポートしている。
サポートされる実行方法の1つに Javascript 向けの WASM 形式ファイルを配布しているので、
このファイルが原因のようだと目星をつけた。

tree-sitter は Rust で書かれていたが、調べてみると WASM は
Emscripten (https://emscripten.org/) というツールで生成されているようだった。

このリポジトリで `Failed to parse URL from` で検索すると、それっぽい issue が見つかった。

- [Node.js implemented WebAssembly.instantiateStreaming in v18.1.0 #16913](https://github.com/emscripten-core/emscripten/issues/16913)

Emscripten の詳細な実装は調べていないが、 Emscripten が生成した WASM はいくつかの実行環境を
サポートすることができる。

実行環境の判別は WASM の実行時に行われるようだが、
その判定で `fetch` が定義されていなければ Node.js 環境というような分岐になっていた。

しかし、 Node.js v18 で `fetch` API が追加されたことにより実行環境の判定が意図しない動作になり
Node.js が追加した `fetch` が呼び出しされてエラーが発生するようになった様子。

この不具合は Emscripten v3.1.13 で修正されているので、それ以降のバージョンの
Emscripten で生成された WASM であれば、今回のエラーは発生しないと思われる。

> **Tip**
> ちなみに、 WEB ブラウザの `fetch` であれば `fetch("foo.wasm")` と書くと
> 現在の URL からの相対パスにある `foo.wasm` へのリクエストと解釈されるのでエラーにならない。
> Emscripten はこの仕様を利用して、現在のサイトに存在する `.wasm` ファイルをダウンロードする
> 目的で `fetch` を呼び出ししていると思われる。
>
> しかし、 Node.js の `fetch` や `node-fetch` といったライブラリの `fetch` は
> `foo("foo.wasm")` はエラーになる。
> これは `fetch` の仕様に `file://` スキームのサポートが明記されていないこと、
> `fetch` でローカルファイルへのアクセスを行うことはセキュリティホールになりかねないことから
> 意図的な実装の様子。
>
> - [file:// URLs · Issue \#75 · node\-fetch/node\-fetch · GitHub](https://github.com/node-fetch/node-fetch/issues/75)
> - [ability to fetch \`file://\` URLs in node \`fetch\`\. · Issue \#48554 · nodejs/node · GitHub](https://github.com/nodejs/node/issues/48554)
>
> よって、 Node.js の `fetch` は `fetch("foo.wasm")` のようなスキーム指定が無い
> URI は受け付けないので `Failed to parse URL from ...` というエラーを発生させるように
> なっている。
> さらに Node.js で `file://` スキームをつけて `fetch("file://foo.wasm")` としても、
> `TypeError: fetch failed`, `cause: Error: not implemented... yet...` が発生して動作しない。
> よって Node.js の `fetch` でローカルファイルを取得する事は出来ない。

## 問題の回避策

Node.js v18 で `fetch` が追加されたことが主な原因なので、 Node.js が定義する `fetch` が
なくなればいいということになる。

`node` の実行時に `--no-experimental-fetch` オプションを指定すると、
Node.js は `fetch` API を定義しなくなるので、この方法で回避できる。

ただし、 Visual Studio Code の実行時に Node.js のオプションを指定する方法は
提供されていない様子。

Node.js 事態が提供している `NODE_OPTIONS` 環境変数をセットすることで
Visual Studio Code を実行する Node.js 環境で `fetch` API の定義を回避できる。

ただし、 Visual Studio Code が Node.js v18 で実行されるようになったので、
拡張機能などでも `fetch` API を呼び出しするようになる可能性がある。

`fetch` API を使用する拡張機能が存在している場合に、
`NODE_OPTIONS=--no-experimental-fetch` をセットしていると
他のバグを誘発する恐れがある点には注意が必要。

この回避方法を使うのであれば Visual Studio Code の Remote Development 機能を利用して
Docker コンテナといった限定的な環境で開発をするといった環境分離措置を取った方がいいと思われる。

`.devcontainer/devcontainer.json` で Docker コンテナで `NODE_OPTIONS` をセットする例:

```json:.devcontainer/devcontainer.json
    "containerEnv": {
        "NODE_OPTIONS": "${containerEnv:NODE_OPTIONS} --no-experimental-fetch"
    }
```

`docker-compose.yml` で Docker コンテナで `NODE_OPTIONS` をセットする例:

```yaml:docker-compose.yml
services:
  foo-service:
    environment:
      NODE_OPTIONS: "--no-experimental-fetch"
```

## 余談

Emscripten で不具合されたバージョン v3.1.13 がリリースされたのが2022年6月のようなので、
この不具合に遭遇するのは該当のバージョン以降で `.wasm` を生成していない拡張機能に限定される。

Visual Studio Code v1.82 のリリースが 2023年8月なので、
このエラーが発生する拡張機能は1年以上は新しいリリースをしていないと考えられる。

拡張機能のメンテナンスが続いていればいいが、メンテナンスが止まっているようなら
代替の拡張機能に移行したほうがいいかもしれない。