皆様どうも、こんにちは!
こまりのフロントエンドとかのエンジニア、桑木です。
先に断っておくと、この方法でデバッガを起動した場合でも、HMRした時にブレークポイントやステップ実行などのデバッガの機能に不具合が出ることがあります。
HMR時にsource mapまで更新しないことが原因のようです。webpackであればinline source mapで対処できる、というような情報もありますが特に試していません。
とりあえず、ブラウザ側でページ全体をリロードして対処していますが、なにか情報があれば教えてください。
この記事の内容
クライアントのウェブアプリはlaunch.jsonを設定しなくてもデバッグできる
そもそも、最近のブラウザには優秀なJavaScriptのデバッガが搭載されているので、VSCodeのデバッガを利用しなくても開発には困りません。
webpackなどのモジュールバンドラを利用している場合も、ビルドオプションでmapファイルを生成しておけば、ブラウザのデバッガが利用できます。
手間があるとしても、ターミナルからnpm start
とかでwatchモードでバンドラを起動して、ブラウザでローカルホストのページを開くだけです。
とはいえ、せっかくだからそれも自動化するぜ
実は先日、モジュールバンドラをwebpackからParcelに乗り換えました。webpackに比べればできることに制限があるとは言われるものの、複雑な設定が不要で明快に使えるのは精神的にとても良いですね。
Parcelに乗り換えてすっきりしたついでに、長年(半年くらい)放置していたクライアント側のデバッグもlaunch.jsonに設定しました。
ここから本番
とりあえず次のようなディレクトリ構造だとします。
- プロジェクトルート
- .vscode
- launch.json
- tasks.json
- app
- src
- public
- dist
- ︙
- server
- bin
- src
- ︙
- .vscode
プロジェクトルートの下に、app
とserver
の2つのnpmパッケージディレクトリがあります。
Chromeと連携させるため、VSCodeには拡張機能の"Debugger for Chrome"をインストールしておきます。この拡張機能を入れることで、Chromeで動作しているJavaScriptにアタッチすることができるようになります。
そして、launch.jsonとtasks.jsonはこちら。
// launch.json
{
// IntelliSense を使用して利用可能な属性を学べます。
// 既存の属性の説明をホバーして表示します。
// 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "server",
"cwd": "${workspaceFolder}/server",
"program": "${workspaceFolder}/server/bin/www",
"envFile": "${workspaceFolder}/server/.env",
"env": {
"NODE_ENV": "development"
},
"outputCapture": "std"
},
{
"type": "chrome",
"request": "launch",
"name": "app",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/app/dist",
"preLaunchTask": "app start",
"postDebugTask": "app stop"
}
],
"compounds": [
{
"name": "all",
"configurations": ["server", "app"]
}
]
}
// tasks.json
{
// tasks.json 形式の詳細についての資料は、
// https://go.microsoft.com/fwlink/?LinkId=733558 をご覧ください
"version": "2.0.0",
"tasks": [
{
"label": "app start",
"type": "npm",
"script": "start",
"path": "app/",
"isBackground": true,
"problemMatcher": {
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "^.*",
"endsPattern": "^.*Built in.*",
}
}
},
{
"label": "app stop",
"type": "shell",
"command": "echo ${input:terminate}"
}
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "app start"
}
]
}
app
ではモジュールバンドラとしてParcelを使用していて、npm scriptsのstart
にはparcel watch public/index.html
が指定されています。これでnpm start
を実行すればParcelがwatchモードで起動します。
出力ディレクトリを特に指定していないので、この場合はapp/dist
にビルド結果が出力されます。
server
はexpress-generatorで生成したExpressアプリで、server/bin/www
がエントリーポイントになっていて、起動後はhttp://localhost:3000
で接続を待ち受けます。また、express.static
でapp/dist
を指定しています。
設定解説
launch.json
preLaunchTask
やpostDebugTask
を指定しておけば、それぞれデバッガの起動前と終了後に指定したtaskを実行させることができます。今回は、preLaunchTask
にParcelをwatchモードで起動させるタスク、postDebugTask
にそのタスクを強制終了させるタスクを指定しています。
タスク自体の設定はtasks.jsonに記述して、label名をこのプロパティに指定して参照します。(label名による参照でなく、直接タスクの内容をこのプロパティに記述することもできますが、すべてのタスク機能には対応していないので専用のtasks.jsonを使用したほうが良いでしょう)
デバッガ自体は、Chromeの新しいウィンドウでローカルホストのページを開く設定にしています。
また、compounds
を設定することで複数デバッガの同時起動をさせることができます。今回はクライアントのウェブアプリだけでなく、Expressサーバも同時に起動するように設定しました。
launch.jsonの詳しいようであまり詳しくない説明は、VSCode公式サイトの"Debugging in Visual Studio Code"のページ書かれているので参照してください。
tasks.json
app start
app/
でnpm run start
するように設定したタスクです。前述のpreLaunchTask
から参照しています。
preLaunchTask
は名前の通りデバッガの起動前に実行するタスクですが、そのタスクが終了するまでデバッガは起動されません。これはコンパイルなどで実行ファイルを生成してから起動させるための挙動です。
しかしwatchモードの場合、コンパイルが完了してもプロセスは終わらないのでデバッガが起動しないことになります。そこでisBackground
にtrue
を指定してタスクのバックグラウンド動作を有効にします。
ただし、バックグラウンドへ移行させるにはproblemMatcher
も指定する必要があります。このプロパティはコンパイラが出力するエラー情報をVSCodeで取得するためのものですが、今回はバックグラウンドへの移行に必要なproblemMatcher.background.endsPattern
だけ適切に設定します。ここに指定した正規表現にマッチすれば、タスクがバックグラウンドへ移行します。
その他のプロパティも指定しておかないとproblemMatcher
として認識しないので、問題の起こらない範囲で適当に指定しておきます。
tasks.jsonの公式の説明は"Tasks in Visual Studio Code"を参照してください。
app stop
前項で起動しっぱなしのタスクを強制終了させるタスクです。これを前述のpostDebugTask
から参照することで、デバッガの終了と連動できます。
しかし、タスク自身の機能で他のタスクを終了させるような直接的な機能はありません。
そこで、VSCodeの内部コマンドworkbench.action.tasks.terminate
をcommand
タイプのinput変数を参照することで実行します。
内部コマンドを実行する変数としてcommand変数もありますが、command変数には引数を指定できません。workbench.action.tasks.terminate
は引数に終了させるタスク名を指定する必要があるので、引数を指定できるinput変数のcommand
タイプを利用します。
このinput変数は、タスク実行時にユーザーの入力や選択を求めることもできる特殊な変数です。詳しい説明は公式サイトの"Visual Studio Code Variables Reference"を参照してください。
まぁまぁ実用的にはなったかな
以上でモジュールバンドラとデバッガが連動するようになったと思います。F5
一発でサーバとクライアントが起動するのは楽ですね。
VSCodeは機能が盛り沢山なので、皆さんもドキュメントを見て便利な機能を見つけてみてください。