12k
All articles

TypeScript 在 Node 中的实用配置

介绍如何在 Node.js 中结合 ESM、tsc 编译与原生类型剥离来配置 TypeScript,以现代工具链构建生产级 API 与脚本。

OpenReplay Team
OpenReplay Team
TypeScript 在 Node 中的实用配置

你已经在浏览器端编写 TypeScript。现在你需要在服务器端运行它——用于 API、构建脚本或 SSR。问题在于:大多数配置指南已经过时,推荐的是 CommonJS 配置或与现代 Node.js 不匹配的工具。

本指南涵盖了 TypeScript Node.js 配置的两种方法:使用 tsc 编译并运行 JavaScript,或使用 Node 的原生类型剥离直接运行 .ts 文件。两种方法都可行。各自适用于不同的场景。

核心要点

  • 在 package.json 中设置 "type": "module" 以默认启用 ESM,适用于现代 TypeScript Node.js 项目
  • 对于生产部署、发布的包以及使用枚举、命名空间或参数属性的代码,使用 tsc 编译
  • 对于本地脚本、开发服务器和快速原型,使用 Node 的原生类型剥离
  • 始终对仅类型导入使用 import type,以避免类型剥离时的运行时错误
  • 在 CI 中运行 tsc --noEmit,因为 Node 的类型剥离不执行类型检查

基础配置:Node 24 LTS 和 ESM

从这个基础开始:

{
  "type": "module"
}

这将默认启用 ESM。你的导入使用 ESM 语法,Node 会相应地解析模块。

Node 24 是此配置的当前 LTS 基线(可从此处下载:https://nodejs.org/en/download)。

方法 1:使用 tsc 编译,运行 JavaScript

这种方法将编译与执行分离。适用于生产部署、发布的包,或当你需要完整的 TypeScript 特性支持时。

适用于 Node 24 的 tsconfig

{
  "compilerOptions": {
    "target": "ES2024",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "sourceMap": true,
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "lib": ["ES2024"]
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

此配置中的关键设置:

  • module: NodeNextmoduleResolution: NodeNext:匹配 Node 的实际模块解析行为
  • verbatimModuleSyntax:要求对仅类型导入显式使用 import type——这对避免运行时错误至关重要(参见 TypeScript 文档:https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax)
  • isolatedModules:确保与单文件转译工具的兼容性

脚本配置

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc --watch"
  }
}

运行 npm run build,然后运行 npm start。编译后的 JavaScript 位于 dist/ 目录中。

方法 2:Node 的原生 TypeScript(类型剥离)

Node 在 Node 22 中引入了原生 TypeScript 支持,并在 Node 24 LTS 中通过类型剥离将其稳定化。适用于脚本、本地工具或开发环境,当你希望零构建步骤时。

官方文档:https://nodejs.org/api/typescript.html

工作原理

在 Node 24+ 中,直接运行:

node src/index.ts

(旧版 Node 需要实验性标志;Node 24 不需要。)

关键限制

Node 的原生 TypeScript 仅剥离类型——它不进行类型检查。你仍然需要在 CI 或编辑器中使用 tsc --noEmit 来捕获错误。

其他限制:

  • 忽略 tsconfig.json:Node 不读取你的编译器选项
  • 需要显式文件扩展名:即使源文件是 utils.ts,也要写 import { foo } from './utils.js'
  • 遵守 ESM vs CJS 规则:你的 package.json type 字段很重要
  • 不会运行 node_modules 中的 TypeScript:依赖项必须是编译后的 JavaScript
  • 仅支持可擦除语法:枚举、命名空间和参数属性会失败,除非启用 --experimental-transform-types

文件扩展名很重要

对于混合模块格式:

  • .mts 文件 → 始终是 ESM
  • .cts 文件 → 始终是 CommonJS
  • .ts 文件 → 遵循 package.json type 字段

避免运行时错误

对仅类型导入使用 import type:

// 正确
import type { Request, Response } from 'express'
import express from 'express'

// 错误 - 使用类型剥离时会在运行时失败
import { Request, Response } from 'express'

在 tsconfig 中启用 verbatimModuleSyntax 以在开发期间捕获这些问题,即使 Node 在运行时会忽略该配置。

使用哪种方法

使用 tsc 编译的场景:

  • 生产部署
  • 发布的 npm 包
  • 使用枚举、命名空间或参数属性的代码
  • 需要在生产环境中使用 source maps 的项目

使用原生类型剥离的场景:

  • 本地脚本和工具
  • 开发服务器(配合 --watch 使用)
  • 快速原型
  • SSR 开发构建

实用的开发配置

结合两种方法:

{
  "scripts": {
    "dev": "node --watch src/index.ts",
    "typecheck": "tsc --noEmit",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

开发环境使用原生执行以提高速度。CI 运行 typecheck。生产环境部署编译后的 JavaScript。

结论

现代 TypeScript Node.js 配置比旧指南所建议的更简单。使用 ESM,配置 NodeNext 模块解析,并根据上下文选择执行策略。原生类型剥离适用于开发和脚本。编译输出适用于生产和包。两种方法共享相同的源代码和 tsconfig——你不会被锁定在任何一种方法中。

常见问题

为什么在导入时需要使用 .js 扩展名,而我的源文件是 .ts?

Node 的模块解析在 ESM 中需要显式的文件扩展名。当你写 import from ./utils.js 时,Node 在运行时会查找该确切路径,即使你的源文件是 utils.ts。由于类型剥离会移除类型但不会重命名文件,而 tsc 会输出 .js 文件,因此在源代码中使用 .js 扩展名可确保导入在两种场景下都能正常工作。

我可以在 Node 的原生类型剥离中使用枚举吗?

默认情况下不可以。枚举需要代码转换,而不仅仅是类型移除。你可以启用 --experimental-transform-types 标志来支持枚举、命名空间和参数属性,但这会增加复杂性。对于更简单的配置,可以考虑使用带有 as const 断言的常量对象作为枚举的替代方案。

在 Node 24 中我还需要 ts-node 或 tsx 吗?

对于大多数用例,不需要。Node 24 的原生类型剥离可以处理直接的 .ts 执行。像 ts-node 和 tsx 这样的工具是可选的便利工具,它们添加了 tsconfig.json 支持、路径别名解析和完整的 TypeScript 转换而无需标志。只有在你的配置需要这些功能时才使用它们。

如何在没有编译 source maps 的情况下调试 Node 中的 TypeScript?

使用原生类型剥离时,Node 直接执行你的 .ts 文件,因此堆栈跟踪中的行号与你的源代码匹配。对于编译后的代码,在 tsconfig.json 中启用 sourceMap,Node 会自动使用 .js.map 文件在错误和调试器会话中显示原始 TypeScript 位置。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.