← 回首頁

Node 現在跑得動你的 TypeScript——除了五件事

Type stripping 預設就開著,而它不是編譯,是刪除。有五個構造沒有「不需要執行期程式碼」的等價寫法可刪,每一個都會讓你的行程在啟動時就停下來。

一疊半透明的紙被抽走露出乾淨的白紙,最上面兩三張卡住抽不出來。

你把建置步驟刪掉、跑 node server.ts,它動了。然後你部署上去,行程在印出任何東西 之前就結束了。

抹除不是編譯

這就是整個模型,而它解釋了全部五個失敗。Node 不編譯你的 TypeScript——它把標註 刪掉然後執行剩下的東西。型別、interface、satisfies、as:全部消失, 底下的 JavaScript 一個 byte 都沒變。這正是它不需要 source map、也不可能改變你的 程式行為的原因。

所以規則不是「哪些功能有支援」,而是:這個構造需不需要 Node 產生執行期程式碼? 需要的話就沒有東西可以刪,於是失敗。

檢查一個檔案

可抹除語法檢查

有 4 個 Node 抹不掉的構造

Node 實際會說什麼

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode

可抹除的改寫

抹不掉

enum Colour {
  Red,
  Green,
}

抹得掉

const Colour = {
  Red: 0,
  Green: 1,
} as const;

type Colour = (typeof Colour)[keyof typeof Colour];

這是正規表示式檢查,不是 TypeScript 剖析器——真正的剖析器要好幾百 KB,而這一頁的預算是 220KB。它可能會誤報。 錯誤訊息錄自 Node v25.9.0,2026-08-06。

那是正規表示式檢查,不是 TypeScript 剖析器——真的剖析器要好幾百 KB,而這一頁的 JavaScript 預算是 220KB。它會誤報,而這是誠實的取捨:一個承認自己是檢查清單的 清單,比一個假裝自己是編譯器的工具安全。

裡面每一則錯誤訊息都是真的在 Node v25.9.0 上執行後錄下來的,不是從文件抄的。 你會把那些字串貼進搜尋框,所以它們必須逐字正確。

五個,以及它們各自為什麼需要產生程式碼

enum 在執行期會產生一個物件——你可以雙向索引它。把宣告刪掉, 呼叫端就會指向不存在的東西。

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]:
TypeScript enum is not supported in strip-only mode

const enum 的失敗完全一樣,這會讓以為它會被內聯掉的人意外。

含有執行期值的 namespace 是同一個故事——namespace Config { export let retries = 3 } 必須變成一個物件。只含型別的 namespace 沒問題,Node 接受;那是唯一的例外, 而且很容易漏掉,因為錯誤訊息只說「namespace declaration」。

建構子參數屬性純粹是一次賦值的語法糖,而那次賦值得由 Node 幫你寫出來:

constructor(private readonly name: string) {}
// 必須產生:this.name = name

import fs = require('node:fs') 需要被改寫成真正的 import。

裝飾器是五個裡的異類。它不是「不支援的 TypeScript」——它還不是 JavaScript, 是一個 TC39 Stage 3 提案。Node 沒有東西可以抹,所以它更早、以不同的方式失敗: 一個普通的 SyntaxError: Invalid or unexpected token,沒有 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX 這個代碼可以拿去搜。

該做的三件事

  1. 在 tsconfig 打開 erasableSyntaxOnly。 它會讓編譯器在型別檢查階段就擋下這 五個——那才是你想發現它們的地方,而不是在生產環境的行程啟動時。
  2. 把前四個改寫掉。 它們都有可抹除的等價寫法(上面的元件裡都有), 而且每一個都比它取代的東西更接近普通的 JavaScript。
  3. 裝飾器才是真正的分岔。 它是唯一沒有可抹除改寫的。如果你的專案依賴它, 你就是要保留建置步驟——那是完全合理的答案,只是要讓它是一個決定, 而不是一個發現。

常見追問

為什麼 Node 不乾脆編譯它們?

因為抹除跟編譯是兩件事。抹除只是拿掉標註,底下的 JavaScript 一個 byte 都沒變, 所以不需要 source map,也不可能改變行為。一旦它開始產生程式碼——一個 enum 物件、 一次欄位賦值——它就是編譯器了,連同版本相容與正確性的整片表面積。

型別專用的 namespace 也會壞嗎?

不會。只含型別的 namespace 是可抹除的,Node 接受。只有宣告了執行期值的 namespace 才失敗。已在 Node v25.9.0 上實測確認。

enum 要用什麼取代?

一個凍結的物件加上一個推導出來的型別。`const Colour = { Red: 0, Green: 1 } as const` 配上 `type Colour = (typeof Colour)[keyof typeof Colour]`,呼叫端寫法一樣, 而且它是任何工具都看得懂的普通 JavaScript。

裝飾器呢?

沒有可抹除的改寫。裝飾器是 TC39 Stage 3 提案,不是 JavaScript,Node 沒有東西 可以抹。需要它的話你就還是需要建置步驟——tsc、SWC 或 esbuild。