mizdra's blog

ぽよぐらみんぐ

Node.js で sourcemap を読み込ませた状態で CLI ツールを起動する

小ネタです。Node.js で CLI ツールを実装する場合、以下のように shebang 付きの実行可能ファイルを用意して、それを package.jsonbin フィールドから参照する構成にすることが多いと思います。

bin/eslint-interactive.js:

#!/usr/bin/env node

import { run } from '../dist/index.js';

run({
  argv: process.argv,
}).catch((error) => {
  console.error(error);
  process.exit(1);
});

package.json:

{
  "name": "eslint-interactive",
  "scripts": {
    "build": "tsc -p tsconfig.json",
  },
  "bin": {
    "eslint-interactive": "bin/eslint-interactive.js"
  }
}

tsconfig.json:

{
  "include": ["src/**/*"],
  "exclude": ["node_modules"],
  "compilerOptions": {
    "outDir": "./dist",
    "target": "ES2019",
    "lib": ["ES2019"],
    "isolatedModules": true,
    "module": "node12",
    "moduleResolution": "node12",
    "sourceMap": true, // デバッグのために sourcemap を出力する
    "locale": "ja"
  }
}

これで npm i -g eslint-interactive した時に、eslint-interactive コマンドがローカルマシンにインストールされ、実行できるようになります。

$ npm i -g eslint-interactive
$ eslint-interactive --version
8.1.0

デフォルトでは sourcemap は読み込まれない

CLI ツールを実装する、という話であれば上記対応だけで事足ります。コマンドも問題なくインストールできますし、実行できます。一方で、コマンドを実行時に実行時エラーが発生した場合、そのエラーメッセージを見ると、あれっ?と思う結果になります。

$ eslint-interactive --version
file:///Users/mizdra/src/github.com/mizdra/eslint-interactive/dist/cli/run.js:5
    throw new Error('test error');
          ^

Error: test error
    at run (file:///Users/mizdra/src/github.com/mizdra/eslint-interactive/dist/cli/run.js:5:11)
    at file:///Users/mizdra/src/github.com/mizdra/eslint-interactive/bin/eslint-interactive.js:5:1
    at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:331:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)

デバッグのために sourcemap を出力しているはずですが、エラーメッセージにはトランスパイル後のファイルの位置情報が使われてしまっています。これは Node.js がデフォルトで sourcemap を読み込まないようになっているためです。

sourcemap を読み込ませた状態で CLI ツールを起動する

Node.js が標準で sourcemap を読み込むためのオプション「--enable-source-maps」を用意してくれているので、これを使うと良いです。Node.js v12.12.0+ でサポートされています。

bin/eslint-interactive.js:

#!/usr/bin/env -S node --enable-source-maps

import { run } from '../dist/index.js';

run({
  argv: process.argv,
}).catch((error) => {
  console.error(error);
  process.exit(1);
});

env コマンドに -S オプションを渡しているのがミソです。-S オプションを付けないと、node --enable-source-maps という名前を持つコマンドを探して実行しようとしてしまいます。詳しくはこのあたり を参照して下さい。

この状態で再度実行してみると、無事 sourcemap が読み込まれていることが確認できます。

$ eslint-interactive --version
/Users/mizdra/src/github.com/mizdra/eslint-interactive/src/cli/run.ts:9
  throw new Error('test error');
        ^

Error: test error
    at null.run (/Users/mizdra/src/github.com/mizdra/eslint-interactive/src/cli/run.ts:9:9)
    at file:///Users/mizdra/src/github.com/mizdra/eslint-interactive/bin/eslint-interactive.js:5:1
    at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:331:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)

余談

デバッグの時に便利なので sourcemap を読み込めるようにしましょう、みたいな紹介の仕方をしましたが、CLI ツールのコードは別に minify している訳でもないですし、デフォルトのエラーメッセージからでも十分デバッグのための情報を得られます。逆に sourcemap を読み込ませると、トランスパイル後のコードのどの位置でエラーが起きたかの情報が失われてしまいます。sourcemap を読み込むと良いケース、悪いケース両方あるはずで、状況によって都度判断すると良いと思います。

参考資料

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.