CLOVER🍀

That was when it all began.

TypeScriptコードを直接実行したい(ts-node、esbuild-register、tsx)

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

Node.jsでソースコードを書く時は基本的にTypeScriptにしたいのですが、時々ちょっとしたスクリプトを書きたい時もあったりします。
そういう時は、ちゃんとした設定などは特に用意せず、さっと書いてそのまま実行したいのですが、そういう時はどういう手段が
あるのかなと思って、調べてみることにしました。

やりたいこと

TypeScriptファイルを実行時に直接トランスパイル、実行したいわけですね。

選択肢としては、ts-node、esbuild-register、tsxがあるようです。

今回はそれぞれをあまり深追いせず、さらっと見ていく感じにします。求めている用途がそういう傾向なので。
ただ、実行速度は気になるので、そこは動作時に合わせて見ていきたいと思います。

ts-node

ts-nodeは、Node.jsのTypeScript実行エンジン、REPLです。型チェックも行われます。

WebサイトとGitHubリポジトリはこちら。

ts-node | ts-node

GitHub - TypeStrong/ts-node: TypeScript execution and REPL for node.js

使い方はこちらで、ts-nodeというコマンドにTypeScriptファイルを渡して実行します。

Usage | ts-node

また、SWCと組み合わせることもできるようです。

SWC | ts-node

esbuild-register

esbuild-registerは、esbuildを使ってTypeScriptファイルを実行時にトランスパイルします。

GitHub - egoist/esbuild-register: Transpile JSX, TypeScript and esnext features on the fly with esbuild

Node.jsの-r(--require)オプションと組み合わせて使います。

tsconfig.jsonが存在する場合、以下のオプションのみ使用するようです。また、型チェックは行いません。

It will use jsxFactory, jsxFragmentFactory and target options from your tsconfig.json

tsx

tsxは、TypeScriptファイルおよびESMファイルを実行できるものです。

GitHub - privatenumber/tsx: ⚡️ TypeScript Execute: Node.js enhanced to run TypeScript & ESM

tsxと書くとTSX(JSX)を連想しますが、こちらはTypeScript Executeの略でtsxだそうです。

REPLがあったり、Watch modeがあったりと機能がいろいろあるようです。

型チェックは行いません。有効なtsconfig.jsonのプロパティは、esbuildに従います。

Content Types / TypeScript / TypeScript caveats / Only certain tsconfig.json fields are respected

見ていくのはこれくらいにして、実際に動かしてみようと思います。

環境

今回の環境は、こちら。

$ node --version
v18.18.2


$ npm --version
9.8.1

お題

ソースコードが複数であっても読めることくらいは確認しておこうかなと思ったので、とりあえず以下の2つのファイルを用意。

index.ts

import { message } from "./func";

console.log(message('TypeScript'));

func.ts

export function message(word: string): string {
  return `Hello ${word}`;
}

こちらを、ts-node、esbuild-register、tsxそれぞれで実行していきます。

ts-node

ts-nodeのインストール。TypeScriptに依存しているので、合わせてインストールしておきます。

$ npm i -D ts-node typescript

バージョン。

  "devDependencies": {
    "ts-node": "^10.9.1",
    "typescript": "^5.3.2"
  }

実行。ts-nodeコマンドを使います。

$ npx ts-node index.ts
Hello TypeScript


$ time npx ts-node index.ts
Hello TypeScript

real    0m1.264s
user    0m2.270s
sys     0m0.130s

動作しましたね。速度はこれだけで1秒を超えました。

ts-node+SWC

オマケで、ts-node+SWCも試してみましょう。インストール。

$ npm i -D ts-node typescript @swc/core @swc/helpers regenerator-runtime

バージョン。

  "devDependencies": {
    "@swc/core": "^1.3.99",
    "@swc/helpers": "^0.5.3",
    "regenerator-runtime": "^0.14.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.3.2"
  }

実行。--swcオプションを追加します。

$ npx ts-node --swc index.ts
Hello TypeScript


$ time npx ts-node --swc index.ts
Hello TypeScript

real    0m0.653s
user    0m0.689s
sys     0m0.073s

ts-nodeのみの時と比べて、実行速度が半分くらいになりました。

esbuild-register

続いて、esbuild-register。インストール。

$ npm i -D esbuild esbuild-register

バージョン。

  "devDependencies": {
    "esbuild": "^0.19.7",
    "esbuild-register": "^3.5.0"
  }

実行。-rオプションに、esbuild-registerを指定します。

$ node -r esbuild-register index.ts
Hello TypeScript


$ time node -r esbuild-register index.ts
Hello TypeScript

real    0m0.145s
user    0m0.122s
sys     0m0.024s

速いですね。ts-nodeの10分の1くらいです。

tsx

最後はtsxです。インストール。

$ npm i -D tsx

バージョン。

  "devDependencies": {
    "tsx": "^4.2.0"
  }

実行。tsxコマンドで実行します。

$ npx tsx index.ts
Hello TypeScript


$ time npx tsx index.ts
Hello TypeScript

real    0m0.586s
user    0m0.613s
sys     0m0.084s

ts-nodeに比べると速いですが、esbuild-registerほどではないみたいです。

オマケ

最後にオマケで、npmパッケージを使う例も試しておきましょう。このパートは、ts-node、esbuild-register、tsxのそれぞれの
パターンを簡略化して書くことにします。

使うパッケージは、Expressとします。この用途でExpressはちょっと違うんじゃないのかなという気がしますが、コマンドライン系の
パッケージには詳しくないので…。

インストール。

$ npm i express
$ npm i -D @types/express

バージョン。

  "devDependencies": {
    "@types/express": "^4.17.21",
    ...
  },
  "dependencies": {
    "express": "^4.18.2"
  }

ソースコードは、こちらにしました。ExpressのHello Worldですね。

app.ts

import express, { Express } from 'express';

const app: Express = express();
const port: number = 3000;

app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(port, () => {
  console.log(`start server, port = ${port}`);
});

Express "Hello World" example

起動の様子は、それぞれ書いていきます。

## ts-node
$ npx ts-node app.ts
start server, port = 3000


## ts-node+SWC
$ npx ts-node --swc app.ts
start server, port = 3000


## esbuild-register
$ node -r esbuild-register app.ts
start server, port = 3000


## tsx
$ npx tsx app.ts
start server, port = 3000

結果はいずれも同じです。

$ curl localhost:3000
Hello World

おわりに

TypeScriptコードを直接実行する方法として、ts-node、esbuild-register、tsxを試してみました。

ちゃんとしたものを作る場合は設定などをしっかり用意すると思うのですが、こうやってライトに実行できる方法も知っておくと便利
なのかなという気がします。

速度的にはesbuild-registerが良さそうですが、型チェックがないのがネックですね。とはいえ、そのあたりをこだわり始めると
そもそもこの方法を試している意味が…となっていくので、ちゃんとしたプロジェクト設定にするコード量の分岐点は
割と低いのではないのかなと思ったりしますね。