TypeScript 文件不能原样运行,因为浏览器和 JavaScript 引擎看不懂类型注解,必须先有工具把类型去掉。这个工具要么是 TypeScript 编译器(tsc),它同时会检查类型;要么是只剥离类型的更快的工具。运行本页代码最快的方式就是点击“运行”按钮:
输出:
[x] Install TypeScript
[ ] Run a .ts file
这些文档中的每个可运行代码块都是这样工作的:代码会先由开启了 strict 的 TypeScript 7 做类型检查,只有在没有类型错误时才会运行。想做更长的实验,可以用 TypeScript 在线编辑器,它是同一个编辑器,只是单独放在一个页面上。本页其余部分讲的是如何在你自己的电脑上运行 .ts 文件。
各种方式一览
| 命令 | 检查类型 | 需要构建步骤 | 支持 enum、namespace、参数属性 |
|---|---|---|---|
先 npx tsc 再 node dist/index.js | 是 | 是 | 是 |
node index.ts(Node.js 22.18+、23.6+) | 否 | 否 | 否 |
npx tsx index.ts | 否 | 否 | 是 |
npx ts-node index.ts | 是 | 否 | 是,但不支持 TypeScript 7 |
deno run index.ts | 否(deno check 会检查) | 否 | 是 |
bun index.ts | 否 | 否 | 是 |
最让人意外的是第一列:大多数快速的方式都会运行带有类型错误的代码。典型的项目会用其中一种方式运行代码,再在编辑器和 CI 中单独运行 tsc --noEmit 来发现错误。
先用 tsc 编译,再用 Node 运行
这种方式在任何地方都能用,而且会检查所有内容。在项目中安装了 TypeScript,并且 tsconfig.json 设置了 "rootDir": "./src" 和 "outDir": "./dist" 时:
npx tsc
node dist/index.js
tsc 会对所有文件做类型检查,然后把 .js 文件写入 dist。如果只是单个文件、没有项目,就传入文件名。这时它会使用默认选项,并把 index.js 写在 index.ts 旁边:
npx tsc index.ts
node index.js
(如果文件夹里有 tsconfig.json,tsc 会以 error TS5112 拒绝文件名参数;直接运行 npx tsc,或者加上 --ignoreConfig。)
默认情况下,即使存在类型错误,tsc 仍然会输出 JavaScript,所以 node 可能会运行一个没通过检查的程序。在配置中加上 "noEmitOnError": true 可以避免这种情况,或者在脚本中把命令串起来,让第二步只在第一步成功后才运行:
{
"scripts": {
"build": "tsc",
"start": "tsc && node dist/index.js"
}
}
开发时,npx tsc --watch 会在每次保存时重新编译。
用 Node.js 直接运行 TypeScript
当前版本的 Node.js 可以自己运行 .ts 文件:
node index.ts
Node 会剥离类型注解,用空白字符替换它们,让堆栈跟踪中的行号依然对得上,然后运行剩下的代码。这项功能从 Node.js 23.6.0 和 22.18.0 起默认开启,从 24.3.0 和 22.18.0 起不再打印警告,并在 Node.js 24.12.0 和 25.2.0 中被标记为稳定。更早的、已经带有该功能的版本(22.6 至 22.17,以及 23.0 至 23.5)需要加上参数:node --experimental-strip-types index.ts。
它有四条规则:
- 不做类型检查。 包含
const age: number = "forty"的文件照样能运行,并打印forty。 - 只支持可擦除的语法。 凡是需要变成 JavaScript 代码而不是直接消失的语法都会被拒绝:
enum、带运行时代码的namespace块、像constructor(private name: string)这样的构造函数参数属性,以及import x = require()别名。Node 会以SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode终止运行。 - 忽略
tsconfig.json。paths或target之类的选项都不起作用。 - 导入时要写真实的文件名。 写成
import { add } from "./math.ts",带上扩展名,并用type标记只导入类型的部分:import { add, type Pair } from "./math.ts"。如果不加type,Node 会去找名为Pair的运行时导出,并报错SyntaxError: The requested module './math.ts' does not provide an export named 'Pair'。
有两个编译选项能让 tsc 执行同样的规则,这样编辑器会抢在 Node 之前提醒你:"erasableSyntaxOnly": true 会对 enum 报告 error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.,"verbatimModuleSyntax": true 要求只导入类型时加上 type 关键字。如果想在导入中继续写 .ts 扩展名,同时还能用 tsc 编译,就加上 "rewriteRelativeImportExtensions": true,它会在输出中把 ./math.ts 改成 ./math.js。
Node.js 24 还有 --experimental-transform-types,它会为枚举和参数属性生成代码,而不是拒绝它们。它会打印 ExperimentalWarning,而且 Node.js 26 已经移除了这个参数,所以不要依赖它。
下面这个代码块用到了 node index.ts 会拒绝的两个特性。它在这里能运行,是因为编辑器用 TypeScript 编译器来编译它,而编译器会为这两者生成 JavaScript:
同一段代码的可擦除版本用 const 对象和普通字段来写,Node 可以原样运行:
tsx
tsx 能一步运行 TypeScript 文件,无需任何配置,也不限制语法:
npm install --save-dev tsx
npx tsx index.ts
npx tsx watch index.ts # rerun on every change
它用 esbuild 转换代码,所以枚举、命名空间和参数属性都能用,不带扩展名的导入也会像在打包工具中那样被解析。和 Node 的类型剥离一样,它不做类型检查。在还不支持类型剥离的 Node.js 版本上,或者代码用到了 Node 会拒绝的语法时,它是运行脚本、开发服务器和测试的常见选择。
ts-node
多年来,ts-node 一直是在 Node.js 上运行 TypeScript 的标准方式,很多教程和老项目至今仍在使用它(npx ts-node index.ts、node -r ts-node/register)。它默认会借助 TypeScript 编译器的 JavaScript API 做类型检查。
而这个 API 恰恰是 TypeScript 7 不再提供的:它的编译器是一个原生程序,typescript 7 包没有向 JavaScript 暴露任何编译器 API。安装了 TypeScript 7 后,ts-node 还没运行任何代码就会崩溃:
TypeError: Cannot read properties of undefined (reading 'fileExists')
at readConfig (/project/node_modules/ts-node/dist/configuration.js:91:33)
ts-node 的最新版本 10.9.2 发布于 2023 年 12 月。新代码请使用 tsx 或 node index.ts。依赖 ts-node 的现有配置,只要项目继续使用 TypeScript 6(npm install --save-dev typescript@6)并且有一个 tsconfig.json(哪怕是空的 {}),就能继续工作。如果没有这个文件,ts-node 会退回到内置的默认设置,其中包含 TypeScript 6 已弃用的 node10 模块解析方式,于是 npx ts-node index.ts 既不运行文件,也不打印错误就直接退出了。
Deno 与 Bun
这两个运行时都把 TypeScript 当作一等公民的文件类型:
deno run index.ts # runs without checking
deno check index.ts # type-checks, reports errors, runs nothing
bun index.ts # runs without checking
它们都不需要安装 typescript,也不需要 tsconfig.json,并且都支持 enum 和其他不可擦除的特性。Deno 自带一份 TypeScript 编译器,供 deno check 使用。Bun 只剥离类型,所以在 Bun 项目中你仍然需要安装 typescript,并运行 tsc --noEmit 来找出类型错误。
只有 tsc 会因类型错误而阻止程序运行
只有先运行 tsc 的方式才会拒绝运行带类型错误的程序。本页的编辑器就是其中之一,所以下面这个代码块会停在编译器这一步:
index.ts(6,21): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
同样的代码保存为文件后,用 node index.ts、npx tsx index.ts 或 bun index.ts 运行,会正常运行并打印 12,因为 3 * "4" 会把字符串转换成数字。所以即使用更快的工具运行代码,也要在流程中保留 tsc --noEmit:
{
"scripts": {
"dev": "tsx watch src/index.ts",
"typecheck": "tsc --noEmit"
}
}
应该选择哪一种?
- 学习或快速测试: 用这些页面上的“运行”按钮,或者在线编辑器。
- 在新版 Node.js 上运行脚本或小工具: 用
node index.ts,并在配置中开启erasableSyntaxOnly,让编辑器标出 Node 会拒绝的任何语法。 - 任何 Node.js 项目、任何语法: 用
tsx运行,用tsc --noEmit检查。 - 库或任何要发布的代码: 用
tsc,因为它还会生成用户需要的.d.ts文件。 - 前端代码: 你的打包工具或框架(Vite、Next.js、Angular CLI)会替你运行 TypeScript;再加上
tsc --noEmit做检查。
常见问题
如何运行 TypeScript 文件?
经典做法分两步:npx tsc 把 .ts 编译成 .js,再用 node dist/index.js 运行输出结果。在 Node.js 22.18 或 23.6 及以上版本中,只要文件只用到可擦除的类型语法,也可以直接运行 node index.ts。npx tsx index.ts 能一步运行任何 TypeScript 文件。
Node.js 能直接运行 TypeScript 吗?
可以。从 Node.js 23.6 和 22.18 开始,node file.ts 无需任何参数就能运行:Node 会去掉类型注解,再运行剩下的代码。它不做类型检查,会忽略 tsconfig.json,并且拒绝需要生成代码的语法,比如 enum、带运行时代码的 namespace 以及构造函数参数属性。
ts-node 支持 TypeScript 7 吗?
不支持。ts-node 调用的是编译器的 JavaScript API,而 typescript 7 包不提供这个 API,所以它一启动就会崩溃(Cannot read properties of undefined (reading 'fileExists'))。它的最后一个版本是 2023 年 12 月发布的 10.9.2。请改用 tsx、Node 自带的类型剥离,或者让 ts-node 继续搭配 TypeScript 6 使用。
tsx 和 ts-node 有什么区别?
tsx 只负责去掉类型(借助 esbuild)并运行结果,所以启动很快,但从不报告类型错误。ts-node 默认会用 TypeScript 编译器做类型检查,因此更慢,并且依赖编译器的 JavaScript API。现在大多数项目用 tsx 或 node file.ts 来运行,再配合 tsc --noEmit 做检查。
有在线的 TypeScript 运行环境吗?
有。这些文档页面上的代码块和 Coddy 的 TypeScript 在线编辑器都会用 TypeScript 7 编译并运行你的代码,显示编译错误或程序输出。typescriptlang.org 上的官方 TypeScript Playground 会显示生成的 JavaScript 和错误信息。