TypeScriptを書いたら、いったんJavaScriptへ変換してから実行する——その常識が揺らいでいます。
TypeScript 5.8とNode.jsのtype stripping(型除去)により、ビルドステップなしで.tsファイルを走らせる道筋が整いつつあります。この記事では、仕組み・制約・実務での設定までを整理します。
この記事でわかること
- type strippingが従来のトランスパイルと何が違うか
- Node.js 22.6以降で
.tsを直接実行する方法 - TypeScript 5.8の
erasableSyntaxOnlyフラグの役割 - 使えなくなる構文とZodとの併用パターン
- ブラウザとTC39提案が示す今後の方向性
type strippingとは何か
TypeScriptはJavaScriptのスーパーセットです。プログラムから型情報を取り除けば、多くの場合そのまま動くJavaScriptになります。この「型だけを消して実行する」手法がtype strippingです。
従来のtscによるコンパイルは、型の削除に加えてモジュール形式の変換や新しい構文の書き換えも行います。一方type strippingは、型注釈を空白に置き換える軽量な処理に徹します。Javaの型消去(type erasure)に近い考え方です。
ここで重要なのは、型を単に削除するのではなく空白で置き換える点です。行番号がソースと実行コードで一致するため、開発中のソースマップが不要になります。エディタの10行目とスタックトレースの10行目がずれにくく、ブレークポイントも当たりやすくなります。
DenoやBunは以前からこの方式を採用してきました。エンタープライズ向けの主流ランタイムであるNode.jsが追随したことで、実務への波及が加速しています。
Node.jsでの直接実行
https://nodejs.org/docs/latest/api/typescript.html
Node.js 22.6(2024年8月リリース)で--experimental-strip-typesフラグが追加され、.tsファイルの直接実行が可能になりました。23.6以降はフラグなしで有効化され、25.2.0および24.12.0でtype strippingは安定版(Stable)に昇格しています。
次のようなTypeScriptコードを用意します。
interface Animal {
name: string;
winged: boolean;
}
function move(creature: Animal): string {
if (creature.winged) {
return `${creature.name} takes flight.`;
}
return `${creature.name} walks the path.`;
}
const bat: Animal = { name: "Bat", winged: true };
console.log(move(bat));
フラグなしでnode animal.tsを実行すると、interfaceが構文エラーになります。22.6系ではnode --experimental-strip-types animal.ts、23.6以降ではnode animal.tsで「Bat takes flight.」と表示されます。
Node.jsは型チェックを行いません。実行時に消えるのは型注釈だけで、型の正しさは別途確認する必要があります。tsc --noEmitやエディタの言語サーバーで検証する運用が前提です。
またNode.jsはtsconfig.jsonを読み込みません。パスエイリアスや新しい構文のダウンレベル変換など、設定に依存する機能はサポート外です。node_modules配下の.tsも処理対象外です。
TypeScript 5.8のerasableSyntaxOnly
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html
TypeScript 5.8は、Node.jsの制約に先回りしてerasableSyntaxOnlyフラグを追加しました。有効にすると、実行時にコード生成が必要な構文をコンパイル時にエラーにします。
対象となる主な構文は次のとおりです。
enum宣言- 実行コードを含む
namespace/module - クラスのパラメータプロパティ(
constructor(public x: number)など) import =/export =形式の代入
パラメータプロパティは、コンストラクタ内にthis.x = xの代入を挿入するため、単純な除去では対応できません。enumもJavaScriptオブジェクトを生成するため、type strippingの対象外です。
Node.js公式ドキュメントが推奨するtsconfig.jsonの設定例は次のとおりです。
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true
}
}
verbatimModuleSyntaxは、型のみのインポートにimport typeを強制します。typeキーワードなしの型インポートは、Node.jsが値として解釈してランタイムエラーになるためです。import文には.ts拡張子の明示も必要です。
erasableSyntaxOnlyはAmaro(Node.js内部のtype strippingライブラリ)やts-blank-spaceと同じ制約を、開発中にIDE上で先に検出するための仕組みです。実行して初めて失敗に気づく事態を減らせます。
Zodでランタイム検証を補う
type strippingでは、インターフェースなどの型情報は実行開始と同時に消えます。APIレスポンスやユーザー入力を型だけで守ることはできません。
ここでZodのようなランタイムバリデーションライブラリが役立ちます。Zodスキーマは通常のJavaScriptオブジェクトとして残るため、type stripping後も検証ロジックはそのまま動きます。
import { z } from "zod";
const AnimalSchema = z.object({
name: z.string(),
winged: z.boolean(),
});
type Animal = z.infer<typeof AnimalSchema>;
AnimalSchemaは実行時に残り、type Animal = ...の行だけが除去されます。enumの代替としてZodの列挙型を使うパターンも、type stripping環境では実用的です。
ブラウザとJavaScript標準化の行方
サーバー側のNode.js、Deno、Bunはtype strippingを受け入れています。一方、ChromeやSafariは型注釈を含むコードをそのまま実行できず、構文エラーになります。フロントエンドは引き続きViteやwebpackなどのビルドツールが必要です。
TC39の「Type Annotations」提案(types as comments)は現在Stage 1です。ランタイムは型構文を無視し、型チェックは外部ツールに任せる設計で、type strippingと同じ「消去可能な型」という前提を共有しています。TypeScriptだけでなくFlowやClosure Compilerも視野に入った標準化の動きです。
State of JavaScript調査では、静的型付けが長年「最も欲しい欠けている機能」として上位に挙がっています。type strippingの普及は、その需要に対する実装側の回答の一つと言えます。
実務で使うときの判断
新規のサーバーサイドプロジェクトや社内ツールから段階的に導入するのが現実的です。既存コードにenumやパラメータプロパティが多い場合は、書き換えコストを見積もってから移行を検討してください。
Node.js 22 LTSでは引き続き--experimental-strip-typesが必要です。フラグなしの直接実行を使うなら23.6以降、安定版として本番採用を検討するなら24.12.0以降が目安です。いずれの場合も、型チェックと実行は分離した二段構えのワークフローが基本になります。
type strippingは単なるNode.jsのフラグではありません。型を開発時のガードレールとして使い、実行時は空白に置き換えて走らせる——という設計思想の転換です。ビルドパイプラインの複雑さを一段下げながら、TypeScriptの型安全性はエディタとtsc側で維持する。5.8とNode.jsの組み合わせは、その実験を日常の開発フローに引き寄せる一歩です。