⤴️

ts-node の代わりに esbuild-register を使ってスピードアップ

commits3 min read 3

ts-node では実行時に型チェックをしてくれますが、 esbuild では型チェックそのものをスキップします。型の整合性などは別途 npx tsc --noEmit や IDE などで担保してください。

参考
https://twitter.com/mryhryki/status/1383574194764488707

ts-node でも型チェックを外すオプションがあることをコメントで教えてもらいました。型チェックをかけない esbuild と同様の条件としたほうが、より平等な計測結果になりますので、計測し直しました。

参考
https://github.com/TypeStrong/ts-node#typechecking

Node.js で TypeScript をトランスパイルしながら実行できる、 esbuild-register というパッケージがあります。

https://github.com/egoist/esbuild-register

非常に速い esbuild を使いながら雑に TypeScript が実行できちゃう頼もしいパッケージです。

今までのメインプレイヤーであった ts-node よりも速いです。次の小さなスクリプトでも 1.5 倍程度の速度が出ています。

function wait(duration: number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {resolve(true)}, duration)
  })
}

async function main({duration}: {duration: number}) {
  await wait(duration)
  console.log('finished')
}

main({
  duration: 0
})

ベンチマーク結果が次です。

$ hyperfine --warmup 3 \
    'npm run ts-node index.ts' \
    'npm run ts-node-skip-check index.ts' \
    'npm run esbuild index.ts'
Benchmark #1: npm run ts-node index.ts
  Time (mean ± σ):      1.241 s ±  0.022 s    [User: 1.731 s, System: 0.190 s]
  Range (min … max):    1.214 s …  1.294 s    10 runs

Benchmark #2: npm run ts-node-skip-check index.ts
  Time (mean ± σ):     637.9 ms ±   3.7 ms    [User: 531.9 ms, System: 133.2 ms]
  Range (min … max):   632.2 ms … 643.9 ms    10 runs

Benchmark #3: npm run esbuild index.ts
  Time (mean ± σ):     405.5 ms ±   3.7 ms    [User: 323.2 ms, System: 116.3 ms]
  Range (min … max):   401.2 ms … 411.9 ms    10 runs

Summary
  'npm run esbuild index.ts' ran
    1.57 ± 0.02 times faster than 'npm run ts-node-skip-check index.ts'
    3.06 ± 0.06 times faster than 'npm run ts-node index.ts'

表にまとめてみました。単位は秒です。

tool user system total
ts-node 1.731 0.190 1.241
ts-node type check なし 0.532 0.133 0.638
esbuild-register 0.323 0.116 0.406

お手元で試したい場合は clone すれば試せるものを用意したので esbuild の速さを体感してみてください。

ts-node を使っているプロジェクトでは切り替えると如実に効率が上がります。ぜひ切り替えてみてください。

esbuild-register の仕組み

伝えたいことは以上ですべてですが、 esbuild-register がどのように動くかという点を書いておきます。

esbuild-register を用いてスクリプトを実行する場合、次のコマンドを叩きます。

node --require esbuild-register /path/to/your/script.ts

Node.js の --require オプションを使っていますね。これは雑に説明すると、指定したモジュールを読み込んでから本筋のスクリプトを実行するためのものです。

esbuild-register はこれを用いて esbuild によるトランスパイル処理を仕込んでいるのですね。

Mocha によるテスト実行時によく使ったオプションですが、見なくなって久しいので最近の方向けに説明を書いてみました。

GitHubで編集を提案

Discussion

ts-node について、トランスパイルの時間だけではなく typecheck も含めた時間を比較してしまっているように見えます。

tsconfig.json

"ts-node": {
  "transpileOnly": true,
}

を追加する、または --transpile-only オプションを渡すなどして、ts-node もトランスパイルのみにした上で比較するのがより適切だと思いました。

コメントでのご指摘、ありがとうございます
--transpile-only オプションを知らなかったので、使ったバージョンでの検証結果にアップデートしました
また、ベンチマークをとるのに hyperfine というコマンドを使うように修正しています

ちなみに、指摘とかではなくただの補足情報というかちょっとした豆知識なんですが、最近リリースされたv10からswcによるトランスパイルがサポートされています。参考までに👍

ログインするとコメントできます