bitbank techblog

vscodeでTypeScriptをデバッグしよう(jestもあるよ)

こんにちは、monjaです。
思った通りに動くプログラムを書き続けることができるプロ・プログラマーになりたいと日々思っていたりいなかったりしますが、残念ながら時々思った通りに動かないプログラムを書いてしまうものです。プログラムは思った通りではなく書いた通りに動きますからね。
そんなとき、思惑と現実の乖離、つまりバグの原因を見つけて直す必要があるわけですが、ソースコードを読み直したり結果を見ても原因がピンと来ないときもあるものです。
とりあえずprintfデバッグ(JavaScriptならconsole.logデバッグですね)をするのも乙ですが、仕込みが面倒であったり、必要な情報を一発で表示できなくて数回実行しなおすハメになったり、原因発見で満足してprintfを消すのを忘れてしまうこともあります。世の中にはデバッガというめちゃ便利ツールがあるので、それが利用できるときは積極的に利用していきたいものです。

ということで今回はTypeScriptのデバッグについて紹介します。なんでTypeScriptかというと、ビットバンクでは普段TypeScriptで開発することが多いからですね。また同じく我々はよくJestを使ってテストを書いているので、Jestを使うテストコードのデバッグについても紹介します。
Jestとはなんぞや、と思った方は弊blogの Jestでテストを書こう! も併わせてご覧ください。
私の好みによりタイトル通りVisual Studio Code (Version: 1.73.1) を使った内容になります。予めご了承ください。あと、デバッグ、デバッガの基本的な知識がある(ステップ実行、ブレークポイントなどとは何かを知っている)前提で書いています。

準備

まずはデバッグ対象のTypeScriptプロジェクトを用意しましょう。雑にこんな感じで。

work$ mkdir hoge && cd !$
mkdir hoge && cd hoge
hoge$ yarn init
yarn init v1.22.18
question name (hoge): ⏎
question version (1.0.0): ⏎
question description: ⏎
question entry point (index.js): ⏎
question repository url: ⏎
question author: ⏎
question license (MIT): UNLICENSED
question private: y
success Saved package.json
✨  Done in 9.76s.

hoge$ yarn add -D typescript @types/node@^18 ts-node
yarn add v1.22.18
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 19 new dependencies.
info Direct dependencies
├─ @types/node@18.11.9
├─ ts-node@10.9.1
└─ typescript@4.9.3
info All dependencies
...
✨  Done in 3.33s.
hoge$ yarn tsc --init          
yarn run v1.22.18
$ /private/tmp/work/hoge/node_modules/.bin/tsc --init

Created a new tsconfig.json with:
                                    TS 
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig
✨  Done in 0.92s.
hoge$ 

長いですね。
そしてvscodeで作ったディレクトリ(hoge)を開きましょう。開けましたね?

b943f651-ddbc-4353-baa5-00c15379c05e

まずはJavaScriptのデバッグをはじめよう

そうしたら、TypeScriptのセットアップをしたけど、まずは入門編ということでJavaScriptのデバッグからいってみましょう。
たとえばこんな感じのコードを用意してみます。

fib.js:

const fib = (n) => {
    if (n <= 2) {
        return 1;
    }
    return fib(n - 2) + fib(n - 1);
};

module.exports = fib;

main.js:

const fib = require('./fib');

for (let i = 1; i <= 10; i++) {
    console.log(fib(i));
}

こういうの、よく見ますね。用意できたら、おもむろにvscodeでmain.jsを開き、console.log(fib(i)) のところにブレークポイントを置いてみます。行数表示の左あたりにポインタをもっていくと赤丸が出てくるのでクリックぽちっとすれば置いたり外したりできます。

1da9b826-df72-4758-a4e3-feb1b4d5aeb8

もちろんコマンドパレットからDebug: Toggle breakpointしたり、ショートカットキーで置いてもかまいません。(MacだとF9というとても使いづらいキーになっているので私は割り当て直したりしてます)
そうしたら、デバッグ実行をしましょう。Run and DebugペインからRun and Debugボタンを押してもいいし、ブレークポイント同様コマンドパレットやショートカットキーでもよいです。もしSelect debuggerと聞かれたらNode.jsを選びましょう。するとおもむろにnode.jsがデバッグ実行され、ブレークポイントで止まることでしょう。
なんて簡単! そう、JavaScriptならね。

