イマドキの JavaScript 開発で使える、リモートデバッグとロガーの Tips (2018年版-後編)

こちらは 「イマドキの JavaScript 開発で使える、リモートデバッグとロガーの Tips (2018年版-後編)」からの転載です。

SEROKU の開発を例に、弊社で使っているリモートデバッグとロガーの Tips をご紹介します。
当記事は 2018 年、と過去の記事ですが、現在でも応用可能な Tips になっています。


案件としても OSS 成果物としても、JavaScript を利用するシチュエーションは増え続けています。まだまだ枯れた言語とは言い難い状況で、使われるバージョンも ES5 から ES7 まで進化を続け、新しい文字列リテラルや async/await のような「イマドキの JavaScript の書き方」を紹介する記事は多い中、デバッグはこうあるべきという情報は比較的少ないように思います。

本記事の前編、中編では、システム開発に於けるデバッガ、ロガーの大切さと、他の言語・フレームワークと比べた際の JavaScript 開発環境に於けるビハインドについて説明し、実際に理想的なロガーを利用する為の設定方法を紹介してきました。

本記事では 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 上でデバッグ可能

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..)

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 ファイルが変更された場合に再起動を行います。

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..)

特に重要なのは webRootsourceMapPathOverrides です。

webRoot は、URL ベースで root がどこを指し示すか、
sourceMapPathOverrides はブラウザの開発者ツールで source map の参照先の webpack:/// から始まるパスが、VSCode のワークスペース上でどこを指し示すべきかのマッピングを行います。ブレークポイントがうまく設定できない場合は、この2つを見直しましょう。

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"]
    },
  ],
}

まとめ

後編ではロギングに関して実際のコードを紹介し、デバッガ利用時の「理想の世界」を実現するコードを紹介しました。デバッガや node-dev のような自動で再起動するツールを使うことで、開発時の効率は数倍になります。

関連記事(前編・中編)と合わせて、是非自身とチームの開発をより効率的に、より楽しいものにしていただければと思います。

関連記事

イマドキの JavaScript 開発で使える、リモートデバッグとロガーの Tips (前編)

イマドキの JavaScript 開発で使える、リモートデバッグとロガーの Tips (中編)