Node.js で TypeScript をネイティブ実行する完全ガイド — ts-node 卒業への道【2026年版】
この記事の内容は 2026年3月時点 のものです。Node.js のバージョンや仕様は今後変わる可能性があります。最新情報は Node.js 公式ドキュメント を確認してください。
はじめに — ts-node、そろそろ卒業できるかもしれない
ちょっとしたスクリプトを TypeScript で書きたいだけなのに、package.json の devDependencies が膨らんでいく経験、ありませんか。
{
"devDependencies": {
"typescript": "^5.4.0",
"ts-node": "^10.9.0",
"@types/node": "^20.0.0"
}
}
CI でも npm install が必要で、プロジェクトが増えるたびにこのセットが繰り返される...。「TypeScript 使いたいだけなのにな」と思ったこと、一度はあるんじゃないかと思うんです。
実は、Node.js がここ1〜2年でかなり静かに変化していて、ts-node も tsx も入れずに .ts ファイルを直接実行できる ようになってきています。
この記事では、Node.js のネイティブ TypeScript 実行機能を使って開発環境をシンプルにする方法と、知っておくべき落とし穴を丁寧に整理してみます。
前提:使用する Node.js のバージョン確認
まず自分の環境を確認してください。
node --version
この機能が使えるのは Node.js v22.7.0 以降 です。それ未満の場合は、volta や nvm などのバージョン管理ツールでアップグレードしておくことをおすすめします。
# volta を使う場合
volta install node@22
# nvm を使う場合
nvm install 22
nvm use 22
まず動かしてみる — 最速 Hello World
難しい話は後にして、まず動かしてみましょう。
以下の TypeScript ファイルを用意します。
// hello.ts
const greet = (name: string): string => {
return `Hello, ${name}!`;
};
console.log(greet("TypeScript"));
実行してみます。
# Node.js v22.7.0 〜 v23.5.x の場合
node --experimental-strip-types hello.ts
# Node.js v23.6.0 以降の場合(フラグ不要)
node hello.ts
Hello, TypeScript!
tsc も ts-node も何もインストールしていません。それでも動きます。
これが Node.js のネイティブ TypeScript 実行 です。シンプルでしょう。
バージョン別早見表
自分の環境に合ったコマンドを選ぶために、バージョン別の対応状況を整理しておきます。
| Node.js バージョン | TS ネイティブ実行 | 必要なフラグ |
|---|---|---|
| v22.7.0 〜 v22.17.x | 実験的サポート | --experimental-strip-types |
| v22.18.0 以降(予定) | 安定サポート | 不要(予定) |
| v23.0.0 〜 v23.5.x | 実験的サポート | --experimental-strip-types |
| v23.6.0 以降 | 安定サポート | 不要 |
| v24.x 以降 | 完全サポート | 不要 |
現在(2026年3月時点)は LTS の v22 系と最新の v24 系が主流です。LTS を使っているなら v22.18 以降か v24 にアップグレードすると、フラグなしで動かせます。
「型チェックしない」ってどういう意味?
ここが一番誤解されやすいポイントです。
Node.js のネイティブ実行は「 型を剥がす(strip types) 」だけです。型チェックはしません。
どういうことかというと、TypeScript の型情報(: string とか as const とか)を全部取り除いて、残った JavaScript として実行するというイメージです。
// TypeScript で書いたコード
const add = (a: number, b: number): number => {
return a + b;
};
上記が内部的には以下のように変換されて実行されます。
// 型情報が剥がされた状態(内部処理のイメージ)
const add = (a, b) => {
return a + b;
};
ここで重要な点があります。
型が間違っていても、実行時エラーにはなりません。
// TypeScript 的には型エラーだが、実行は通る
const add = (a: number, b: number): number => {
return a + b;
};
console.log(add("hello" as any, "world" as any)); // "helloworld" と出力される
「型チェックなしで大丈夫なの?」という不安はもっともです。そこで 役割分担 を意識するのが大事で、こういう感じで考えるといいかもしれません。
| 役割 | 担当 |
|---|---|
| 型チェック(リアルタイム) | VSCode などのエディタ |
| 型チェック(CIなど) | tsc --noEmit |
| 実行 |
node(型を剥がしてJSとして動かす) |
型の正確性はエディタと tsc に任せて、Node.js はシンプルに実行だけする。この分業がこの機能の設計思想です。
CI/CD での注意点: ネイティブ実行だけでは型チェックが走りません。本番環境やチーム開発では、必ず tsc --noEmit をCIに組み込んでください。
ts-node / tsx / tsc+node との比較
「じゃあ従来のツールと何が違うの?」という比較をしておきます。
| ツール | 型チェック | 実行速度 | 外部インストール | enum対応 | 向いているシーン |
|---|---|---|---|---|---|
| node(ネイティブ) | なし | ⚡ 高速 | 不要 | 要フラグ | スクリプト、CLIツール、試作 |
| ts-node | あり | 🐢 やや遅め | 必要 | 完全対応 | 型チェックしながら実行 |
| tsx | なし(esbuild) | ⚡ 高速 | 必要 | 対応 | watch mode、高速な開発ループ |
| tsc + node | あり | 🔨 2ステップ | 必要 | 完全対応 | 本番ビルド、CI/CD |
ネイティブ実行が向いているのは、こういうシーンです。
- ちょっとしたスクリプトを書きたいとき
- 依存関係を最小限にしたいとき(コンテナや Serverless Functions など)
- TypeScript を試してみたいとき
逆に、enum を多用しているプロジェクトや、型チェックも含めて厳密に実行確認したい場合は ts-node や tsx を選ぶ方が安心です。
落とし穴と回避策
落とし穴 1:enum が動かない
これは一番ハマりやすいポイントです。
// ❌ これは strip-only mode では動かない
enum Direction {
Up,
Down,
Left,
Right,
}
実行すると次のエラーが出ます。
TypeError: TypeScript enum is not supported in strip-only mode
解決策 A: --experimental-transform-types を追加する
node --experimental-strip-types --experimental-transform-types app.ts
ただしこのフラグも experimental です。本番用途ではなく、開発・試作段階での利用を推奨します。
解決策 B: as const で代替する(推奨)
これが現代的な書き方で、enum を使わずに同等のことができます。
// ✅ as const を使ったモダンな書き方
const Direction = {
Up: "Up",
Down: "Down",
Left: "Left",
Right: "Right",
} as const;
// 型として使う
type Direction = typeof Direction[keyof typeof Direction];
// 使用例
const move = (dir: Direction): void => {
console.log(`Moving ${dir}`);
};
move(Direction.Up); // "Moving Up"
as const で定義すると、Direction.Up などが文字列リテラル型として推論されます。enum と似た使い勝手で、型安全性も担保できます。TypeScript 公式でも enum より as const が推奨される傾向にあります。
落とし穴 2:type: "module" との共存
package.json に "type": "module" がある場合、.ts ファイルは ESモジュールとして扱われます。
{
"type": "module"
}
この場合、require() は使えません。import/export 構文を使うように統一してください。
// ✅ ESモジュール構文
import { readFileSync } from "fs";
export const readConfig = () => { /* ... */ };
落とし穴 3:パスの .js 拡張子問題
TypeScript の import 文でファイルパスを指定するとき、.ts ではなく .js で書くルールがあります。
// ✅ TypeScript の慣習では .js を書く
import { helper } from "./utils.js";
ネイティブ実行でもこのルールは同様です。Node.js がモジュール解決の際に .ts → .js の変換を内部でやってくれます。
package.json で快適な開発環境を作る
よく使う設定を package.json の scripts に登録しておくと便利です。
{
"scripts": {
"start": "node --experimental-strip-types src/index.ts",
"dev": "node --watch --experimental-strip-types src/index.ts",
"typecheck": "tsc --noEmit",
"build": "tsc"
}
}
--watch フラグについて少し説明します。
# ファイル変更を監視して自動再起動(Node.js v22以降で安定)
node --watch --experimental-strip-types src/index.ts
これで nodemon を入れなくてもホットリロードができます。依存関係をまた一つ減らせます。
開発フローのイメージはこうなります。
開発中 → node --watch(変更のたびに自動再起動)
+ VSCode で型チェックをリアルタイム確認
CI/CD → npm run typecheck(tsc --noEmit で型エラーを検出)
→ npm run build(本番用に tsc でビルド)
本番実行 → node dist/index.js(TypeScript 不使用、純粋なJSを実行)
まとめ
Node.js v22.7 以降から使えるネイティブ TypeScript 実行、使えるシーンは意外と多いんじゃないかと思います。
- インストール不要(ts-node / tsx なしで動く)
- 高速(esbuild などのビルドプロセスをスキップ)
- シンプル(フラグ一つ、またはフラグなしで動く)
もちろん、型チェックは自前でやる必要があります。「型を剥がすだけ」という設計なので、それは覚えておいてほしい点です。でも、型チェックはエディタとCIに任せて、実行はシンプルに Node.js でやるという分業は、ツールの責任範囲が明確でわかりやすいとも言えます。
enum を多用している既存プロジェクトには向きませんが、新しいスクリプトや小規模ツールから試してみると、「これで十分だった」と気づくシーンが結構あるかもしれません。
v22.18.0 ではフラグなしで動くようになる予定です。Node.js がこういう方向に進んでいるのは、TypeScript を使いたい開発者にとって良い流れだと感じています。