a0e82fe9-e459-40c2-aa20-34961b415663

左のDebugペインを見たりソースの変数iなどにポインタを当てることで変数の内容が見れるし、タイトルバーの下あたりにあるツールバーをぽちぽちしたり、ショートカットキーをぽちぽちしたりすると、ステップ実行やContinueができます。今回は10回実行される行にブレークポイントを置いているのでContinueも最大10回できます。やったね。(何が?)
ステップインでファイルをまたいで追えることもわかると思います。

……もしかしたらそれ以前に、以下のように Can't find Node.js binary ... error getting version: ... と言われてデバッグにありつけないかもしれません。

1e2eadd6-1da4-4100-a153-cb17ceffb1e3

これはnvmやasdfといったランタイム管理ツールを使っている場合によく起きます。上記のスクリーンショットはasdfの例ですね。こんなときはちゃんとこのプロジェクトで使うバージョンを指定してあげましょう。asdfならasdf localで設定しましょう。(実は成功例のスクリーンショット取るときはasdf localを設定していたんですね〜。)

しかしそれでもダメな時が以前ありました。そういう時はたとえばasdfならasdf globalで設定することでも解決できそうでしたが、asdfのようなランタイム管理ツールを使っている人はglobalなんて設定したくないと思います。そんなときはlaunch.jsonにてruntimeProgramを設定する手があります。ちょっと管理ツールの内臓に触れてしまうけど仕方がない。
具体的にはDebug and Runペインで create a launch.json file リンクをつついてNode.jsを選択し一旦普通のnode.jsデバッグのデバッグ設定を作ります。そうすると.vscode/launch.jsonが作成されるとともにこんな感じのエントリができあがるので、

...
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/main.js"
        }
...

そこにこんな感じで一行、runtimeExecutableというのを追加します。

--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,6 +11,7 @@
             "skipFiles": [
                 "<node_internals>/**"
             ],
+            "runtimeExecutable": "/private/tmp/work/.asdf/installs/nodejs/18.12.1/bin/node",
             "program": "${workspaceFolder}/main.js"
         }
     ]

これは当然ランタイム管理ツールによって内容が異なります。上記はasdfの例です。とはいえランタイム管理ツールがやっているのは数あるインストールされたランタイムたちからいい感じに特定の実行ファイルを選ぶというものなので、さほど難しいものではないでしょう。ちなみにasdfの場合は適当なバージョンを有効にした後に asdf which node でこのパスを表示できます。
また、おこのみでprogramの欄を "${file}" にしたりすると、今開いているファイルをデバッグできるようになったりするのでおすすめです。

ここまで書いてきたことは、実は管理ツールの話を除けばすべて公式ドキュメント https://code.visualstudio.com/docs/nodejs/nodejs-debugging に書いてあるし、ここに書いてないアタッチなど他の機能についても触れられているので、興味がでてきた方は是非一読ください。私は量多いなーと思って後で読むリストに追加しました。(きっと読むことはない。)

TypeScriptで本気だす

さて次は本題のTypeScriptをやっていきましょう。実は方法が2つあります。ビルドして前述のJavaScriptデバッグをするのと、直接ts-nodeで実行する方法です。

とりあえず、先程のプログラムをTypeScript化しましょう。といってもそんなに変わらないけど……。jsファイルをリネームしちゃって中身を書き換えましょう。

fib.ts:

export const fib = (n: number): number => {
    if (n <= 2) {
        return 1;
    }
    return fib(n - 2) + fib(n - 1);
};

main.ts:

import { fib } from './fib';

for (let i = 1; i <= 10; i++) {
    console.log(fib(i));
}

ビルド方式

