VSCode で WSL 上のシンボリックリンクを含むパスで NodeJS(TypeScript) をデバッグ

  • Fedora 31 on WSL1
  • NodeJS 14.4.0
  • VSCode 1.48.2

Remote WSL で VSCode を WSL 上で簡単に実行できるので、次のような Launch Configuration だけでリモートデバッグ出来ます。また、runtimeExecutable やら runtimeArgs やらで ts-node を使えば ts が直接実行できます。

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "restart": true,
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/src/app.ts",
            "runtimeExecutable": "ts-node",
            "runtimeArgs": ["-T"],
        }
    ]
}

ホスト機が Windows であることなどまったく気にする必要ありません。完。

シンボリックリンク内のワークスペースの問題

と思っていたのですが、実際にやってみたらダメでした。ワークスペースのパスにシンボリックリンクが含まれていてその中で VSCode を開いていると、ブレークポイントを設定しても Unbound breakpoint とか言われて止められません。

例えば、次のようなシンボリックリンクがあり、

ln -sfn /c/Users/oreore/devel /home/oreore/devel

ワークスペースのパスが /home/oreore/devel/path/to/project だとすると、それをそのまま開いてもダメでした。/c/Users/oreore/devel/path/to/project を開く必要があります。

諸事情で普段いつもパスにシンボリックリンクが含まれている場所で作業しているので、これだとかなり都合が悪いです。

下記の Issue によると runtimeArgs--preserve-symlinks--preserve-symlinks-main を指定すれば良い、とのことですが、ダメでした。

VSCode の設定で debug.javascript.usePreview: false を指定すれば大丈夫でした。

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "restart": true,
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/dist/app.js",
            "runtimeArgs": [
                "--preserve-symlinks",
                "--preserve-symlinks-main"
            ]
        }
    ]
}

ただ node コマンドのオプションを指定する必要があるため ts-node は使えません。runtimeArgs-r ts-node/register/transpile-only を付けるようにしてもダメでした。

outFiles でビルドされた js ファイルを指定すれば実行できました。

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "restart": true,
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/src/app.ts",
            "outFiles": ["${workspaceFolder}/dist/**/*.js"],
            "runtimeArgs": [
                "--preserve-symlinks",
                "--preserve-symlinks-main"
            ]
        }
    ]
}

ただこれだとあくまでもビルドされた js が実行されるだけであって ts ファイルが直接実行されているわけではありません。

次のようにコマンドを叩けば node コマンドからでも直接 ts ファイルが実行できているんですけど・・なんででしょうね?

node --preserve-symlinks --preserve-symlinks-main -r ts-node/register/transpile-only src/app.ts

VSCode の NodeJS デバッガー拡張

複数あるもよう。

ms-vscode.js-debug が最も新しいデバッガーで debug.javascript.usePreview: true のときに使用されます(デフォルト true なので明示的に無効にしない限りこれが使用される)。

ms-vscode.node-debugms-vscode.node-debug2debug.javascript.usePreview: false のときに使用されます。どちらが使用されるかは NodeJS のバージョンで自動的に決定されます。Launch Configuration で指定することもできます。

また、Launch Configuration の typepwa-node を指定すると debug.javascript.usePreview には依らず ms-vscode.js-debug 使用されるようです。

なお、この pwa は プログレッシブウェブアプリケーション のことではないようです。

node のコマンドの開始方法も異なります。ms-vscode.js-debug の場合は環境変数 NODE_OPTIONS にデバッガに必要なオプションが指定されます。なので ts-node などを runtimeExecutable に指定することができます。一方で ms-vscode.node-debugms-vscode.node-debug2 ではコマンドラインオプションに --debug-brk--inspect-brk が直接追加されます。

前述のシンボリックリンク問題が --preserve-symlinks--preserve-symlinks-main で解決するというのは、リンクされている PR が ms-vscode.node-debug2 のものなので、ms-vscode.js-debug だと効果ないようです。

また、ms-vscode.node-debug2 だと program で指定されたファイルが js かどうかを判定して、js ではない場合はソースマップから js ファイルを特定してそちらを実行するようになっています。

拡張子が .js .es6 .jsx .mjs のいずれかだと js だと判定されるようです。

Launch Configuration で __debuggablePatterns*.ts を指定すれば ts ファイルを直接実行させられる?

と思ったんですがダメでした。

remoteRoot で実際のパスを指定

debug.javascript.usePreview: true のままでも、remoteRoot でシンボリックリンクを解決した実際のパスを指定すれば大丈夫でした。

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "restart": true,
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/src/app.ts",
            "runtimeExecutable": "ts-node",
            "runtimeArgs": ["-T"],
            "localRoot": "${workspaceRoot}",
            "remoteRoot": "/c/Users/oreore/devel/path/to/project",
        }
    ]
}

remoteRoot にパスをベタ書きする必要があるのがちょっとイケていないですね。

VSCode を Windows で実行して WSL の node プロセスにアタッチ

VSCode は Windows 上で実行しつつ、WSL 内で node --inspect dist/app.js のように実行し、下記のような Launch Configuration でインスペクタのポートにアタッチしても良さそうです。

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "attach",
            "restart": true,
            "name": "Attach to Remote",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "address": "127.0.0.1",
            "port": 9229,
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "/c/Users/oreore/devel/path/to/project",
        },
    ]
}

ただこの場合は node コマンドを使う必要があるので ts ファイルではなく js ファイルを実行する必要があります。 あるいは次のように -r ts-node/register/transpile-only を指定すれば node コマンドを使いつつ ts ファイルが直接実行できます。ただ、この場合はプロジェクトの node_modulests-nodetypescript が必要です。

node --inspect -r ts-node/register/transpile-only src/app.ts

あるいは NODE_OPTIONS 環境変数で --inspect オプションを指定しても OK です。この方法なら ts-nodetypescript はグローバルにあれば十分です。

env NODE_OPTIONS=--inspect ts-node -T src/app.ts

さいごに

シンボリックリンクを含むパスの中で作業するという変なことをやっていなければハマることもなくもっと簡単だったのかも。。。