模块:TypeScript#

稳定性:2 - 稳定

启用#

在 Node.js 中启用运行时 TypeScript 支持有两种方式:

  1. 若要完整支持所有 TypeScript 语法和特性(包括使用任意版本的 TypeScript),请使用第三方软件包。

  2. 若仅需轻量级支持,可以使用内置的类型剥离功能。

完整的 TypeScript 支持#

若要使用完整支持所有 TypeScript 特性(包括 tsconfig.json)的 TypeScript,可以使用第三方软件包。以下说明以 tsx 为例,但还有许多其他类似的库可供使用。

  1. 使用项目所用的包管理器将该包安装为开发依赖。例如,使用 npm

    npm install --save-dev tsx
    
  2. 然后,可以通过以下方式运行 TypeScript 代码:

    npx tsx your-file.ts
    

    或者,也可以通过以下方式使用 node 运行:

    node --import=tsx your-file.ts
    

类型剥离#

默认情况下,Node.js 会执行仅包含可擦除 TypeScript 语法的 TypeScript 文件。Node.js 会将 TypeScript 语法替换为空格,且不执行类型检查。要禁用此功能,请使用标志 --no-strip-types

Node.js 会忽略 tsconfig.json 文件,因此依赖于 tsconfig.json 设置的功能(例如路径映射或将较新的 JavaScript 语法转换为旧标准)被有意不支持。要获得完整的 TypeScript 支持,请参阅完整的 TypeScript 支持

类型剥离功能旨在实现轻量化。通过有意不支持需要 JavaScript 代码生成的语法,并将内联类型替换为空格,Node.js 可以在无需源映射的情况下运行 TypeScript 代码。

类型剥离与大多数版本的 TypeScript 兼容,但我们建议使用 5.8 或更新版本,并配置以下 tsconfig.json 设置:

{
  "compilerOptions": {
     "noEmit": true, // Optional - see note below
     "target": "esnext",
     "module": "nodenext",
     "rewriteRelativeImportExtensions": true,
     "erasableSyntaxOnly": true,
     "verbatimModuleSyntax": true
  }
}

如果你打算仅执行 *.ts 文件(例如构建脚本),请使用 noEmit 选项。如果你打算分发 *.js 文件,则无需此标志。

确定模块系统#

Node.js 在 TypeScript 文件中同时支持 CommonJSES 模块语法。Node.js 不会在不同模块系统之间进行转换;如果你希望代码作为 ES 模块运行,则必须使用 importexport 语法;如果希望代码作为 CommonJS 运行,则必须使用 requiremodule.exports

  • .ts 文件的模块系统确定方式与 .js 文件相同。要使用 importexport 语法,请在最近的父级 package.json 中添加 "type": "module"
  • .mts 文件将始终作为 ES 模块运行,类似于 .mjs 文件。
  • .cts 文件将始终作为 CommonJS 模块运行,类似于 .cjs 文件。
  • 不支持 .tsx 文件。

与 JavaScript 文件一样,在 import 语句和 import() 表达式中,文件扩展名是强制性的:应使用 import './file.ts',而不是 import './file'。出于向后兼容性考虑,在 require() 调用中文件扩展名同样是强制性的:应使用 require('./file.ts'),而不是 require('./file'),这类似于在 CommonJS 文件中 require 调用强制要求 .cjs 扩展名。

tsconfig.json 中的 allowImportingTsExtensions 选项允许 TypeScript 编译器 tsc 对包含 .ts 扩展名的 import 说明符文件进行类型检查。

TypeScript 特性#

由于 Node.js 仅移除内联类型,任何涉及将 TypeScript 语法替换为新 JavaScript 语法的 TypeScript 特性都会报错。

最显著的需要转换的特性包括:

  • Enum(枚举)声明
  • 包含运行时代码的 namespace(命名空间)
  • 参数属性(parameter properties)
  • 导入别名(import aliases)

支持不包含运行时代码的 namespace。以下示例可以正常工作:

// This namespace is exporting a type
namespace TypeOnly {
   export type A = string;
}

这会导致 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX 错误:

// This namespace is exporting a value
namespace A {
   export let x = 1
}

由于装饰器目前是 TC39 Stage 3 提案,它们不会被转换,并将导致解析器错误。Node.js 不提供 polyfill,因此在 JavaScript 原生支持装饰器之前,它不会支持该特性。

此外,Node.js 不会读取 tsconfig.json 文件,也不支持依赖于 tsconfig.json 设置的特性,例如路径映射或将较新的 JavaScript 语法转换为旧标准。

在没有 type 关键字的情况下导入类型#

由于类型剥离的本质,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';

非文件形式的输入#

可以在 --eval 和 STDIN 中启用类型剥离。模块系统将由 --input-type 决定,就像 JavaScript 一样。

REPL、--checkinspect 不支持 TypeScript 语法。

源映射#

由于内联类型被替换为空格,因此对于堆栈跟踪中的正确行号,源映射是不必要的;Node.js 也不会生成它们。

依赖项中的类型剥离#

为了劝阻包作者发布用 TypeScript 编写的包,Node.js 拒绝处理 node_modules 路径下的文件夹中的 TypeScript 文件。

路径别名#

tsconfig "paths" 不会被转换,因此会产生错误。目前可用的最接近的特性是子路径导入(subpath imports),但限制是它们必须以 # 开头。