さて、まずはビルドする方法ですが、これは単純に手でtscしましょう。今回はyarnを使っているので yarn tsc でできますね。
ただしtsconfig.jsonでsourceMapを有効にしておく必要があります。代わりにinlineSourceMapでも大丈夫なようです。冒頭に実行したtsc --initではどれもコメントアウトで無効化されているので注意しましょう。
そしておもむろにmain.tsなどのtsファイルでNode.jsとしてデバッグを開始すると、あら不思議、なぜか自動的にtscで出力されたjsファイルを対象としてデバッグが始まり、そしてTypeScriptレベルでブレークポイントやステップインなどが実行できるのです! 本当に不思議。だけど便利。

ここでうっかりどのsourceMapも有効にせずtscしてtsファイルをデバッグすると、tsファイルをJavaScriptファイルとみなしてNode.jsデバッグが開始され、以下のような感じでだいたいは即エラーになって終わります。

Uncaught SyntaxError /private/tmp/work/hoge/main.ts:1
import { fib } from './fib';
^^^^^^

SyntaxError: Cannot use import statement outside a module

サイレントに失敗するので気付きにくい。気を付けましょう。

しかし、デバッグするたびに手で yarn tsc するのはだるいですよね。そこでvscodeの機能性に頼りましょう。コマンドパレットから Tasks: Run Build Task を選ぶ(もしくはショートカットキーでそれを起動する)と、 tsc: build - tsconfig.json などが出てきます。それを選択すると自動的にtscを実行してくれるので、手数が節約できます。

9341cc8f-d1e8-4f71-88b3-d7f4f1d93ee2

(JSデバッグとほぼ同じ見た目だけど、TypeScriptをデバッグできている点に注目。)

そしてそれをデバッグ前に自動実行する機能性もあります。launch.jsonタイムの始まりです。
Debug and Runペインで create a launch.json file リンクをつついてNode.jsを選択し一旦普通のnode.jsデバッグのデバッグ設定を作ります。そうすると.vscode/launch.jsonが作成されるとともにこんな感じのエントリができあがるので、

...
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/main.ts",
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        }
...

そこにこんな感じで一行、preLaunchTaskというのを追加します。

--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -12,6 +12,7 @@
                 "<node_internals>/**"
             ],
             "program": "${workspaceFolder}/main.ts",
+            "preLaunchTask": "tsc: build - tsconfig.json",
             "outFiles": [
                 "${workspaceFolder}/**/*.js"
             ]

このpreLaunchTaskの値はIntelliSenseで補完されないので入力が地味に面倒です……。(Configure Taskしてtasks.json作ってもダメなんですねぇ。)
また、お好みでprogramの欄を "${file}" にしたりすると、今開いているファイルをデバッグできるようになったりするのでおすすめです。
(この流れ、見覚えあるかもしれないですが細部が異なっているので注意してください。)

そうすると、もはや何もしなくても、tsファイルをデバッグしようとするだけで自動的にtscが実行されるので、快適なTypeScriptデバッグライフが送れるのです! 圧倒的工数0。最高。

debugging-ts-by-build.gif

というのが公式ドキュメント https://code.visualstudio.com/docs/typescript/typescript-debugging にほぼ書いてあったりします。またこのパターンかよ! と思った方はごめんなさい。でもin-line sourceに対応してないと書かれているのに結構前からサポートされていそうだったり、preLaunchTaskのくだりが自動生成されるように書いてあるけど実際はならなかったりと、微妙に 現実と乖離 してるところもあるのでここにしたためた意味はあったでしょう……。

ts-node方式

さて、ビルド方式だけでも結構長くなってしまったんですが、ts-nodeで実行する方法も見ていきましょう。launch.jsonタイム再びです。
まずはDebug and Runペインで create a launch.json file リンクをつついてNode.jsを選択し一旦普通のnode.jsデバッグのデバッグ設定を作ります。そうすると.vscode/launch.jsonが作成されるとともにこんな感じのエントリができあがるので、

...
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/main.ts",
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        }
...

そこにこんな感じで一行、runtimeExecutableというのを追加します。

--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,6 +11,7 @@
             "skipFiles": [
                 "<node_internals>/**"
             ],
