Skip to content

TypeScript 的编译器、运行方式与工具生态

很多人学 TypeScript 时,前半段通常会卡在类型系统,后半段则会卡在工程工具。

最常见的问题往往不是:

  1. interfacetype 有什么区别
  2. 泛型怎么写

而是:

  1. tsc 到底是什么
  2. ts-nodetsx 在干什么
  3. 为什么有的项目明明在用 TypeScript,却不是靠 tsc 产出最终构建代码
  4. Babelesbuildswc 和 TypeScript 到底是什么关系
  5. d.ts@types/* 为什么又总是一起出现

这一篇的目标,就是把这些工程问题放回一条稳定主线里理解。

1. 先分清 TypeScript 生态里到底在做哪几类事

围绕 TypeScript 的工具很多,但它们本质上主要在做下面几件事:

  1. 类型检查
  2. 代码转译
  3. 直接运行 ts 文件
  4. 生成声明文件
  5. 加速构建或开发体验

如果不先分清这几件事,就很容易把不同工具混成一句话:反正都是在“跑 TypeScript”。

这其实不准确。

把这层分工拉开看:

  1. 有的工具负责检查
  2. 有的工具负责把 TypeScript 变成 JavaScript
  3. 有的工具负责让你在开发阶段更方便执行 .ts
  4. 有的工具负责加速转译,但并不一定承担完整类型检查职责

2. tsc 到底是什么

tsc 是 TypeScript 官方编译器的命令行入口。

可以直接把它看成:TypeScript 世界里最基础、最官方的一把工具。

它最核心的职责通常包括:

  1. 读取 tsconfig.json
  2. 检查类型错误
  3. ts/tsx 转译成 js
  4. 生成声明文件,例如 .d.ts

例如:

bash
tsc
tsc --noEmit
tsc --watch

这几个命令的语义分别是:

  1. tsc:按当前配置做完整编译
  2. tsc --noEmit:只做类型检查,不输出文件
  3. tsc --watch:监听文件变化,持续重新检查或编译

3. 为什么说 tsc 既像编译器,又不完全等于“现代构建工具”

很多人一开始会把 tsc 理解成:只要用了 TypeScript,最后一定就是它来产出项目构建结果。

这并不总成立。

放到现代工程里看:

  1. tsc 是官方编译器
  2. 但现代前端项目最终的构建产物,很多时候是由 Vite、Webpack、esbuild、swc 一类工具链来完成的

所以工程里很常见的一种分工是:

  1. tsc --noEmit 做类型检查
  2. 用构建工具负责真正的转译、打包和产物输出

也就是说:tsc 在现代项目里经常承担“类型检查中枢”的角色,而不一定独自承担最终构建输出。`

4. ts-node 是什么

ts-node 可以看成 让 Node.js 在开发阶段更方便执行 TypeScript 文件的一层工具

如果没有它,你通常要先这样做:

  1. tscts 编译成 js
  2. 再用 node 去执行生成后的 js

ts-node 想解决的是:能不能直接跑 TypeScript,而不必每次都手动先编译一次。

例如:

bash
ts-node script.ts

它更适合的场景通常包括:

  1. 本地脚本
  2. 开发调试
  3. Node.js 工具脚本
  4. 较轻量的后端开发环境

5. ts-node 需要注意什么

ts-node 的价值很直接,但也要注意它的边界。

5.1 它更偏开发时便利,不是最终交付形态

工程上更常见的理解是:

  1. ts-node 更适合开发和调试
  2. 生产环境通常还是更常见编译后再运行

5.2 它不是“TypeScript 运行时”

这点很容易误解。

更贴近实际项目的情况是:

  1. 最终真正执行代码的仍然是 JavaScript 运行时
  2. ts-node 只是帮你把“编译 + 执行”这件事在开发阶段接得更顺

6. tsx 是什么

tsx 是近几年在很多前端和 Node.js 项目里越来越常见的一个工具。

可以看成 一个更轻、更快、更偏现代开发体验的 TypeScript 执行工具

例如:

bash
tsx script.ts

它通常会被拿来替代一部分原本用 ts-node 的场景,尤其是在:

  1. 本地脚本
  2. Node.js 开发阶段
  3. 需要快速启动的工具脚本

7. ts-nodetsx 怎么区分

这两个名字很容易被看成一类工具,这没错,但它们的体验定位确实不完全一样。

可以直接把它理解成:

工具更适合先怎么理解
ts-nodeTypeScript 生态里更经典的“直接执行 ts 文件”工具
tsx更轻、更现代、开发体验更顺的执行工具

如果只抓工程上最常见的判断:

  1. 历史项目里看到 ts-node 很常见
  2. 新项目或本地脚本场景里,tsx 现在越来越常见

8. d.ts 是什么

d.ts 是 TypeScript 的声明文件。

可以看成 只描述类型信息,不包含真正业务实现的一类文件

它主要在解决:

  1. 一个库本身是 JavaScript 写的,但我仍然想让 TypeScript 理解它的类型
  2. 一个库已经编译完成,但我仍然需要给使用方暴露清楚的类型边界

例如你可能看到:

  1. index.js
  2. index.d.ts

前者更偏实现,后者更偏类型描述。

9. @types/* 是什么

很多 JavaScript 库本身并没有直接内置 TypeScript 类型声明,或者历史上并不是用 TypeScript 写的。

这时你经常会看到:

  1. @types/node
  2. @types/react
  3. @types/lodash

可以看成 社区提供的第三方类型声明包

它们最常见的来源,是 DefinitelyTyped 这一套社区类型声明生态。

所以更贴近工程现实的说法是:

  1. 一个库能不能在 TypeScript 里舒服使用
  2. 往往不只取决于它有没有实现功能
  3. 还取决于它有没有足够稳定的类型声明

10. Babel 和 TypeScript 是什么关系

很多人会把 BabelTypeScript 理解成互相替代关系,这不准确。

把关系拉开看:

  1. TypeScript 是语言和类型系统
  2. tsc 是 TypeScript 官方编译器
  3. Babel 是一套更广义的 JavaScript 转译工具链

在工程里,Babel 可以处理 TypeScript 语法,但通常要特别注意一点:

Babel 更偏语法转译,不等于它天然承担完整类型检查。

这也是为什么有些项目会这样分工:

  1. tsc --noEmit 负责类型检查
  2. Babel 负责源码转译

11. esbuildswc 在做什么

esbuildswc 这两个词,现在在现代前端工具链里非常常见。

直接看成:

  1. 它们更偏高性能转译和构建底层能力
  2. 很多工具喜欢用它们来加速开发启动和构建过程

11.1 esbuild

可以看成 一个非常强调速度的转译和构建工具

它常常被用于:

  1. 快速转译 TypeScript
  2. 开发服务器预处理
  3. 某些轻量构建流程

11.2 swc

可以看成 另一个强调性能的现代转译器

它常见于:

  1. 高性能前端构建链路
  2. 一些框架或工具的底层转译阶段

11.3 它们和 tsc 的关系

最重要的不是背它们的实现语言或性能数字,而是先抓分工:

  1. tsc 更偏官方类型检查和标准编译能力
  2. esbuildswc 更偏高性能转译与构建加速

所以很多项目的真实情况是:真正做类型检查的是 tsc,而真正把源码快速转成可运行代码的,是 esbuild 或 swc。

12. 一张工具分工图

mermaid
flowchart TD
    A[TypeScript 源码] --> B[tsc 类型检查]
    A --> C[tsc 转译]
    A --> D[Babel esbuild swc 转译]
    A --> E[ts-node 或 tsx 开发阶段直接执行]
    C --> F[JavaScript 产物]
    D --> F
    E --> F
    A --> G[d.ts 声明文件]

这张图最重要的意思是:

  1. 围绕 TypeScript 的工具不是一把锤子解决所有问题
  2. 它们经常是分工协作关系
  3. 工程里最容易混的,正是“检查、转译、执行”这三件事

13. 为什么有的项目明明用了 TypeScript,却不靠 tsc 构建

这在现代前端项目里非常常见。

原因通常不是“tsc 不重要”,而是:

  1. 最终构建往往还涉及打包、代码拆分、资源处理、热更新等一整套流程
  2. 这部分工作更常由 Vite、Webpack 及其底层转译器承担
  3. tsc 在这种项目里更像负责类型检查和声明文件生成

所以你会经常看到这样的组合:

bash
tsc --noEmit
vite build

这并不冲突,反而是非常常见的分工方式。

14. 工程里常见的 TypeScript 组合方式

14.1 纯 tsc 方案

更适合:

  1. 库项目
  2. 较简单的 Node.js 项目
  3. 更强调标准 TypeScript 编译流程的场景

14.2 tsc --noEmit + 构建工具

更适合:

  1. 现代前端项目
  2. Vite、Webpack 一类工程
  3. 需要打包和资源处理的应用项目

14.3 tsxts-node 跑本地脚本

更适合:

  1. 开发脚本
  2. 工具脚本
  3. 本地调试任务

15. tsconfig 和这些工具是什么关系

很多人知道 tsconfig.json 很重要,但不清楚它和这些工具的关系。

可以把它看成:

  1. tsconfig.json 是 TypeScript 项目的核心配置文件
  2. tsc 会直接读取它
  3. 其他工具也常常会参考它的部分配置

它最常见影响的包括:

  1. target
  2. module
  3. strict
  4. paths
  5. declaration

所以很多工程问题最后其实会回到一句话:不是 TypeScript 不行,而是当前工具链对 tsconfig 的理解和接法没有对齐。

16. 工程里最容易踩的坑

16.1 误把 tsc 当成“唯一工具”

它很重要,但现代项目里它常常不是唯一角色。

16.2 误把 ts-nodetsx 当成生产部署形态

它们更偏开发便利,而不是所有项目默认的生产运行方式。

16.3 误把“语法能跑”当成“类型已经检查过”

有些工具可以很快转译代码,但这不等于它们已经替你做了完整类型检查。

16.4 看不到 d.ts@types/* 的价值

最后就会在接第三方库时频繁遇到:

  1. 类型缺失
  2. 补全不准
  3. 边界不清

16.5 不清楚当前项目到底是谁在检查、谁在转译、谁在执行

这会让很多问题越查越乱。

17. 一份检查清单

当你在工程里使用 TypeScript 时,至少可以自查:

  1. 当前项目是谁在做类型检查
  2. 当前项目是谁在把 TypeScript 转成 JavaScript
  3. 当前脚本是靠 nodets-node,还是 tsx 执行
  4. 第三方库的类型声明来自哪里
  5. 当前 tsconfig 是否和实际工具链配置对齐

18. 小结

这一篇最想建立的核心认识是:

  1. tsc 是 TypeScript 官方编译器,也是类型检查的核心入口
  2. ts-nodetsx 更偏开发阶段直接执行 TypeScript 的工具
  3. Babelesbuildswc 更常承担高性能转译或构建链路角色
  4. d.ts@types/* 组成了 TypeScript 很重要的声明文件生态
  5. 现代项目里最常见的,不是单工具包打天下,而是多工具分工协作

如果只压缩成一句话,可以记住:

围绕 TypeScript 的工具生态,核心不是记一堆名词,而是先分清谁在检查、谁在转译、谁在执行。

基于 VitePress 构建的个人技术笔记。