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.
試しに適当なコードを書いてみます:
const main = () => {
console.log('It works!');
};
main();
動かしてみます:
> ./node_modules/.bin/ts-node src/main.ts
It works!
いい感じにすぐ実行されました。
もちろん型が誤っている場合もきちんとエラーになりますし、元のファイルの情報が表示されます:
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);
~~~~
エイリアス
import some from '../../a/b.ts'
とかになってややこしいので、パスのエイリアスを登録しておくことが結構あると思います:
{
"compilerOptions": {
// ...
"baseUrl": "./",
"paths": {
"#/*": ["src/*"]
},
// ...
}
}
この例では
import some from '#/a/b.ts';
// <=> import some from './a/b.ts';
import some from '#/a/b.ts';
// <=> import some from '../../a/b.ts';
となります。捗りますね。
これで import
するスクリプトの階層にかかわらず、ソースルートディレクトリからのパスとして記述できるので、すっきりとしてわかりやすくなります。VSCode でもエイリアスが効いていい感じです。
Vue でも @/components/a.ts
で参照できるようなエイリアスになってますが、似たような感じですね。 (あれは webpack のエイリアスのようですが)。
例として次のようなファイル構成にしてみましょう:
export default (): string => 'mod-a';
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(' | ');
}
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-paths
を require
してあげることで、tsconfig.json
で定義したエイリアスで import
を解決できるようになりました。
ただ、毎回入れるのは面倒なので package.json
の scripts
に追加して npm start
一発で実行できるようにすると楽でいいです:
{
// ...
"scripts": {
"start": "ts-node --files -r tsconfig-paths/register",
"test": "echo \"Error: no test specified\" && exit 1"
}
}
npm start src/main.ts
型定義
自分で型定義 (*.d.ts
) を持っている場合は、これも tsconfig.json
に追加します:
{
"compilerOptions": {
// ...
"typeRoots": ["./src/types"],
// ...
}
テスト
テストに関しては別エントリで記述しました。同じくバベらずとも ts-jest
を使うことで型を活かしながら簡単にテストができます:
👉 TypeScript のテストを Jest (ts-jest) でやってみる
これで TypeScript で手元の適当なコードを動かしたいときから、バッチ等でゴリゴリ動かしたいときまで簡単に対応できます。