Node.js 现在能直接跑 TypeScript 了,tsx 和 ts-node 还需要吗?
大前端历险记 / 大鱼
以前写一个脚本,明明只有几十行 TypeScript,也要先纠结装 tsx、ts-node、ts-node-dev,再配一遍 tsconfig.json。现在 Node.js 已经能直接执行一部分 .ts 文件了,这件事对前端项目里的工具脚本很有吸引力。
但它不是"Node 内置了完整 TypeScript 编译器"。这篇我们用几个最小例子把边界跑清楚:哪些脚本可以直接交给 Node,哪些场景还是离不开 tsx 和 ts-node。
先说结论
如果你只是写项目脚本,比如清理文件、生成配置、调用接口、处理 JSON、跑一些构建前后的自动化任务,Node.js 原生 TypeScript 已经很够用。
如果你的代码依赖下面这些能力,暂时别急着删工具:
- 需要类型检查;
- 需要
enum、参数属性这类会生成运行时代码的 TypeScript 语法; - 需要
tsx/ JSX; - 依赖
tsconfig.json里的paths、baseUrl、jsx等编译配置; - 需要 watch、热重载、开发服务器体验。
一句话:Node 原生 TS 适合跑"接近 JavaScript 的 TypeScript 脚本",不适合替代完整工程编译链路。
准备一个最小 demo
先确认你的 Node 版本。建议使用当前较新的 Node LTS 或 Current 版本,版本太旧会没有这项能力。
bash
node -v
建一个干净目录:
bash
mkdir node-ts-native-demo
cd node-ts-native-demo
npm init -y
npm pkg set type=module
新建 scripts/hello.ts:
ts
type User = {
id: number;
name: string;
};
const users: User[] = [
{ id: 1, name: "Daisy" },
{ id: 2, name: "Tom" },
];
for (const user of users) {
console.log(`${user.id}: ${user.name}`);
}
直接跑:
bash
node scripts/hello.ts
如果你的 Node 版本支持原生 type stripping,会正常输出:
txt
1: Daisy
2: Tom
这里 Node 做的事情很克制:它把 type User = ...、: User[] 这些纯类型信息擦掉,然后按 JavaScript 执行剩下的代码。
它不会帮你做类型检查
这是最容易误会的地方。把刚才的代码改坏:
ts
type User = {
id: number;
name: string;
};
const user: User = {
id: "1",
name: "Daisy",
};
console.log(user);
id 明明应该是 number,我们传了字符串。但你直接运行:
bash
node scripts/hello.ts
它仍然会执行。
原因很简单:Node 原生能力只负责"擦类型并运行",不负责"检查类型是否正确"。所以项目里仍然应该保留 tsc --noEmit:
bash
npm install -D typescript
npx tsc --init
npx tsc --noEmit
我会把它们分工看成这样:
txt
node xxx.ts 负责快速运行脚本
tsc --noEmit 负责检查类型
不要因为 node script.ts 能跑,就把类型检查从 CI 里删掉。
哪些语法会踩坑
Node 的 type stripping 只能处理"擦掉以后就是合法 JavaScript"的 TypeScript 代码。有些 TS 语法不是单纯类型,它会生成运行时代码,这类就不能直接靠擦除解决。
最典型的是 enum:
ts
enum Role {
Admin = "admin",
User = "user",
}
console.log(Role.Admin);
enum 在 TypeScript 编译后会生成 JavaScript 对象。Node 如果只是擦类型,就没法凭空生成这段运行时代码。
更稳的写法是用普通对象:
ts
const Role = {
Admin: "admin",
User: "user",
} as const;
type Role = (typeof Role)[keyof typeof Role];
console.log(Role.Admin);
参数属性也是类似问题:
ts
class User {
constructor(public name: string) {}
}
它需要编译成属性赋值。改成普通 JavaScript 能理解的写法:
ts
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
如果你的项目大量使用这些语法,tsx 或 TypeScript 编译器仍然更省心。
tsconfig 不是它的主场
再看一个前端项目很常见的配置:
json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
然后在脚本里写:
ts
import { readConfig } from "@/shared/config";
这在 Vite、Next.js 或 tsx 里可能能跑,但 Node 原生执行 .ts 时不会自动理解你的 paths。它按 Node 自己的模块解析规则走,不会把 @/shared/config 翻译成真实路径。
所以工具脚本我更建议用相对路径,或者直接把脚本组织得简单一点:
ts
import { readConfig } from "../src/shared/config.ts";
这里有一个小细节:官方更推荐在 TypeScript 文件里写清楚可运行的扩展名,例如 .ts、.mts、.cts。这样 Node 执行时不需要猜模块类型。
它适合哪些前端脚本
我会优先把这些场景迁过去:
txt
scripts/
clean.ts
sync-env.ts
generate-routes.ts
check-i18n.ts
build-report.ts
比如写一个检查环境变量的脚本:
ts
const requiredEnvs = ["DATABASE_URL", "OPENAI_API_KEY"];
const missing = requiredEnvs.filter((key) => !process.env[key]);
if (missing.length > 0) {
console.error(`Missing env: ${missing.join(", ")}`);
process.exit(1);
}
console.log("All required envs are set.");
放到 scripts/check-env.ts,package.json 里直接这样写:
json
{
"scripts": {
"check:env": "node scripts/check-env.ts"
}
}
这类脚本的特点是:逻辑独立、依赖少、启动要快、不需要 JSX、不需要复杂别名。
以前为了跑它装一个运行时转换工具,现在可以少一个依赖。
tsx 和 ts-node 还剩什么价值
tsx 仍然很适合开发体验更重的场景:
- 要 watch 模式;
- 要快速跑一个依赖复杂的 TS 入口;
- 要支持
tsconfig paths; - 要跑 TSX/JSX;
- 要在本地开发服务里反复重启。
比如:
bash
tsx watch scripts/dev-server.ts
或者:
bash
tsx src/playground.tsx
ts-node 更常出现在老项目、测试工具链、REPL、或者一些依赖 TypeScript 编译器行为的环境里。它启动通常没 tsx 轻,但兼容历史生态更久。
我的建议是别做宗教选择,按场景拆:
| 场景 | 建议 |
|---|---|
| 简单项目脚本 | node xxx.ts |
| 类型检查 | tsc --noEmit |
| watch 开发脚本 | tsx watch |
| TSX / JSX | tsx 或框架自己的工具链 |
| 老项目已有 ts-node | 不急着迁 |
| CI 里的脚本任务 | 能用 Node 原生就用 Node 原生 |
我会怎么迁移老项目
不要一上来全仓替换。先从 scripts/ 目录挑一个最简单的脚本:
bash
node scripts/check-env.ts
能跑通后再做三件事:
bash
npx tsc --noEmit
npm run lint
npm run build
如果脚本用了 enum、路径别名、TSX,先别硬改。为了少一个 devDependency 重写一堆业务脚本,通常不划算。
比较稳的迁移标准是:
txt
迁移后代码更简单:可以迁
只是为了追新而改写一堆语法:别迁
对前端团队来说,这个能力最大的价值不是"替代 TypeScript",而是让很多边缘脚本不用再挂一层额外运行时。
资源链接
Node.js TypeScript 官方文档:nodejs.org/api/typescr...
Node.js 原生运行 TypeScript 指南:nodejs.org/en/learn/ty...
TypeScript 官方文档:www.typescriptlang.org/docs/
ts-node:typestrong.org/ts-node/
结尾
Node 原生 TypeScript 很适合给项目脚本减负,但它的定位是"擦类型并执行",不是替代完整 TypeScript 工具链。
你们项目里现在有多少 scripts/*.ts 是为了跑一个小任务,额外装了 tsx 或 ts-node?