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の組み合わせは、その実験を日常の開発フローに引き寄せる一歩です。