+            "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ts-node",
             "program": "${workspaceFolder}/main.ts",
             "outFiles": [
                 "${workspaceFolder}/**/*.js"

もうこれだけでデバッグができてしまいます。この方式の場合、tsconfig.jsonでsourceMap等を有効にしなくても良いです。(おそらくts-nodeが内部で有効にしてくれている?)ビルド方式に比べるとセットアップがかなり簡単ですね。
ちなみに、このruntimeExecutableというのは、"node"コマンドの代わりに実行するものをすり替えるというもので、ここではts-nodeにすり替えるので、tsのままビルドせずに実行できる、という寸法なのです。
また、お好みでprogramの欄を "${file}" にしたりすると、今開いているファイルをデバッグできるようになったりするのでおすすめです。
(この流れ、とても見覚えあるかもしれないですが細部が異なっているので注意してください。)

ts-nodeの方法は公式ドキュメントに記載されていないので将来動かなくなるかもしれませんが、個人的にはセットアップが簡単なので好みです。あなたのお好みに応じて好きなほうでやっていきましょう。

vscode-jestはいいぞ

さて、TypeScriptのデバッグを完全に理解したところで次はJest with TypeScriptをいってみましょう。まずは下準備として、TypeScriptなJestを動かせる環境に仕立てます。こんな感じで。

hoge$ yarn add -D jest ts-jest @types/jest
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 198 new dependencies.
info Direct dependencies
├─ @types/jest@29.2.3
├─ jest@29.3.1
└─ ts-jest@29.0.3
info All dependencies
...
✨  Done in 9.19s.
hoge$ yarn ts-jest config:init
yarn run v1.22.19
$ /private/tmp/work/hoge/node_modules/.bin/ts-jest config:init

Jest configuration written to "/private/tmp/work/hoge/jest.config.js".
✨  Done in 0.52s.
hoge$

jest.config.jsを作る際に jest --init を使ってもいいですが、ts-jestを差し込む設定を覚えるのが面倒なのでts-jestの機構に頼ってみました。この場合 jest.config をTypeScriptにできませんが、まあ手で変換するのもさほど面倒ではないですし、今のところ変更する予定もないのでこのままで。productionとして本気でやるときに考えることにしましょう。

このあたりまではjestのドキュメントにもまとまっています。 https://jestjs.io/ja/docs/getting-started#ts-jest-経由で

そうしたら、以下のようなテストファイルを作って

import { fib } from "./fib";

describe('fib', () => { 
    it('should return first 6 correct values', () => {
        expect(fib(1)).toBe(1)
        expect(fib(2)).toBe(1)
        expect(fib(3)).toBe(2)
        expect(fib(4)).toBe(3)
        expect(fib(5)).toBe(5)
        expect(fib(6)).toBe(8)
    });
});

手でjestしてみましょう。

hoge$ yarn jest
yarn run v1.22.19
$ /private/tmp/work/hoge/node_modules/.bin/jest
 PASS  ./fib.spec.ts
  fib
    ✓ should return first 6 correct values (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.888 s
Ran all test suites.
✨  Done in 3.33s.
hoge$ 

いけましたね。
この状態だとちょっとおもしろくないので、fib.tsを壊しておきます。

--- a/fib.ts
+++ b/fib.ts
@@ -2,5 +2,5 @@ export const fib = (n: number): number => {
     if (n <= 2) {
         return 1;
     }
-    return fib(n - 2) + fib(n - 1);
+    return fib(n - 1) + fib(n - 1);
 };

もういちどjestしてみます。

hoge$ yarn jest
yarn run v1.22.19
$ /private/tmp/work/hoge/node_modules/.bin/jest
 FAIL  ./fib.spec.ts
  fib
    ✕ should return first 6 correct values (4 ms)

  ● fib › should return first 6 correct values

    expect(received).toBe(expected) // Object.is equality

    Expected: 3
    Received: 4

       6 |         expect(fib(2)).toBe(1)
       7 |         expect(fib(3)).toBe(2)
    >  8 |         expect(fib(4)).toBe(3)
         |                        ^
       9 |         expect(fib(5)).toBe(5)
      10 |         expect(fib(6)).toBe(8)
      11 |     });

      at Object.<anonymous> (fib.spec.ts:8:24)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.972 s, estimated 2 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
hoge$ 

いい感じに壊れましたね。

