小ネタです。Node.js で CLI ツールを実装する場合、以下のように shebang 付きの実行可能ファイルを用意して、それを package.json
の bin
フィールドから参照する構成にすることが多いと思います。
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 を読み込むと良いケース、悪いケース両方あるはずで、状況によって都度判断すると良いと思います。