Node 24 原生跑 .ts 实测:strip-only 能跑什么,enum、namespace 和参数属性为什么报错

举报
岚枫 发表于 2026/10/25 08:09:04 2026/10/25
【摘要】 Node v24.9.0 实测:.ts 直接运行能跑接口和抽象类,enum、namespace、参数属性报 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX,附 transform 开关与模块解析的坑。

Node 24 原生跑 .ts 实测:strip-only 能跑什么,enum、namespace 和参数属性为什么报错

Node.js 24 里,node app.ts 已经不需要任何 flag、不需要 ts-node、不需要先 tsc 编译。但它做的不是"编译 TypeScript",而是把类型标注擦掉后直接交给 V8,也就是 Node 报错原文里写的 strip-only mode。擦得掉的和擦不掉的,边界在哪,报错长什么样,我在 Windows 上用 Node v24.9.0 把十来种写法逐个跑了一遍。

结论先放在这里:接口、类型别名、类型标注、abstract、import type 都能跑;enum、const enum、namespace、构造函数的参数属性会直接抛 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX;再加一个 --experimental-transform-types 就能跑,但会打印实验特性警告。另外,它完全不做类型检查。

一、实测环境

node --version
# v24.9.0

node -p "process.platform + ' ' + process.arch"
# win32 x64

node -e "console.log(JSON.stringify(process.features.typescript))"
# "strip"

process.features.typescript 是本次实验里最直接的一行判据:默认值是 "strip"。加上 transform 开关后它会变成 "transform",这个后面再说。

测试目录里没有 package.json,所有文件都是散放在同一个文件夹里的 .ts。这一点会影响模块系统判定,但不影响下面关于语法的结论。文中所有结论都基于 v24.9.0 这一个版本,没有覆盖其他 24.x 小版本。

二、能直接跑的部分

第一个文件同时用了接口、类型别名、类型标注和 satisfies:

interface User { name: string; age: number }
type Id = string | number;
const u: User = { name: 'Ada', age: 36 };
const id = 42 satisfies Id;
function greet(x: User): string { return `hello ${x.name}`; }
console.log(greet(u), id);
$ node ok.ts
hello Ada 42
$ echo $?
0

这些写法的共同点是:类型信息只存在于源码里,运行时不产生任何代码。擦掉之后剩下的就是普通 JavaScript,所以 V8 照跑不误。

抽象类也能跑。注意抽象方法和普通方法不同——抽象方法只有声明,运行时不生成东西:

abstract class A { abstract go(): void; run(){ this.go(); } }
class B extends A { go(){ console.log('go'); } }
new B().run();
$ node abs.ts
go

类型导入同样没问题。下面这个文件同时有 import type 和普通的带类型导入,并且显式写了 .ts 后缀:

import type { Foo } from './types.ts';
import { v } from './mod.ts';
const x: Foo = { a: 1 };
console.log(x.a, v);
$ node imp.ts
1 7

内联的类型修饰符 import { type Foo } 也可以,实测输出 2,退出码 0。

三、会报错的四种写法

这四种写法的共同点是:TypeScript 编译器要为它们生成运行时代码——enum 要生成一个对象,namespace 要生成一个立即执行函数,参数属性要在构造函数里插入 this.name = name。只擦除、不生成,就做不出来,于是直接报错。

1. enum

enum Color { Red, Green }
console.log(Color.Green);
$ node enum.ts
enum Color { Red, Green }
^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
    at parseTypeScript (node:internal/modules/typescript:68:40)
    at processTypeScriptCode (node:internal/modules/typescript:146:42)

Node.js v24.9.0
$ echo $?
1

2. const enum

把 enum 换成 const enum,报错文案完全一样,仍是 TypeScript enum is not supported in strip-only mode,退出码 1。也就是说常量枚举并不能绕过这条限制。

3. namespace

namespace N { export const v = 1 }
console.log(N.v);
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode

4. 构造函数的参数属性

class P { constructor(public name: string) {} }
console.log(new P('x').name);
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript parameter property is not supported in strip-only mode

这三种报错文案都带 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX 错误码,退出码都是 1,并且 Node 会把出错的那一行和波浪线一起打出来,定位很直接。const enum 只是复用了 enum 那一条文案,所以本节算四类写法、三种报错文案。

四、加 --experimental-transform-types 就能跑

这四个文件换成同一条命令再跑:

$ node --experimental-transform-types enum.ts
1
$ node --experimental-transform-types ns.ts
1
$ node --experimental-transform-types ctor.ts
x
$ node --experimental-transform-types cenum.ts
1

全部退出码 0,结果也符合预期(Color.Green 是 1,N.v 是 1,new P('x').name 是 x)。代价是每次运行都会在 stderr 打印两行:

(node:10028) ExperimentalWarning: Transform Types is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

transform 模式下 process.features.typescript 返回 "transform"。另外 node --help 里能查到这两个相关开关:--experimental-transform-types 与 --no-experimental-strip-types。

五、模块解析:后缀必须写,tsconfig 的 paths 不生效

strip-only 不做类型检查,也不接管 TypeScript 的模块解析。最容易踩的是省略后缀:

