CLOVER🍀

That was when it all began.

スタックトレースをTypeScriptのファイルの内容で表示する

これは、なにをしたくて書いたもの?

TypeScriptで作ったプログラムで例外をスローした時に、出力されるスタックトレースってどうなるのかな?と
思いまして。

結果としては、トランスパイル後のJavaScriptファイルの内容になるのですが。

ひと手間加えると、TypeScriptファイルの内容で表示することもできるみたいなので試してみました。

Source Map Support

Source Map Supportを使うと、V8のスタックトレースAPIとSource Mapを使用して、スタックトレースを元々の
ファイルのパスと行番号で表示できるようです。

GitHub - evanw/node-source-map-support: Adds source map support to node.js (for stack traces)

Stack trace API · V8

使い方は、nodeコマンドでのアプリケーション起動時に仕込む方法と

$ node -r source-map-support/register compiled.js

プログラム内に仕込む方法の2つがあるようです。

// こちらか
require('source-map-support').install();


// こちら
import 'source-map-support/register'

// Instead of:
import sourceMapSupport from 'source-map-support'
sourceMapSupport.install()

また、ブラウザでも使えるようです。

環境

今回の環境は、こちら。

$ node -v
v16.13.0


$ npm -v
8.1.0

プロジェクトの作成。

$ npm init -y
$ npm i -D typescript
$ npm i -D -E prettier
$ echo {} > .prettierrc.json
$ mkdir src

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "baseUrl": "./src",
    "outDir": "dist",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true
  },
  "include": [
    "src"
  ]
}

この時点でのdevDependencies。

  "devDependencies": {
    "prettier": "2.4.1",
    "typescript": "^4.4.4"
  }

TypeScriptとPrettierのバージョン。

$ npx tsc --version
Version 4.4.4


$ npx prettier --version
2.4.1

サンプルを書く

では、最初にお題となるプログラムを書いてみましょう。

こんな感じのソースコードを用意。

src/index.ts

import { bar } from "./sub/foo";

function hoge() {
  return bar();
}

try {
  hoge();
} catch (e) {
  console.log('catch!!');
  console.error(e);
}

サブディレクトリにあるファイルで、例外をスローするようにしました。

src/sub/foo.ts

export function bar() {
  throw new Error('Oops!!');
}

で、コンパイルして

$ npx tsc

実行。

$ node dist/index.js
catch!!
Error: Oops!!
    at bar (/path/to/dist/sub/foo.js:5:11)
    at hoge (/path/to/dist/index.js:5:26)
    at Object.<anonymous> (/path/to/dist/index.js:8:5)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

ここでのファイル名や行番号が、トランスパイル後のJavaScriptファイルの情報になっています。

Source Map Supportを使う

では、ここでSource Map Supportを使ってみましょう。

まずはインストール。

$ npm i source-map-support

依存関係はこうなりました。

  "devDependencies": {
    "prettier": "2.4.1",
    "typescript": "^4.4.4"
  },
  "dependencies": {
    "source-map-support": "^0.5.20"
  }

また、tsconfig.jsonではsourceMapを有効にする必要があります。

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "baseUrl": "./src",
    "outDir": "dist",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "sourceMap": true
  },
  "include": [
    "src"
  ]
}

Source Mapの出力有効化を忘れると、Source Map Supportを使っても意味がありません。

ビルドして

$ npx tsc

-r source-map-support/registerを付けて実行。

$ node -r source-map-support/register dist/index.js

-rはrequireですね。

で、結果はこうなります。

$ node -r source-map-support/register dist/index.js
catch!!
Error: Oops!!
    at bar (/path/to/src/sub/foo.ts:2:9)
    at hoge (/path/to/src/index.ts:4:13)
    at Object.<anonymous> (path/to/src/index.ts:8:3)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

ファイル名や行番号がTypeScriptのものになりました。なんなら、ファイルパス自体もトランスパイル前のものに
なっています。

これで、TypeScriptファイルの情報でスタックトレースが読めるようになりました。

まとめ

Source Map Supportを使って、スタックトレースをトランスパイル前のTypeScriptファイルの情報で表示するように
してみました。

便利といえば便利なのですが、実行時に仕込まないといけないことと、Source Map Supportの更新も止まって
時間が経っているようなので、いつも使いたいかというと微妙なところです。

まあ、こういう方法でTypeScriptファイルの情報にスタックトレースを戻せる、ということは覚えておこうかなと
思います。