というわけで、jestのデバッグもいってみま……いやその前にまずはvscode-jestという拡張を紹介させてください。

https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest

48c1a048-6413-48b4-8a30-80826ed68a32

(なおvscode-jestは記事執筆時でv4.6.0でした。)

この拡張をインストールすると、後ろで自動的にテストが走って、その結果をspec.tsファイルに表示してくれるようになるんです。

8cb8fd95-df98-4ba2-bfc8-126d8833a7e9

しかもテストファイルや実装しているファイルを保存するとテストを実行しなおしてくれるのです。便利。

debugging-jest-autorun.gif

便利……ですが、ファイル保存のたびにテストが走ると重いので(特にproductionなコードのとき)、私は設定でautoRunをoffにしています。それでもショートカットキーなどで特定のテストファイルの特定のケースやファイルまるごとテスト実行が簡単にできるので意味があります。

vscode-jestでデバッグする

というわけで今度こそデバッグしていきましょう。なぜいきなりvscode-jest拡張を紹介したのかというと……さきほどのスクリーンショットで気付かれた方もいらっしゃるかもしれませんが、テストのデバッグが簡単にできるようになるからなんですね。

やりかたはとっても簡単。あらかじめブレークポイントを設置しておき、デバッグしたいケースの上に表示される Debug をポチっとクリックするだけ。

3e0e5e97-8f38-4254-98ed-72dd94c2d3dd

すると、こんな感じでいきなりTypeScriptレベルでテストをデバッグできます。こうなると何もいうことがないですね。

7e783f8d-e13a-4f64-8ee5-4005204c128d

なお、vscode-jestのデフォルト設定では成功したケースにはDebugが表示されないようになっているので、そのままではDebugできません。もし成功ケースをデバッグしたい場合は以下のどれかを行えばよいです。

  • ケースを壊して表示させる(💪パワー!)
  • 設定のJest > Debug Code Lens: Show When Test State Inで"pass"を追加することで成功ケースについてもDebugを表示するようにして、Debugする
  • デバッグしたいケースの左の ✅ を右クリックしてDebug Testを選ぶ
  • デバッグしたいケースのコードのどこかにキャレットを置いたのちコマンドパレットから Test: Debug Test at Cursor を実行する(もちろんショートカットキーも可)

よりどりみどり! ちなみに私はショートカットキー派なので最後をよくやります。

以上……と言いたいんですが、JavaScriptのデバッグの時と同様、以前に以下のようにこれだけでは動かないことがありました。

557aa657-4dd9-4757-9fd8-56778358060b

(上記スクリーンショットはasdfでバージョンを未指定にして再現したものなので当時と少し違うかもしれないです。)

こんな時は同様にlaunch.jsonタイムが始まるのですが、vscode-jestがデバッグ実行する方法が通常と異なっているため、設定内容や設定方法が異なってきます。
方法は2つあり、vscode-jestのSetup機能(Beta)を使う方法と、launch.jsonを直接変更する方法です。

Setup機能(Beta)はコマンドパレットで Jest: Setup Extension (Beta) を選ぶと起動できます。こんな感じでWizardが出てくるので、Setup Jest Debug Configを選びましょう。

2f05e682-032f-4ffa-b2ee-6ff44dafd49a

