ts-node で TypeScript + node をサクッと実行する

node でさっくりと TypeScript を実行したいときに、babel なり webpack なりで transpile して実行するのは不便です。しかも型が考慮されなくなってしまいますね。

そういうときは ts-node を使うことで、バベってファイルを生成してから食わせずとも、そのまま実行できるので手軽です。型も考慮されます。


使い方

さっくりと依存を追加します:

npm install --save typescript ts-node

tsconfig.json もついでに生成しておきます:

> ./node_modules/.bin/tsc --init

message TS6071: Successfully created a tsconfig.json file.

試しに適当なコードを書いてみます:


src/main.ts

const main = () => {

console.log('It works!');
};

main();


動かしてみます:

> ./node_modules/.bin/ts-node src/main.ts

It works!

いい感じにすぐ実行されました。

もちろん型が誤っている場合もきちんとエラーになりますし、元のファイルの情報が表示されます:


src/main.ts

const main = (str: string) => {

console.log('It works!');
};

main(1111);


TSError: ⨯ Unable to compile TypeScript:

src/main.ts:5:6 - error TS2345: Argument of type '1111' is not assignable to parameter of type 'string'.

5 main(1111);
~~~~

:thumbsup:


エイリアス

import some from '../../a/b.ts' とかになってややこしいので、パスのエイリアスを登録しておくことが結構あると思います:


tsconfig.json

{

"compilerOptions": {
// ...
"baseUrl": "./",
"paths": {
"#/*": ["src/*"]
},
// ...
}
}

この例では


src/x.ts

import some from '#/a/b.ts';

// <=> import some from './a/b.ts';


src/i/j/k.ts

import some from '#/a/b.ts';

// <=> import some from '../../a/b.ts';

となります。捗りますね。

これで import するスクリプトの階層にかかわらず、ソースルートディレクトリからのパスとして記述できるので、すっきりとしてわかりやすくなります。VSCode でもエイリアスが効いていい感じです。

Vue でも @/components/a.ts で参照できるようなエイリアスになってますが、似たような感じですね。 (あれは webpack のエイリアスのようですが)。


例として次のようなファイル構成にしてみましょう:


src/mod-a/build.ts

export default (): string => 'mod-a';



src/mod-b/repeat.ts

import build from '#/mod-a/build';

export default (number: number): string => {
let queue = [];
const value = build();
for (let i = 0; i < number; ++i) {
queue.push(value);
}

return queue.join(' | ');
}



src/main.ts

import repeat from "#/mod-b/repeat";

const main = () => {
console.log(repeat(5));
};

main();


さて、これでエイリアスを使って読み込めたので

実行してみたいと思います:

> ./node_modules/.bin/ts-node src/main.ts

Error: Cannot find module '#/mod-b/repeat'
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)
at Function.Module._load (internal/modules/cjs/loader.js:507:25)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
at Object.<anonymous> (/Users/user/Sandbox/my-ts-project/src/main.ts:1:1)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Module.m._compile (/Users/user/Sandbox/my-ts-project/node_modules/ts-node/src/index.ts:473:23)
at Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Object.require.extensions.(anonymous function) [as .ts] (/Users/user/Sandbox/my-ts-project/node_modules/ts-node/src/index.ts:476:12)
at Module.load (internal/modules/cjs/loader.js:599:32)

しかしながら、これは import が解決できずエラーになります。なぜでしょうか。

この問題は

tsconfig-paths を追加して、require してあげることで解決できます:

npm install --save tsconfig-paths

> ./node_modules/.bin/ts-node --files -r tsconfig-paths/register src/main.ts

mod-a | mod-a | mod-a | mod-a | mod-a

tsconfig-pathsrequire してあげることで、tsconfig.json で定義したエイリアスで import を解決できるようになりました。

ただ、毎回入れるのは面倒なので package.jsonscripts に追加して npm start 一発で実行できるようにすると楽でいいです:


package.json

{

// ...
"scripts": {
"start": "ts-node --files -r tsconfig-paths/register",
"test": "echo \"Error: no test specified\" && exit 1"
}
}

npm start src/main.ts

:thumbsup:


型定義

自分で型定義 (*.d.ts) を持っている場合は、これも tsconfig.json に追加します:


tsconfig.json

{

"compilerOptions": {
// ...
"typeRoots": ["./src/types"],
// ...
}


テスト

テストに関しては別エントリで記述しました。同じくバベらずとも ts-jest を使うことで型を活かしながら簡単にテストができます:

👉 TypeScript のテストを Jest (ts-jest) でやってみる


これで TypeScript で手元の適当なコードを動かしたいときから、バッチ等でゴリゴリ動かしたいときまで簡単に対応できます。