编程 用 tsconfig 拦住 enum 与 parameter properties:Node type stripping 的配置侧写法

2026-09-28 00:03:54

用 tsconfig 拦住 enum 与 parameter properties:Node type stripping 的配置侧写法

type stripping 做什么,不做什么

Node 内置的 TypeScript 支持只做一件事:把可擦除(erasable)的 TS 语法替换成空白。类型注解、interface、type 声明被抹掉,剩下的代码按原样执行。它不做类型检查,也不生成 source map。

这意味着配置的目标很明确——保证源码里只出现能被擦除的语法,其余问题交给 tsc 单独校验。官方推荐 TypeScript 5.8+。

推荐的 compilerOptions

{
  "compilerOptions": {
    "noEmit": true,
    "target": "esnext",
    "module": "nodenext",
    "rewriteRelativeImportExtensions": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true
  }
}

各选项的作用如下。

erasableSyntaxOnly:让 tsc 提前拦住不可擦除语法

这是配置侧最关键的一项。打开后,源码里一旦出现 Node 无法擦除的语法,tsc 直接报错,而不是等到 node 跑起来才崩。覆盖的正是 enum、parameter properties、带 runtime code 的 namespace、import alias 这几类。

verbatimModuleSyntax:强制写 import type

Node 剥离类型时只看语法,无法判断某个 import 是不是只在类型位置使用。没加 type 修饰的导入会被当成值导入,运行时找不到对应导出就报错。打开 verbatimModuleSyntax 会强制类型导入必须写成 import type,与 Node 的剥离行为对齐。

allowImportingTsExtensions 与 rewriteRelativeImportExtensions

allowImportingTsExtensions 允许 import 说明符里带 .ts 扩展名,这样 tsc 才能直接吃源码做类型检查(该选项需配合 noEmit 或不产出 JS 的模式)。Node 在 import / import() 里强制要求写扩展名,require() 出于兼容同样强制。

rewriteRelativeImportExtensions 管输出侧:编译时把 .ts 说明符改写成 .js。

noEmit、target、module

noEmit 只在仓库里执行 *.ts(例如构建脚本)时需要;如果产物是分发出去的 *.js,不必打开。target 用 esnext,module 用 nodenext。

运行时会报 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX 的语法

Node 遇到以下不可擦除语法会直接抛 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX:

  • Enum 声明
  • 带 runtime code 的 namespace
  • parameter properties(构造函数参数属性)
  • import aliases

不带 runtime code 的 namespace(只有 export type)可以用。Decorators 属于 TC39 Stage 3,Node 既不转换也不 polyfill,直接抛 parser error。

Node 不读 tsconfig.json

tsconfig.json 对 Node 运行时不可见。paths 别名不会被转换成实际路径,遇到直接报错。替代方案是使用必须以 # 开头的 subpath imports,在 package.json 的 imports 字段里声明映射。

扩展名与模块系统

.ts 的模块判定规则和 .js 相同:要走 ESM 就需要 package.json 里写 "type": "module"。.mts 恒为 ESM,.cts 恒为 CJS,.tsx 不支持。Node 不会在 CJS 和 ESM 之间做转换。

CI 里用 tsc --noEmit 单独做类型检查

类型检查从运行链路里拆出来,交给 CI 里的 tsc:

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "start": "node src/index.ts",
    "test": "node --test"
  }
}
# .github/workflows/ci.yml
- run: npm ci
- run: npx tsc --noEmit
- run: node --test

node 负责执行,tsc --noEmit 负责类型与可擦除性校验,两者职责不重叠。erasableSyntaxOnly 报出的错误会在这一步挡住合并。

需要完整支持时用 tsx

需要 decorators、enum 这类非擦除语法,或者想要更完整的转换能力,可以换成 tsx:

npm install --save-dev tsx
npx tsx your-file.ts
# 或者
node --import=tsx your-file.ts

链接

  • Node.js TypeScript 文档:https://nodejs.org/api/typescript.html
  • tsx:https://tsx.is

推荐文章

程序员茄子在线接单