Skip to content

Modules: TypeScript

Refer

Source: Type stripping

Author: Node.js

Translator: SenaoXi

Reference Materials:

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 支持有两种方式:

  1. 如果需要完整支持所有 typescript 的语法特性,包括使用任意版本的 typescript,可以使用第三方包。
  2. 如果只需要轻量级支持,可以使用 node.js 内置的类型擦除功能。

Full TypeScript support

可以通过第三方包来获得完整的 typescript 支持(包括对 tsconfig.json 的配置支持)。这里以 tsx 为例进行说明,当然市面上还有许多类似的库可供选择。

首先,需要使用项目中的包管理器将其安装为开发依赖。比如使用 npm

bash
npm install --save-dev tsx

然后,可以通过以下方式运行 typescript 代码:

bash
npx tsx your-file.ts

或者,通过 node 命令运行:

bash
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.jsv22.7.0 版本中实验性支持。

需要注意的是,依赖于 tsconfig.json 设置的 typescript 特性

  • 路径映射功能tsconfig 配置文件中的 paths 字段不会被转换,因此会产生错误。最接近的可用特性是子路径导入,限制是路径需要以 # 开头。
  • 将新版本的 javascript 语法转换为旧版本的功能。

是特意不被支持的。如果需要完整的 typescript 支持,请参考上文的 Full TypeScript support 说明。

类型移除特性的 设计理念是保持轻量级,通过以下两个核心策略实现这一目标:

  • 有意不支持需要额外生成 javascript 代码的类型语法。
  • 将内联类型语法替换为空白字符。

由于上述两个核心策略,对于源码映射的影响基本是微乎其微,node.js 不需要再额外维护源映射。

当启用 --experimental-transform-types 时,源映射默认开启。

虽然类型擦除功能与大多数版本的 typescript 都兼容,但建议使用 5.7 或更新版本,并使用以下 tsconfig.json 配置:

json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "allowImportingTsExtensions": true,
    "rewriteRelativeImportExtensions": true,
    "verbatimModuleSyntax": true
  }
}

Determining module system

node.jstypescript 文件中同时支持 commonjsesm 语法。需要注意的是,node.js 不会在这两种模块系统之间进行转换。如果代码作为 esm 模块运行,就必须使用 importexport 语法;如果代码作为 commonjs 运行,则必须使用 requiremodule.exports。具体规则如下:

  • .ts 文件的模块系统判定方式与 .js 文件相同。要使用 importexport 语法,需要在最近的父级 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 会将导入视为值导入,这将导致运行时错误。可以使用 tsconfigverbatimModuleSyntax 选项来匹配这种行为。

以下示例将正确运行:

ts
import type { Type1, Type2 } from './module.ts';
import { fn, type FnParams } from './fn.ts';

而以下代码将导致运行时错误:

ts
import { Type1, Type2 } from './module.ts';
import { fn, FnParams } from './fn.ts';

Non-file forms of input

类型擦除可以在 --evalSTDIN 中启用。模块系统将由 --input-type 决定,这与 javascript 的处理方式相同。需要注意的是,typescript 语法在 REPL--checkinspect 中不受支持。

Source maps

由于内联类型被替换为空白字符,同时没有新增其他额外的 javascript 代码,因此在堆栈跟踪中获得正确的行号不需要源码映射,所以 node.js 不会生成它们。当启用 --experimental-transform-types 时,源码映射默认是启用的。

Type stripping in dependencies

为了避免包作者发布用 typescript 编写的包,node.js 默认会拒绝处理 node_modules 路径下文件夹中的 typescript 文件。

Paths aliases

tsconfig 中可以通过配置 paths 来配置路径别名。

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "scripts": ["./scripts/toolbox.ts"]
    }
  }
}

那么在应用中可以使用

ts
import { toolbox } from 'scripts';

来加载 monorepo 应用中的其他包。但不幸的是,node.js 并不会尊重 tsconfig 文件,因此运行时会报如下错误:

bash
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'scripts' imported from /Users/project/demo/packages/core/index.ts

node.js 提供了原生的 子路径导入 作为替换方案,需要在 package.json 中配置:

json
{
  "imports": {
    "#scripts": "scripts/toolbox.ts"
  }
}

然后在代码中这样使用:

ts
import { toolbox } from '#scripts';

Attention

这里需要注意的是,scripts 是应用之外的包,而子路径导入的包要么位于 node_modules 目录下,要么位于应用之内。虽然可以通过将 scripts 包手动软链到 node_modules 目录下,或者将 scripts 包软链到应用之内,但这就形成了感染链,即应用依赖的 scripts 包的所有依赖模块也必须满足这个条件。

Contributors

Changelog

Discuss

Released under the CC BY-SA 4.0 License. (2619af4)