案件としても OSS 成果物としても、JavaScript を利用するシチュエーションは増え続けています。まだまだ枯れた言語とは言い難い状況で、使われるバージョンも ES5 から ES7 まで進化を続け、新しい文字列リテラルや async/await のような「イマドキの JavaScript の書き方」を紹介する記事は多い中、デバッグはこうあるべきという情報は比較的少ないように思います。
[adinserter block="1"]
本記事の前編、中編では、システム開発に於けるデバッガ、ロガーの大切さと、他の言語・フレームワークと比べた際の JavaScript 開発環境に於けるビハインドについて説明し、実際に理想的なロガーを利用する為の設定方法を紹介してきました。
- イマドキの JavaScript 開発で使える、リモートデバッグとロガーの Tips(2018年版-前編)
- イマドキの JavaScript 開発で使える、リモートデバッグとロガーの Tips(2018年版-中編)
本記事では JavaScript 開発時のデバッガー利用のための具体的な設定方法を紹介します。
目次
理想の世界
まずは前編で掲げたゴールの再掲です。
対象システム
以下の3つのシナリオをカバーすることにします。
- シナリオA
- node.js (Express)
- シナリオB
- node.js (Express) + webpack/babel によるクライアントビルド
- シナリオC
- next.js on Express
やりたいこと
- SourceMap の利用
- ブラウザの開発者ツールでエラーを追う場合、トランスパイル前のソースの行数が分かる
- リモートデバッグ
- Microsoft Visual Studio Code で編集中のソースに対してブレークポイントを仕掛けて止めることができる
- node.js 用コードでも、クライアント用コードでも同様に VSCode 上でデバッグ可能
[adinserter block="1"]
Let's Try! ― シナリオA
シナリオAは、node.js (Express) 環境向けのロガー設定です。
必要なことは以下 2 つです。
- 何のプロセスをデバッグするかを決める
- VSCode の設定
npm 経由のプロセスをデバッグ
node.js のプロセスは、 node
コマンドに --inspect
あるいは --inspect-brk
を渡すことでリモートデバッグに必要な inspector の利用が可能になります。node
コマンドをそのまま叩くことは少ないので、まずは npm
経由で立ち上げた node プロセスをデバッグできるようにしましょう。
VSCode の公式ページによれば、以下のようなコードで、npm から起動したプロセスをデバッグできるようになります。
(..snip..)
"scripts": {
"debug": "node --nolazy --inspect-brk=9229 app.js"
},
(..snip..)
9229 ポートは inspector 利用時のデフォルトポートなので、単に --inspect
でも構いません。
次に VSCode の設定ファイルです。
launch.json
(..snip..)
{
"name": "Launch via NPM",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run-script", "debug"
],
"port": 9229
}
(..snip..)
これだけで、VSCode のデバッガが利用可能になります。
個人的には以下のように若干カスタムしたものを使っています。
launch.json
(..snip..)
{
"type": "node",
"request": "launch",
"name": "Debug: Server",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run", "debug"
],
"port": 9229,
"restart": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
(..snip..)
[adinserter block="1"]
node-dev を使おう
さて、debug script では node をそのまま起動していますが、開発中にソースコード変更を検知して Express サーバーが再起動してくれると更に楽です。巷では nodemon が有名ですが、筆者が推すのは断然 node-dev です。コンフィグレスで require されたものだけを watch してくれる上に、デスクトップ通知があって再起動したことがわかりやすいのが特徴です。
debug
script を修正し、以下のようにしておきましょう。
(..snip..)
"scripts": {
"debug": "node-dev --nolazy --inspect app.js"
},
(..snip..)
追加の watch 設定
開発環境で、js だけではなく json 等が変更されたときでも node-dev による再起動が働いて欲しい場合もあると思います。そんなときは、追加で require する機構を用意し、 development 環境でのみ働くようにしておきましょう。
dev.js
const fs = require('fs');
const path = require('path');
const localeDir = 'src/locales';
class Dev {
init() {
this.requireForAutoReloadServer();
}
/**
* require files for node-dev auto reloading
*/
requireForAutoReloadServer() {
// load all json files for live reloading
fs.readdirSync(localeDir)
.filter(filename => {
return fs.statSync(path.join(localeDir, filename)).isDirectory();
})
.map((dirname) => {
require(path.join(localeDir, dirname, 'translation.json'));
});
}
}
module.exports = Dev;
app.js
(..snip..)
if (self.node_env === 'development') {
const Dev = require('./dev');
const dev = new Dev();
dev.init();
}
(..snip..)
上記のサンプルコードは、 src/locales/*
下にある translation.json
ファイルが変更された場合に再起動を行います。
[adinserter block="1"]
Let's Try! ― シナリオB
シナリオBは、node.js (Express) + webpack/babel によるクライアントビルド両方で利用できる設定です。
ここまでに作ったファイルは完全に流用できます。
webpack の設定と、VSCode の設定を追加しましょう。
webpack.js
(..snip..)
devtool: 'cheap-module-eval-source-map',
(..snip..)
devtool
にどのような値を設定可能かは webpack の公式ドキュメントを参照してください。ここでは速度を重視し、 cheap-module-eval-source-map
を選びました。
次に VSCode の設定ファイルです。
launch.json
(..snip..)
{
"type": "chrome",
"request": "launch",
"name": "Debug: Chrome",
"sourceMaps": true,
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///*": "${workspaceFolder}/*"
},
"url": "http://localhost:3000",
}
(..snip..)
特に重要なのは webRoot
と sourceMapPathOverrides
です。
webRoot
は、URL ベースで root がどこを指し示すか、
sourceMapPathOverrides
はブラウザの開発者ツールで source map の参照先の webpack:///
から始まるパスが、VSCode のワークスペース上でどこを指し示すべきかのマッピングを行います。ブレークポイントがうまく設定できない場合は、この2つを見直しましょう。
[adinserter block="1"]
Let's Try! ― シナリオC
シナリオCは、next.js 向けの設定です。ディレクトリ構成がほぼ決まっているので、シナリオBより簡単かもしれませんね。
launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Next: Node",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"debug"
],
"port": 9229,
"restart": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"sourceMapPathOverrides": {
"webpack:///*": "${workspaceFolder}/*"
},
},
{
"type": "chrome",
"request": "launch",
"name": "Next: Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///*": "${webRoot}/*"
},
}
],
"compounds": [
{
"name": "Next: Both",
"configurations": ["Next: Node", "Next: Chrome"]
},
],
}
[adinserter block="1"]
まとめ
後編ではロギングに関して実際のコードを紹介し、デバッガ利用時の「理想の世界」を実現するコードを紹介しました。デバッガや node-dev のような自動で再起動するツールを使うことで、開発時の効率は数倍になります。
ハリー
Profile:エンジニア歴15年。現在もバリバリのエンジニアとして一線で活動中。