すると Missing required "jest.jestCommandLine" setting... というポップアップが出るかもしれません。出なかった場合は次の段落まで飛ばして良いです。出た場合は以下のようにやっていきましょう。

  • Setup jestCommandLineを選ぶ
  • 続けて Are you able to run jest tests from the terminal ? と聞かれるのでYesを選ぶ
    • ちなみにNoと選ぶとまずはjestを実行できるようにしてから出直してきてねと言われてWizardが終了します。かなしいね
  • どうやればjestを実行できるか聞かれるので、冒頭でテストを壊したときに実行した yarn jest をそのまま入力する
    • 以前動かなかったときは何故かyarnを経由すると動いたのでこのようにしています。もしこれでもダメな場合はランタイム管理ツールが管理しているnode実行ファイルのフルパスをyarnの前に指定し、yarnもnode_modules/.bin/yarnと指定する必要があるでしょう……
    • この記事通りに手を動かしている方は問題ないですが、既存プロジェクトでjestの設定ファイルをいくつかに分けていたり、ファイル名を変えている場合は、ここでさらにconfigオプションをつけておきましょう。たとえばjest.config.dev.jsonなら yarn jest --config=jest.config.dev.json のように。ここで正しい設定を入力すると、ファイル保存時の自動テスト実行なども正しく動くようになります
      • SyntaxErrorでどのテストも動かない場合はだいたいconfig指定忘れなことが多いです。(たまにconfig手作りだとtransform書き忘れのパターンもある)
    • 一度この設定を完了すると、次回からWizardでここまでの設定を聞かれなくなります。もし設定しなおしたい場合は、設定から Jest: Jest Command Line の項目を変更しましょう。なおWorkspaceで変更されているので注意しましょう
      • 設定画面では最初Userが選択されているので「あれ、設定したのになんで空なんだろう?」となりがちです。ちゃんと (Modified in Workspace) と出るので気付けるとは思いますが……

さて、jestの実行方法をvscode-jestに教えたところまでくれば後は簡単。WizardでGenerateという項目ひとつだけが表示かつ選択された状態になっていると思うのでEnterを押すだけです。

ad3fa22d-a2ac-47fa-a6b2-025e0f136ed0

すると A new debug config "vscode-jest-tests.v2" has been added in launch.json. Please review and update as needed. というポップアップが出るのでOkして、Wizardの最初に戻ってくるので、Exitを選ぶなりESCキーを押すなりでWizardを終了します。
するとlaunch.jsonが開かれていると思うので、これはそっと閉じます。閉じる前にargsの欄を見てみると、テストファイル名やケース名がパラメータになっているのが見えておもしろいかもしれません。jestに追加パラメータを追加したい場合はいじってみても良いでしょう。
ここまでくれば、テストのデバッグができるようになっているでしょう!