import { v } from './mod';
console.log(v);
$ node nosuffix.ts
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '<测试目录>\mod' imported from <测试目录>\nosuffix.ts
    code: 'ERR_MODULE_NOT_FOUND'

(报错里的本机绝对路径已替换为 <测试目录>,其余逐字保留。)

必须有 .ts 后缀。同理,tsconfig.json 里的 baseUrl 和 paths 对 Node 无效。我用一份标准配置试了路径别名:

{"compilerOptions":{"baseUrl":".","paths":{"@lib/*":["./lib/*"]}}}
$ node app.ts
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@lib/util.ts' imported from <测试目录>\cfg\app.ts
    code: 'ERR_MODULE_NOT_FOUND'

它把 @lib/util.ts 当成一个 npm 包名去找,而不会去读 tsconfig 里的映射。所以用 tsconfig paths 配了别名的现有项目,直接 node app.ts 是跑不起来的,要么改相对路径加 .ts,要么继续用 tsc 或打包工具。

反过来,纯类型导入连解析都不做。下面这行从一个根本不存在、也不带后缀的文件里导入类型:

import type { Foo } from './types-noext';
const x: Foo = { a: 1 };
console.log(x.a);
$ node typeonly.ts
1

语句被整行擦掉了,文件根本没去找。这也是为什么"只导入类型"不会引发模块解析错误——但写错名字也不会有人提醒你。

还有一个容易被忽略的限制:node_modules 下的 .ts 不处理

如果某个依赖直接把 .ts 当入口发布,Node 不会去擦它的类型。我把一个带 "main": "index.ts" 的包放进 node_modules 再导入,得到的是:

Error [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]: Stripping types is currently unsupported for files under node_modules, for "<测试目录>\node_modules\pkg\index.ts"

加上 --experimental-transform-types 也一样报这个错。结论很明确:node_modules 下的包仍然应该是编译后的 JavaScript,原生跑 .ts 只适用于自己项目里的源码。

六、不做类型检查,也不管你写错什么

这条要单独强调。下面这段把字符串赋给了 number 变量,函数签名也声称返回 number,实际返回的是字符串:

const n: number = "not a number";
function f(x: string): number { return x; }
console.log(n, f("abc"));
$ node typecheck.ts
not a number abc
$ echo $?
0

没有任何警告,退出码 0。同一份文件交给 tsc --noEmit 检查(本机 TypeScript 7.0.2),赋值和返回值各报一条错误,两条都是 TS2322: Type 'string' is not assignable to type 'number'。类型检查仍然要交给 tsc --noEmit、编辑器的语言服务或 CI。Node 的运行时只保证"能擦的擦干净、擦不掉的报错"。

七、CommonJS 照旧可用

.ts 文件里写 require 也能跑,实测 typeof os.platform() 输出 string,退出码 0。判断模块类型看最近的 package.json:没有 type 字段时 Node 会检测源码里的 import/export,有就按 ESM 处理;一旦写了 "type": "commonjs",.ts 里的 import 就不再被当成 ESM,实测直接报:

(node:35580) Warning: Failed to load the ES module: <测试目录>\esm.ts. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
SyntaxError: Cannot use import statement outside a module

八、把开关关掉会怎样

$ node --no-experimental-strip-types ok.ts
interface User { name: string; age: number }
          ^^^^

SyntaxError: Unexpected identifier 'User'

关掉之后 Node 就完全不认 TypeScript,连接口声明都当语法错误。这个开关主要用于排查"到底是 Node 在处理类型,还是别的工具在处理"。

九、实用建议

基于上面这些实测,我的用法是:

1. 单文件脚本、小工具:直接 node x.ts,能省掉一次编译。
2. 代码里出现 enum / namespace / 参数属性:要么改写成等价 JS
   (对象常量 + as const 代替 enum,普通类字段赋值代替参数属性),
   要么加 --experimental-transform-types 并接受实验警告。
3. 相对导入一律带 .ts 后缀;路径别名留在打包工具或 tsc 里。
4. 类型检查单独跑 tsc --noEmit,不要指望 node 帮你发现问题。

十、实测结果汇总

写法默认 strip-only加 --experimental-transform-types
interface / type / 类型标注 / satisfies通过,输出 hello Ada 42通过
abstract class通过,输出 go通过
import type / import { type X }通过通过
enumERR_UNSUPPORTED_TYPESCRIPT_SYNTAX通过,输出 1
const enumERR_UNSUPPORTED_TYPESCRIPT_SYNTAX通过,输出 1
namespaceERR_UNSUPPORTED_TYPESCRIPT_SYNTAX通过,输出 1
构造函数参数属性ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX通过,输出 x
省略 .ts 后缀的导入ERR_MODULE_NOT_FOUND同样报错
tsconfig paths 别名ERR_MODULE_NOT_FOUND同样报错
.ts 里写 require通过,输出 string通过
"type": "commonjs" 下的 importSyntaxError: Cannot use import statement outside a module同样报错
node_modules 里包的 .ts 入口ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING同样报错
类型写错 / 返回类型不符静默通过,不检查同样不检查

一句话总结:Node 24 原生跑 TypeScript 解决的是"少一步编译",不是"替代 tsc"。理解 strip-only 的边界——擦类型可以,生成运行时结构不行——能省掉大部分意外报错。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。