Modules: TypeScript
A Note to Our Readers
While maintaining fidelity to the source material, this translation incorporates explanatory content and localized expressions, informed by the translator's domain expertise. These thoughtful additions aim to enhance readers' comprehension of the original text's core messages. For any inquiries about the content, we welcome you to engage in the discussion section or consult the source text for reference.
Enabling
在 node.js
中启用运行时 typescript
支持有两种方式:
- 如果需要完整支持所有
typescript
的语法特性,包括使用任意版本的typescript
,可以使用第三方包。 - 如果只需要轻量级支持,可以使用
node.js
内置的类型擦除功能。
Full TypeScript support
可以通过第三方包来获得完整的 typescript
支持(包括对 tsconfig.json
的配置支持)。这里以 tsx
为例进行说明,当然市面上还有许多类似的库可供选择。
首先,需要使用项目中的包管理器将其安装为开发依赖。比如使用 npm
:
npm install --save-dev tsx
然后,可以通过以下方式运行 typescript
代码:
npx tsx your-file.ts
或者,通过 node
命令运行:
node --import=tsx your-file.ts
Type Stripping
Process
Added in
: v22.6.0
Stability: 1.1
: Active development
Default Enabled
: v23.6.0
(2025-01-07
)。v23.6.0
版本中 默认启用 了 --experimental-strip-types
标志,node.js
将能够执行 typescript
文件而无需额外配置。在 type-stripping 中记录的支持语法存在一些限制。这个功能是实验性的,可能会发生变化。
Related Materials
:
启用 --experimental-strip-types
标志,node.js
可以直接运行 typescript
文件。默认情况下,node.js
只会执行那些不包含需要转换的 typescript
特性(如枚举或命名空间)的文件。
启用这个特性后,node.js
会将 内联类型注释 替换为空白字符,且 不会执行类型检查。如果需要启用复杂类型转换(如枚举或命名空间),可以使用 --experimental-transform-types
标志,这个标志在 node.js
的 v22.7.0
版本中实验性支持。
需要注意的是,依赖于 tsconfig.json
设置的 typescript
特性
- 路径映射功能:
tsconfig
配置文件中的paths
字段不会被转换,因此会产生错误。最接近的可用特性是子路径导入,限制是路径需要以#
开头。 - 将新版本的
javascript
语法转换为旧版本的功能。
是特意不被支持的。如果需要完整的 typescript
支持,请参考上文的 Full TypeScript support 说明。
类型移除特性的 设计理念是保持轻量级,通过以下两个核心策略实现这一目标:
- 有意不支持需要额外生成
javascript
代码的类型语法。 - 将内联类型语法替换为空白字符。
由于上述两个核心策略,对于源码映射的影响基本是微乎其微,node.js
不需要再额外维护源映射。
当启用
--experimental-transform-types
时,源映射默认开启。
虽然类型擦除功能与大多数版本的 typescript
都兼容,但建议使用 5.7
或更新版本,并使用以下 tsconfig.json
配置:
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"verbatimModuleSyntax": true
}
}
Determining module system
node.js
在 typescript
文件中同时支持 commonjs
和 esm
语法。需要注意的是,node.js
不会在这两种模块系统之间进行转换。如果代码作为 esm
模块运行,就必须使用 import
和 export
语法;如果代码作为 commonjs
运行,则必须使用 require
和 module.exports
。具体规则如下:
.ts
文件的模块系统判定方式与.js
文件相同。要使用import
和export
语法,需要在最近的父级package.json
中添加type: "module"
。.mts
文件将始终作为esm
模块运行,类似于.mjs
文件。.cts
文件将始终作为commonjs
模块运行,类似于.cjs
文件。.tsx
文件目前不受支持。
与 javascript
文件一样,在 import
语句和 import()
表达式中必须包含文件扩展名,例如:使用 import './file.ts'
,而不是 import './file'
。出于向后兼容性考虑,在 require()
调用中也必须包含文件扩展名,例如:使用 require('./file.ts')
,而不是 require('./file'
,这与在 commonjs
文件中 .cjs
扩展名必须在 require
调用中指定的要求类似。
tsconfig.json
中的 allowImportingTsExtensions
选项将允许 typescript
编译器 tsc
对包含 .ts
扩展名的导入说明符进行类型检查。
TypeScript features
由于 node.js
只负责移除内联类型,因此任何需要将 typescript
语法替换为新的 javascript
语法的特性都会报错,除非传入 --experimental-transform-types
标志。
--experimental-transform-types
主要需要转换的特性包括:
- 枚举(Enum)
- 命名空间(namespaces)
- 传统模块(legacy module)
- 参数属性(parameter properties)
由于装饰器(Decorators
)目前是 TC39
三阶段提案,并且很快就会得到 javascript
引擎的支持,因此不会被转换,并会导致解析错误。这是一个临时限制,将在未来得到解决。
此外,node.js
不会读取 tsconfig.json
文件,也不支持依赖于 tsconfig.json
设置的特性,比如 路径别名 或 将新版 javascript
语法转换为旧版标准。
Importing types without type keyword
由于类型擦除的工作原理,使用 type
关键字对于正确擦除类型导入是必要的。如果没有 type
关键字,node.js
会将导入视为值导入,这将导致运行时错误。可以使用 tsconfig
的 verbatimModuleSyntax
选项来匹配这种行为。
以下示例将正确运行:
import type { Type1, Type2 } from './module.ts';
import { fn, type FnParams } from './fn.ts';
而以下代码将导致运行时错误:
import { Type1, Type2 } from './module.ts';
import { fn, FnParams } from './fn.ts';
Non-file forms of input
类型擦除可以在 --eval
和 STDIN
中启用。模块系统将由 --input-type
决定,这与 javascript
的处理方式相同。需要注意的是,typescript
语法在 REPL
、--check
和 inspect
中不受支持。
Source maps
由于内联类型被替换为空白字符,同时没有新增其他额外的 javascript
代码,因此在堆栈跟踪中获得正确的行号不需要源码映射,所以 node.js
不会生成它们。当启用 --experimental-transform-types
时,源码映射默认是启用的。
Type stripping in dependencies
为了避免包作者发布用 typescript
编写的包,node.js
默认会拒绝处理 node_modules
路径下文件夹中的 typescript
文件。
Paths aliases
tsconfig
中可以通过配置 paths
来配置路径别名。
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"scripts": ["./scripts/toolbox.ts"]
}
}
}
那么在应用中可以使用
import { toolbox } from 'scripts';
来加载 monorepo
应用中的其他包。但不幸的是,node.js
并不会尊重 tsconfig
文件,因此运行时会报如下错误:
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'scripts' imported from /Users/project/demo/packages/core/index.ts
node.js
提供了原生的 子路径导入 作为替换方案,需要在 package.json
中配置:
{
"imports": {
"#scripts": "scripts/toolbox.ts"
}
}
然后在代码中这样使用:
import { toolbox } from '#scripts';
Attention
这里需要注意的是,scripts
是应用之外的包,而子路径导入的包要么位于 node_modules
目录下,要么位于应用之内。虽然可以通过将 scripts
包手动软链到 node_modules
目录下,或者将 scripts
包软链到应用之内,但这就形成了感染链,即应用依赖的 scripts
包的所有依赖模块也必须满足这个条件。