一方のlaunch.jsonを直接変更する方法ですが、言葉通りlaunch.jsonを変更していきます。
launch.jsonが無ければRun and Debugペインから create a launch.json file リンクをつついて Debug Jest tests using vscode-jest を選べばOKです。
launch.jsonが既にある場合は、一旦.vscode/launch.jsonを開き、 "configurations": [ の行の直後に空行を挿入し、 jest などと打つと補完が始まるので Jest: Default jest configuration を選択すると同様の効果が得られます。
ただしこの方法ですとSetup機能(Beta)で設定できるjestを実行するコマンドラインの設定が無視されてしまうので、この方法は何もしなくてもjestが実行できる環境でしか使えない方法となります。このため、私はこちらの方法はおすすめしません。

なおどちらの方法でもデバッグ設定のname欄が vscode-jest-tests.v2 となっていると思います。不思議な名前ですが、この .v2 は大事で、消してしまうとうまく動かなくなってしまいます。これはvscode-jest v4.3.1 でデバッグ設定に変更があり、その非互換への対処であるのです。
(最初、vscode-jestの設定をいろいろいじっていたときデバッグ設定が2つできてしまい、その時に.v2の存在に気付いたけども、これは衝突回避のための名前付けなのかなと勘違いしてハマりました……。)

つらいポイント

さてかなり長くなってしまいましたが、ここまででTypeScriptなコードのデバッグが一通りできるようになったかと思います。なっていると嬉しいな。ここまでお読みいただきありがとうございます。
そんなあなたに、ささやかながらTypeScriptデバッグでよくハマるポイントというかつらいポイントをいくつか紹介して筆を置きたいと思います。

breakpointはつらいよ

行の左をつついたりして設置できるブレークポイント、これはその行に最初に実行される箇所が実行されそうになったら止まるというものですね。
しかしtsconfig.jsonにおけるtargetによっては、それが正しく止まらなかったり、またはステップ実行がかなり粗くなったりします。
以下のようにtargetをes5, es2015, es2017に変えてみると、JavaScriptへのトランスパイル結果、そしてデバッグ挙動が違うことがわかるかと思います。つらい。

debugging-ts-anomaly.gif

とくにasyncで強くこの傾向が見られます。場合によってはinline breakpointとよばれる、この行のここ(この桁)が実行されそうになったら止まるブレークポイントを設置することで止まってくれること「も」あります。止まらない時もあります。ステップ実行はどうしようもありません。一応呼び先関数内にブレークポイントを置いたり、Source Mapped Steppingを無効にしたりSmart Stepを無効にしたりしてJavaScriptレベルでデバッグすればできなくもないですが、targetによってはかなりつらいかと思います……。がんばろう。

Debug Consoleで関数が呼べなくてつらいよ

Debug Consoleペインの入力欄を使うと、ブラウザのDeveloper ToolsのConsoleよろしくその場で書いたちょっとしたコードを実行できたりします。特定の変数をちょっと凝った値に書き換えて特定の状態にしたり、関数を呼んでみて部分的に動作が正しそうかなど確認するときに重宝します。
このうち後者の「関数を呼んでみて」でハマる時があります。コード内ではたしかにその関数が呼べるのに、Debug Consoleからだけはそんな関数は無いと言われて呼べないことがよくあると思います。
これはSource Mapped SteppingをDisableにすると事情がわかったりします。JSになったコードを見ればわかると思うんですが、TypeScriptコンパイラによってimportされた関数の呼び出しがモジュール経由の呼び出しに書き換えられているんですね。そしてDebug ConsoleはあくまでJavaScriptレベルで動作するので、「そんな関数は無い」と言われてしまうのです。
以下のようにモジュール経由で呼び出してあげれば、めでたく実行できます。

debugging-ts-import-renaming.gif

(ちなみにこのfibは実装を正しく直している版です。)

ちょっと面倒くさいね。

Debug Consoleでasyncはつらいよ

さて、いろんな関数がDebug Consoleで呼べるようになってホクホク、と思っているところに水を差すつらいポイントをもうひとつ紹介します。
JavaScriptやTypeScriptでコードを書いていれば、productionレベルと言わずとも、ちょっと凝ったプログラムになるとAPIやDBのアクセスなどで非同期を扱うことはよくあると思います。そしてこの非同期とDebug Consoleの相性がとても悪いんですね。

まずtoplevel awaitがうまく動きません。現状、実際にawaitせず、しかもなぜかPromiseが帰ってきたように見えるようです。これはvscodeの将来のバージョンでは変わるかもしれません。(以前はそもそもtoplevelでawaitできないみたいなエラーが出た気がするので……。)

9b3a0fa5-e687-4604-8936-5d24a4b34fe6

(await afn('bar')は42を返すはずなんだけど?)

いちおう、対応策としては無理矢理ですがグローバル変数に代入するという技があります。たとえば上記コードなら afn('foo').then(v => {x = v}) のように書けば、とりあえずグローバル変数 x に42という値が入るはずです。
しかし残念ながら微妙にうまくいかないのです。以下のようにdebug consoleから実行されたコードは特別扱いされることなくmicrotaskが実行されるタイミングまで実行されないので、どこか実行されるタイミングまでステップ実行などで進めないといけません。気持ちはわかるが使いにくい。

debugging-ts-console-async-tick.gif

(実行タイミングがわかりやすいように直前のスクリーンショットから一部コードを変更しています。)

非同期とDebug Consoleの相性が悪いことがお分かりいただけたかと思います。まあ、何もできないよりはマシと考えて、がんばろう。

行儀悪いテストはつらいよ

最後はこちら。そんなこんなで色々やってテストを書くこともあるかと思います。そしてうっかりリソースリークしてしまうコードを書いてしまうこともあると思います。vscode-jestはautoRunがoffの時にそのようなテストに遭遇してしまうと、以下のようにRunnerがボイコットを始めてしまうのです。全然便利じゃなくなっちゃう!

debugging-ts-runner.gif

対処としては上のスクリーンショットでも行っていますが、Jest: Stop All RunnersしてすかさずJest: Start All Runnersする、つまりrunnerを再起動するといけるようになるようです。
再起動してまたボイコットさせないように、行儀悪いコードを直してから再起動するようにしましょう。

以上となります。重ねて、ここまでお読みいただきありがとうございました。
Have a happy debugging day!

Author image
About monja
よく焼きでお願いします。
expand_less