- TypeScript
TypeScriptをNode.jsで実行|tsx・ts-node・型ストリップ
TypeScriptをNode.jsで実行する方法は、「nodeにそのまま渡す(型ストリップ)」「tsx」「ts-node」「tscでJavaScriptにしてからnode」の4通りです。いまのNode.jsは、型の注釈を取り除くだけで動くコードなら、追加の道具なしで node ファイル名.ts と打つだけで実行できます。 型ストリップ(type stripping)とは、型の注釈を空白に置き換えて、残りをそのままJavaScriptとして動かす仕組みのことです。
確認環境:Ubuntu 24.04、Node.js 22.22.2 と 24.21.0、TypeScript 7.0.2(tsc)、tsx 4.23.15、ts-node 10.9.2(TypeScript 6.0.3 と組み合わせ)、@types/node 22.20.5。この記事では、同じ10個のファイルを4通りの方法で動かし、表示された結果とエラー文をそのまま載せます。出力に出てくるフォルダのパスは /home/taro/app に置き換えて表示しています。
TypeScriptをNode.jsで実行する方法の早見表
4通りの方法
| 方法 | コマンド | 追加で入れるもの | 型チェック | 向いている用途 |
|---|---|---|---|---|
| node で直接実行(型ストリップ) | node src/main.ts |
なし | しない | スクリプト、小さなツール、新しく作るサーバー |
| tsx | npx tsx src/main.ts |
tsx | しない | enum やパスエイリアスを使う既存のコード、開発中の実行 |
| ts-node | npx ts-node src/main.ts |
ts-node、typescript 6 系 | する | 実行のたびに型も確かめたい CommonJS のコード |
| tsc のあと node | npx tsc && node dist/main.js |
typescript | する(tsc が担当) | 配布するパッケージ、本番で動かすビルド |
書き方ごとの結果(Node.js 22.22.2 と 24.21.0 で同じ結果)
| 書き方 | node 直接 | tsx | ts-node(ESM) | tsc のあと node |
|---|---|---|---|---|
| 型注釈・interface だけ | 動く | 動く | 動く | 動く |
| enum | ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX | 動く | 動く | 動く |
| namespace(値を含む) | ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX | 動く | 動く | 動く |
| パラメータプロパティ | ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX | 動く | 動く | 動く |
| パスエイリアス(@/lib/add.js) | ERR_MODULE_NOT_FOUND | 動く | Cannot find package | tsc は通るが node で ERR_MODULE_NOT_FOUND |
| 拡張子なし import(./lib/add) | ERR_MODULE_NOT_FOUND | 動く | Cannot find module | tsc が TS2835 |
| 拡張子 .ts の import | 動く | 動く | 動く | 動く(.js に書き換えて出力) |
| 拡張子 .js の import | ERR_MODULE_NOT_FOUND | 動く | 動く | 動く |
| JSX(.tsx) | ERR_UNKNOWN_FILE_EXTENSION | 動く | 動く | 動く |
| type を付けない型の import | SyntaxError | 動く | 動く | 動く |
| 型エラーのあるコード | 動く(型は見ない) | 動く(型は見ない) | 止まる | tsc が TS2322 を表示 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptをNode.jsで実行|tsx・ts-node・型ストリップ」
確認に使ったファイルと設定
package.json には "type": "module" を指定し、tsconfig.json は次の内容にしました。
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"types": ["node"],
"rootDir": "./src",
"outDir": "./dist",
"jsx": "react",
"paths": { "@/*": ["./src/*"] },
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true
}
}
主なファイルは次のとおりです。
// src/lib/add.ts
export function add(a: number, b: number): number {
return a + b;
}
// src/basic.ts(型注釈・interface だけ)
interface Task {
id: number;
title: string;
}
const task: Task = { id: 1, title: "備品の発注" };
console.log(`basic: ${task.id} ${task.title}`);
// src/enum.ts
enum Status {
Todo,
Done,
}
console.log(`enum: ${Status.Done} ${Status[1]}`);
// src/namespace.ts
namespace Tax {
export const rate = 0.1;
export function withTax(price: number): number {
return Math.round(price * (1 + rate));
}
}
console.log(`namespace: ${Tax.withTax(1000)}`);
// src/param-prop.ts(パラメータプロパティ)
class Member {
constructor(private readonly name: string) {}
hello(): string {
return `こんにちは、${this.name}さん`;
}
}
console.log(`param-prop: ${new Member("佐藤").hello()}`);
// src/alias.ts(パスエイリアス)
import { add } from "@/lib/add.js";
console.log(`alias: ${add(1, 2)}`);
// src/noext.ts(拡張子なし)。ext-ts.ts は "./lib/add.ts"、ext-js.ts は "./lib/add.js" と書いたもの
import { add } from "./lib/add";
console.log(`noext: ${add(1, 2)}`);
// src/typeerror.ts(型エラー)
const count: number = "3";
console.log(`typeerror: ${count}`);
// src/view.tsx(JSX。自作の関数 h で文字列を組み立てる)
/** @jsx h */
function h(tag: string, props: Record<string, unknown> | null, ...children: unknown[]): string {
const attrs = Object.entries(props ?? {}).map(([k, v]) => ` ${k}="${v}"`).join("");
return `<${tag}${attrs}>${children.join("")}</${tag}>`;
}
declare global {
namespace JSX {
type Element = string;
interface IntrinsicElements {
[name: string]: Record<string, unknown>;
}
}
}
const view = <p class="note">jsx: ok</p>;
console.log(view);
方法1:nodeで直接実行する(型ストリップ)
node src/basic.ts
basic: 1 備品の発注
Node.js 22.22.2 と 24.21.0 のどちらも、フラグなし・警告なしでこの結果になりました。Node.js の公式ドキュメントによると、経緯は次のとおりです。
| Node.js の版 | 型ストリップの扱い(公式ドキュメントの記載) |
|---|---|
| 22.7.0 | --experimental-transform-types フラグを追加 |
| 22.18.0 以降 | フラグなしで有効(それより前は --experimental-strip-types を付ける) |
| 23.6.0 | フラグなしで有効 |
| 24.3.0 | 実験的機能の警告を表示しなくなった |
| 24.12.0 | 安定版(Stable)になった |
型を取り除くだけでは動かない構文
型ストリップは「消すだけ」なので、JavaScript のコードを新しく作る必要がある構文は扱えません。enum を渡したときの出力です(at で始まる行は省略)。
node src/enum.ts
file:///home/taro/app/src/enum.ts:1
> enum Status {
Todo,
Done,
> }
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
…(省略)…
code: 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'
}
Node.js v22.22.2
| ファイル | エラー文(22.22.2 と 24.21.0 で同じ) |
|---|---|
| enum.ts | SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode |
| namespace.ts | SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode |
| param-prop.ts | SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript parameter property is not supported in strip-only mode |
| view.tsx | TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".tsx" for /home/taro/app/src/view.tsx |
importは「.ts を付ける」「型には type を付ける」
Node.js は tsconfig.json を読まないので、import は実際のファイル名どおりに書きます。
| import の書き方 | 結果 | エラー文 |
|---|---|---|
"./lib/add.ts" |
動く(ext-ts: 3) |
— |
"./lib/add" |
止まる | Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/taro/app/src/lib/add' imported from /home/taro/app/src/noext.ts |
"./lib/add.js" |
止まる | Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/taro/app/src/lib/add.js' imported from /home/taro/app/src/ext-js.ts |
"@/lib/add.js"(パスエイリアス) |
止まる | Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/lib' imported from /home/taro/app/src/alias.ts |
もう1つ、型を import するときの書き方にも決まりがあります。interface と値の両方を export しているファイルを用意しました。
// src/lib/types.ts
export interface Task {
id: number;
title: string;
}
export const defaultTitle = "無題";
// src/import-type-ng.ts
import { Task, defaultTitle } from "./lib/types.ts";
const task: Task = { id: 1, title: defaultTitle };
console.log(`import: ${task.title}`);
file:///home/taro/app/src/import-type-ng.ts:1
import { Task, defaultTitle } from "./lib/types.ts";
^^^^
SyntaxError: The requested module './lib/types.ts' does not provide an export named 'Task'
Node.js v22.22.2
Task は型なので、実行時には存在しません。型の import には type を付けます。 type が付いた部分は、型ストリップで取り除かれます。
// src/import-type-ok.ts
import { type Task, defaultTitle } from "./lib/types.ts";
import: 無題
--experimental-transform-typesでenumなども動かす
--experimental-transform-types を付けると、Node.js が enum などを JavaScript に変換してから動かします。
node --experimental-transform-types src/enum.ts
enum: 1 Done
(node:31201) ExperimentalWarning: Transform Types is an experimental feature and might change at any time
| ファイル | フラグなし | --experimental-transform-types 付き |
|---|---|---|
| enum.ts | 止まる | enum: 1 Done |
| namespace.ts | 止まる | namespace: 1100 |
| param-prop.ts | 止まる | param-prop: こんにちは、佐藤さん |
| alias.ts | 止まる | 止まる(Cannot find package '@/lib') |
| noext.ts / ext-js.ts | 止まる | 止まる(ERR_MODULE_NOT_FOUND) |
| view.tsx | 止まる | 止まる(ERR_UNKNOWN_FILE_EXTENSION) |
| import-type-ng.ts | 止まる | 止まる(does not provide an export named 'Task') |
変わるのは構文の変換だけで、import の決まりと .tsx の扱いは同じです。24.21.0 でも実験的機能の警告が表示されました。
node_modulesの中の.tsは実行されない
node_modules の中に .ts を入口にしたパッケージを置いて import すると、次のエラーになりました。
Error [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]: Stripping types is currently unsupported for files under node_modules, for "file:///home/taro/app/node_modules/demo-pkg/index.ts"
公式ドキュメントにも、node_modules 配下の TypeScript ファイルは扱わないと書かれています。パッケージとして配るコードは、tsc で .js にしてから公開します。
erasableSyntaxOnlyで「消すだけで動く書き方」にそろえる
実行する前に気づけるように、tsc の erasableSyntaxOnly を有効にして、型チェックの段階で知らせてもらいます。
npx tsc --noEmit --erasableSyntaxOnly
src/enum.ts(1,6): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
src/namespace.ts(1,11): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
src/param-prop.ts(2,15): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
--locale ja を付けたときの文面です。
src/enum.ts(1,6): error TS1294: 'erasableSyntaxOnly' が有効な場合、この構文は使用できません。
src/namespace.ts(1,11): error TS1294: 'erasableSyntaxOnly' が有効な場合、この構文は使用できません。
src/param-prop.ts(2,15): error TS1294: 'erasableSyntaxOnly' が有効な場合、この構文は使用できません。
(同じ実行で、noext.ts の TS2835 と typeerror.ts の TS2322 も表示されます。ここでは TS1294 の行だけを抜き出しました。)
書き換え例:enum → as const のオブジェクト
// 直す前
enum Status {
Todo,
Done,
}
// 直したあと(src/erasable/enum.ts)
const Status = {
Todo: 0,
Done: 1,
} as const;
type Status = (typeof Status)[keyof typeof Status];
const current: Status = Status.Done;
console.log(`enum: ${current}`);
enum: 1
書き換え例:namespace → ふつうの関数とオブジェクト
// 直したあと(src/erasable/namespace.ts)
const rate = 0.1;
function withTax(price: number): number {
return Math.round(price * (1 + rate));
}
export const Tax = { rate, withTax };
console.log(`namespace: ${Tax.withTax(1000)}`);
namespace: 1100
書き換え例:パラメータプロパティ → フィールド宣言と代入
// 直したあと(src/erasable/param-prop.ts)
class Member {
private readonly name: string;
constructor(name: string) {
this.name = name;
}
hello(): string {
return `こんにちは、${this.name}さん`;
}
}
console.log(`param-prop: ${new Member("佐藤").hello()}`);
param-prop: こんにちは、佐藤さん
3つとも、Node.js 22.22.2 と 24.21.0 でフラグなしで動きました。
nodeで直接実行するときのtsconfig.json
Node.js の公式ドキュメントは、TypeScript 5.8 以降と、noEmit・target esnext・module nodenext・rewriteRelativeImportExtensions・erasableSyntaxOnly・verbatimModuleSyntax の組み合わせをすすめています。これに strict などを足して、次の設定で確かめました。
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"strict": true,
"types": ["node"],
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true
},
"include": ["src"]
}
verbatimModuleSyntax を有効にすると、type を付け忘れた import も tsc が見つけてくれます。
src/import-type-ng.ts(1,10): error TS1484: 'Task' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
src/import-type-ng.ts(1,10): error TS1484: 'Task' は型であり、'verbatimModuleSyntax' が有効であるときは、型のみのインポートを使用してインポートされる必要があります。
| オプション | 役割 | 見つけてくれるもの |
|---|---|---|
| erasableSyntaxOnly | 消すだけでは動かない構文をエラーにする | enum、namespace、パラメータプロパティ(TS1294) |
| verbatimModuleSyntax | import / export を書いたとおりに扱う | type の付け忘れ(TS1484) |
| allowImportingTsExtensions | import に .ts を書けるようにする | — |
| rewriteRelativeImportExtensions | tsc で出力するとき、相対パスの .ts を .js に書き換える | — |
| noEmit | 出力せず、型チェックだけ行う | — |
方法2:tsxで実行する
npm install --save-dev tsx
npx tsx src/enum.ts
enum: 1 Done
用意した10個のファイルは、tsx 4.23.15 ではすべて動きました(Node.js 22.22.2 と 24.21.0 の両方)。
| ファイル | tsx の出力 |
|---|---|
| basic.ts | basic: 1 備品の発注 |
| enum.ts | enum: 1 Done |
| namespace.ts | namespace: 1100 |
| param-prop.ts | param-prop: こんにちは、佐藤さん |
| alias.ts(tsconfig.json の paths を使う) | alias: 3 |
| noext.ts / ext-ts.ts / ext-js.ts | noext: 3 / ext-ts: 3 / ext-js: 3 |
| view.tsx | JSX が h の呼び出しに変換され、p 要素の文字列が表示された |
| import-type-ng.ts(type なし) | import: 無題 |
| typeerror.ts | typeerror: 3 |
tsx は型チェックをしません。 typeerror.ts は、number と注釈した変数に文字列を入れていますが、そのまま実行されました。型の確認は npx tsc --noEmit と組み合わせます。
方法3:ts-nodeで実行する
ts-node は、TypeScript 本体の API を使って変換と型チェックを行います。そのため、組み合わせる typescript の版が結果を左右します。
TypeScript 7.0.2とは組み合わせられない
typescript 7.0.2 と ts-node 10.9.2 を同じフォルダに入れて実行した結果です。
npx ts-node src/basic.ts
/home/taro/app/node_modules/ts-node/dist/configuration.js:91
const { fileExists = ts.sys.fileExists, readFile = ts.sys.readFile, skipProject = index_1.DEFAULTS.skipProject, project = index_1.DEFAULTS.project, tsTrace = index_1.DEFAULTS.tsTrace, } = rawApiOptions;
^
TypeError: Cannot read properties of undefined (reading 'fileExists')
TypeScript 7.0 には、プログラムから呼ぶための API が入っていません。ts-node を使うフォルダには、typescript の 6 系を入れます。 以降は TypeScript 6.0.3 との組み合わせです。
ESMのプロジェクト:"type": "module" のとき
package.json に "type": "module" があるフォルダで npx ts-node src/enum.ts と打つと、次のエラーになりました。
SyntaxError: TypeScript enum is not supported in strip-only mode
これは Node.js の型ストリップのエラー文です。つまり、この打ち方では Node.js 自身が .ts を処理していました。ts-node に処理させるには、ローダーとして指定します。
node --loader ts-node/esm src/enum.ts
(node:403) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
…(省略)…
enum: 1 Done
| ファイル | node --loader ts-node/esm の結果 |
|---|---|
| basic / enum / namespace / param-prop | 動く |
| ext-ts.ts / ext-js.ts / view.tsx / import-type-ng.ts | 動く |
| alias.ts | Error: Cannot find package '@/lib' imported from /home/taro/app/src/alias.ts |
| noext.ts | Error: Cannot find module '/home/taro/app/src/lib/add' imported from /home/taro/app/src/noext.ts |
| typeerror.ts | 止まる(実行されず、エラーの中身は表示されなかった) |
CommonJSのプロジェクト:"type" を書かないとき
package.json から type を外し、tsconfig.json を "module": "commonjs", "moduleResolution": "bundler" に変えたフォルダでは、npx ts-node ファイル名 の形で ts-node が処理しました。
| ファイル | npx ts-node の結果 |
|---|---|
| basic / enum / namespace / param-prop | 動く |
| noext.ts | 動く(noext: 3) |
| view.tsx | 動く(ファイル末尾に export {}; を足してモジュール扱いにした場合) |
| alias.ts | Error: Cannot find module '@/lib/add.js' |
| ext-ts.ts / ext-js.ts | Error: Cannot find module './lib/add.js' |
| typeerror.ts | 型エラーを表示して止まる |
型エラーのときの出力です。
TSError: ⨯ Unable to compile TypeScript:
src/typeerror.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'.
diagnosticCodes: [ 2322 ]
}
実行のたびに型エラーで止めてくれるのが、ts-node の特徴です。 型チェックを省いて速く動かしたいときは --transpile-only を付けます(typeerror: 3 と表示されました)。
方法4:tscでJavaScriptにしてからnodeで実行する
npx tsc
node dist/enum.js
enum: 1 Done
tsc は、enum やパラメータプロパティを JavaScript のコードに変換します。dist/enum.js と dist/param-prop.js の中身です。
var Status;
(function (Status) {
Status[Status["Todo"] = 0] = "Todo";
Status[Status["Done"] = 1] = "Done";
})(Status || (Status = {}));
console.log(`enum: ${Status.Done} ${Status[1]}`);
export {};
class Member {
name;
constructor(name) {
this.name = name;
}
hello() {
return `こんにちは、${this.name}さん`;
}
}
console.log(`param-prop: ${new Member("佐藤").hello()}`);
export {};
10個のファイルをまとめて tsc にかけたときの出力は、次の2行でした。
src/noext.ts(1,21): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './lib/add.js'?
src/typeerror.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'.
| ファイル | tsc | node dist/… |
|---|---|---|
| basic / enum / namespace / param-prop / view | 通る | 動く |
| ext-ts.ts | 通る(import を "./lib/add.js" に書き換えて出力) | 動く |
| ext-js.ts | 通る | 動く |
| import-type-ng.ts | 通る(Task の import を取り除いて出力) | 動く |
| noext.ts | TS2835 | Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/taro/app/dist/lib/add' imported from /home/taro/app/dist/noext.js |
| alias.ts | 通る("@/lib/add.js" のまま出力) | Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/lib' imported from /home/taro/app/dist/alias.js |
| typeerror.ts | TS2322(.js は出力される) | 動く(typeerror: 3) |
paths は型チェックのための設定で、tsc は出力する import を書き換えません。 パスエイリアスを使うコードを tsc の出力のまま node で動かすときは、相対パスで書くのが確実です。
用途別の選び方早見表
| やりたいこと | 選ぶ方法 | 理由 |
|---|---|---|
| 新しく書くスクリプトやツールを、道具を増やさずに動かす | node で直接実行 | 追加のインストールなし。erasableSyntaxOnly で書き方をそろえられる |
| enum・namespace・パラメータプロパティを使っているコードを、そのまま動かす | tsx | 10個のファイルがすべて動いた |
| tsconfig.json の paths(@/ など)を使っている | tsx | 4通りのうち、パスエイリアスを解決できたのは tsx だけだった |
| .tsx(JSX)を Node.js で動かす | tsx か、tsc のあと node | node 直接では .tsx が ERR_UNKNOWN_FILE_EXTENSION になる |
| 実行のたびに型エラーで止めたい | ts-node(CommonJS、TypeScript 6 系) | TS2322 を表示して止まった |
| TypeScript 7 の tsc を使っている | node 直接 か tsx + tsc --noEmit |
ts-node 10.9.2 は 7.0.2 と組み合わせられなかった |
| npm で配るパッケージを作る | tsc のあと node | node_modules 配下の .ts は型ストリップの対象外 |
| 本番で動かす | tsc のあと node、または node で直接実行 | 実行時に必要な道具が少ない |
どの方法でも、型チェックは npx tsc --noEmit で別に行うと考えておくと迷いません(ts-node だけは実行時にも行います)。
コピペできるpackage.jsonのscripts
nodeで直接実行する構成(追加の道具は typescript だけ)
{
"type": "module",
"scripts": {
"start": "node src/main.ts",
"dev": "node --watch src/main.ts",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "^22.20.5",
"typescript": "^7.0.2"
}
}
npm run start
npm run dev
main: 1 備品の発注
Completed running 'src/main.ts'. Waiting for file changes before restarting...
node --watch は、ファイルを保存するたびに実行し直します。tsconfig.json は「nodeで直接実行するときのtsconfig.json」の内容を使います。
tsxで実行する構成
{
"type": "module",
"scripts": {
"start": "tsx src/main.ts",
"dev": "tsx watch src/main.ts",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "^22.20.5",
"tsx": "^4.23.15",
"typescript": "^7.0.2"
}
}
tscでビルドしてから実行する構成
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/main.js",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "^22.20.5",
"typescript": "^7.0.2"
}
}
3つの構成の start と、node --watch、tsx watch は、確認環境で main.ts を実行できることを確かめています。
よくある質問
Q. tsxとts-nodeの違いは何ですか? A. いちばんの違いは型チェックです。tsx は型を見ずに変換して実行し、ts-node は実行前に型チェックをして、エラーがあれば止まります。今回の確認では、tsx は paths や拡張子なしの import も解決し、TypeScript 7.0.2 と同じフォルダで動きました。ts-node 10.9.2 は TypeScript の API を使うため、6 系との組み合わせで動きました。
Q. node で直接実行できるなら、tsc はいらなくなりますか?
A. 型チェックのために引き続き使います。Node.js は型の注釈を取り除くだけで、型が合っているかは調べません。typeerror.ts が typeerror: 3 と表示されて最後まで動いたのが、その例です。npx tsc --noEmit を scripts の typecheck に登録しておくと安心です。
Q. import の拡張子は .ts と .js のどちらを書けばよいですか? A. node で直接実行するなら .ts です(.js や拡張子なしは ERR_MODULE_NOT_FOUND)。tsc で出力して動かす場合は、.js と書くか、.ts と書いて rewriteRelativeImportExtensions で .js に書き換えてもらいます。後者なら、直接実行と tsc のあと node の両方で同じソースが動きました。
Q. enumを使っているコードをnodeで直接動かすには? A. 3つの道があります。as const のオブジェクトに書き換える(フラグなしで動く)、--experimental-transform-types を付ける(実験的機能の警告が出る)、tsx を使う、のいずれかです。新しく書くコードなら、erasableSyntaxOnly を有効にして最初から書き換え後の形で書くと、どの方法でも動きます。
まとめ
- Node.js 22.22.2 と 24.21.0 は、node ファイル名.ts だけで TypeScript を実行できる(型ストリップ)。型チェックはしない
- 型ストリップで動かないのは enum・namespace・パラメータプロパティ・.tsx・パスエイリアス・拡張子なし import。エラーは ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX や ERR_MODULE_NOT_FOUND
- erasableSyntaxOnly を有効にすると、tsc が TS1294 で事前に知らせてくれる。enum は as const、パラメータプロパティはフィールド宣言に書き換える
- import は .ts を付け、型には type を付ける。verbatimModuleSyntax を有効にすると TS1484 で気づける
- tsx は10個のファイルがすべて動いた。paths と JSX を使うコードに向く。型チェックは tsc --noEmit と組み合わせる
- ts-node は実行時に型エラーで止まる。TypeScript 7.0.2 とは組み合わせられず、6 系と使う
- 配布するパッケージや本番用は tsc のあと node。paths は出力で書き換わらないので相対パスで書く
参考・出典
- Node.js 公式ドキュメント「Modules: TypeScript」(v24.x 系の原稿):https://github.com/nodejs/node/blob/v24.x/doc/api/typescript.md
- Node.js 公式「Running TypeScript Natively」:https://nodejs.org/en/learn/typescript/run-natively
- Announcing TypeScript 7.0(Microsoft TypeScript 公式ブログ):https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptをNode.jsで実行|tsx・ts-node・型ストリップ」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。