本附录目标:把 TypeScript 生态里真正会被反复用到的工具按类别列成表,只讲「它是干什么的」与「什么情况下该选它」,不绑定任何具体版本,也不替你决定技术栈——选型永远取决于团队与场景。
附录 C 常用工具、库与资源
TypeScript 生态的一个特点是:官方只提供编译器和语言服务,其余全部由社区承担。这既是活力所在,也是新手最容易迷路的地方——同一个问题往往有五六种解法。
本附录只列类别与用途,不列具体版本号,也不给出任何外部链接。版本与 API 请以你所用工具自身随包发布的文档为准。表格里的「什么时候选它」是经验判断,不是硬性规则。
编译器与运行时
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
tsc | 官方编译器,同时负责类型检查与产出 JS | 需要严格类型检查、生成 .d.ts、构建库时首选 |
ts-node | 直接运行 .ts,无需预先编译 | 本地脚本、Node 环境的开发调试;生产构建不建议依赖 |
tsx | 基于 esbuild 的 TS 运行器 | 需要启动快、支持 ESM 的本地开发与一次性脚本 |
esbuild | 极快的打包与转译器,用 Go 编写 | 只做转译不做类型检查,适合开发服务器与构建流水线 |
swc | 用 Rust 编写的转译器 | 大型项目里替换 Babel 做语法降级,速度优先 |
bun | 内置 TS 支持的运行时与包管理器 | 新项目想「开箱即用跑 TS」,且能接受运行时替换 |
node --experimental-strip-types | Node 自带的类型擦除能力 | 只想在 Node 里直接跑 .ts,不想引入额外工具 |
deno | 内置 TS、安全沙箱优先的运行时 | 需要权限模型与内置工具链的一体化体验 |
关键认知:esbuild、swc、bun 只做转译,不做类型检查。它们的速度优势正来自「跳过类型」。所以典型组合是「构建用快工具,类型检查用 tsc --noEmit 单独跑一遍」。
// package.json 中常见的双轨脚本
{
"scripts": {
"build": "esbuild src/index.ts --bundle --outdir=dist",
"typecheck": "tsc --noEmit"
}
}
打包与构建
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
tsup | 以 esbuild 为内核的库打包器 | 发布 npm 包,需要同时产出 ESM/CJS 与 .d.ts |
vite | 开发服务器 + 构建工具 | 前端项目、需要 HMR 与快速冷启动 |
rollup | 以 tree-shaking 著称的打包器 | 构建库、产物需要极致精简 |
webpack | 生态最庞大的打包器 | 存量项目、需要大量 loader 与插件 |
parcel | 零配置打包器 | 原型或小项目,不想写任何构建配置 |
unbuild | 基于 rollup 的库构建工具 | 需要更细的产物控制(多入口、stub 模式) |
选型经验:应用选 vite,库选 tsup 或 rollup,存量项目别轻易迁移 webpack。迁移成本往往远高于收益,除非你确实卡在启动速度上。
本书第 16 章 esbuild/swc/tsup 与打包产物 详细讲了转译与类型检查的分工,以及为什么「构建产物里没有类型」。
类型检查与 lint
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
tsc --noEmit | 纯类型检查,不产出文件 | 所有项目的 CI 必跑项,与构建解耦 |
typescript-eslint | 基于 ESLint 的 TS 规则集 | 需要细粒度规则、生态插件丰富的老牌方案 |
biome | 集 lint 与格式化于一体的工具 | 新项目想用一个工具搞定 lint + format,配置简单 |
重要区分:类型检查(tsc)与 lint 是两件事。tsc 回答「类型对不对」,lint 回答「代码风格与潜在缺陷」。二者不能互相替代,CI 里应各跑一次。开启 strict 之后仍有一批规则(如浮空 Promise、未使用变量)需要 lint 兜底。
严格模式的配置细节见 16.1 编译目标与严格模式配置 。
编辑器与语言服务
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
tsserver | 官方语言服务,提供补全、跳转、重构 | 任何编辑器接入 TS 能力的底座,通常无需手动启动 |
typescript-language-server | 基于 tsserver 的 LSP 封装 | 非 VS Code 编辑器(如 Neovim、Emacs)接入 TS |
@typescript/vfs | 在内存里构建虚拟文件系统供编译器使用 | 写类型检查工具、在线演练场、代码生成器 |
typescript API | 以编程方式使用编译器 | 写 codemod、自定义 lint 规则、AST 分析脚本 |
编辑器体验的绝大多数问题都出在「编辑器用的 TS 版本与项目不一致」上。先确认版本,再谈配置。语言服务的能力边界(哪些重构能做、哪些不能)由编译器决定,与编辑器无关。
包管理与工作区
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
npm | Node 自带包管理器 | 追求零额外依赖、团队无需额外约定 |
pnpm | 内容寻址存储 + 严格依赖隔离 | Monorepo、依赖臃肿的项目;能显著省盘并暴露幽灵依赖 |
yarn | 包管理器,含工作区支持 | 存量项目沿用,或需要特定插件体系 |
turborepo | 任务编排与增量构建缓存 | Monorepo 里希望「只重建受影响包」 |
changesets | 版本与变更日志管理 | 需要按变更自动决定语义化版本号并生成发布说明 |
Monorepo 的典型难点不是「如何装依赖」,而是「如何只检查/构建受影响的包」。这需要包之间的依赖图,而依赖图的正确性又依赖 tsconfig 的 references 配置。相关做法见 16.3 Monorepo 与 Project References
。
文档与类型来源
| 类型来源 | 说明 | 什么时候用 |
|---|---|---|
包自带 .d.ts | 作者随包发布,最权威 | 优先选择;package.json 的 types 字段指向它 |
@types/* 社区包 | DefinitelyTyped 维护,版本独立于原包 | 原包无类型时使用,注意版本可能滞后 |
自写 .d.ts | 本地声明补丁 | 前两者都缺时使用,可随项目一起演进 |
tsc --declaration 产出 | 由源码自动生成 | 自己发布的库应产出,消费方无需手写 |
一个容易被忽略的事实:@types/* 的版本号与原包不同步。原包升级后 @types 可能仍停留在旧 API,这类不一致往往表现为「运行时正常、类型报错」。遇到时先核对两者版本,再决定是升级 @types 还是自己打补丁。
测试
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
vitest | 与 Vite 同源的测试框架 | Vite 项目首选,原生 TS/ESM 支持,启动快 |
jest | 老牌测试框架 | 存量项目、已有大量 jest 配置与插件 |
tsd | 类型层面的断言库 | 校验 .d.ts 对外暴露的类型是否符合预期 |
expect-type | 轻量的类型断言工具 | 想在普通测试文件里顺手写类型断言 |
msw | 在测试中拦截网络请求 | 需要稳定的 API 桩,避免测试依赖真实后端 |
playwright | 端到端浏览器测试 | 需要覆盖真实渲染与交互路径 |
运行测试验证「行为」,类型测试验证「类型」。发布库时两者都需要:前者防止逻辑回归,后者防止类型回归——类型回归尤其隐蔽,因为它在运行时毫无表现。
类型测试的具体写法见 15.2 类型测试(tsd/expect-type) 。
运行时校验
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
zod | 声明式 schema,从 schema 推导静态类型 | 需要在 API 边界做校验并复用同一份契约 |
valibot | 函数式、可摇树优化的校验库 | 对包体积敏感(如边缘运行时、前端首屏) |
arktype | 用类型语法直接写校验规则 | 希望校验规则读起来就是 TS 类型 |
ajv | 基于 JSON Schema 的校验器 | 契约本身是 JSON Schema(如 OpenAPI 生成物) |
typebox | 用 TS 生成 JSON Schema | 需要在运行时校验与 JSON Schema 之间共用一份定义 |
这一类的存在理由只有一个:类型在运行时被擦除,任何来自网络、文件、localStorage 的数据在类型层面都是「声称」而非「事实」。在系统边界处校验一次,内部就能放心使用静态类型。
Zod 的推导机制见 13.2 Zod 模式验证与类型推导 。
后端框架
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
fastify | 高性能、schema 驱动的 HTTP 框架 | 需要稳定成熟、插件生态完善的传统服务 |
hono | 轻量、跨运行时的 Web 框架 | 需要同时跑在 Node、边缘函数、Bun 等多家运行时 |
nestjs | 带依赖注入与模块化的企业级框架 | 团队规模较大、需要强约定与分层架构 |
express | 最简 HTTP 框架 | 存量项目,或只需要一层极薄的路由 |
trpc | 端到端类型安全的 RPC | 前后端同仓、希望「改后端类型前端立刻报错」 |
三个框架代表三种哲学:fastify 重性能与插件、hono 重可移植与轻量、nestjs 重架构与约定。选型时先问「我的部署环境是什么」,再问「团队需要多少约束」。
ORM 与数据访问
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
prisma | schema 文件驱动的 ORM,自动生成类型安全客户端 | 想要开箱即用的类型安全查询与迁移工具链 |
drizzle | 以 TS 写 schema 的轻量 ORM | 偏好 SQL 手感、希望 schema 就是代码、产物更薄 |
kysely | 类型安全的查询构建器 | 想保留手写 SQL 的结构,同时获得列名与结果类型检查 |
typeorm | 装饰器风格的 ORM | 存量项目沿用,或偏好实体类写法 |
两者的共同点是把数据库表结构变成 TypeScript 类型,让「字段改名漏改一处」这类错误在编译期暴露。差别在于 prisma 把 schema 放在独立文件并用代码生成,drizzle 把 schema 直接写成 TS 代码。
前端
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
react | 组件库 | 生态最大、招人最容易、需要大量现成组件 |
vue | 组件库 | 模板语法上手快、团队有 Vue 背景 |
next.js | React 全栈框架 | 需要 SSR/SSG、路由与后端一体化 |
tanstack query | 服务端状态管理 | 需要缓存、重试、失效等异步数据编排能力 |
svelte / solid | 编译期优化的组件库 | 想减少运行时开销,且团队愿意接受新范式 |
tanstack router | 类型安全的路由 | 需要「路由参数与路径都有类型」的强约束 |
前端的类型收益集中在两处:组件 props 与 API 响应。前者靠框架自身的泛型支持,后者靠前后端共享类型。共享类型的做法见 17.2 前后端共享类型与 API 契约 。
迁移与渐进采用
| 工具 | 用途 | 什么时候选它 |
|---|---|---|
allowJs / checkJs | 允许并检查 .js 文件 | 从 JS 项目渐进迁移的第一步 |
// @ts-check | 单文件开启检查 | 只想先给关键文件加类型,不改构建 |
ts-migrate | 批量迁移辅助工具 | 大型 JS 代码库需要一次性铺开类型 |
tsc --init | 生成初始配置 | 新项目起步,或想对照默认选项 |
渐进迁移的顺序建议是:先让编译器参与(allowJs + checkJs),再逐文件加 // @ts-check,最后重命名 .js 为 .ts 并收紧 strict。反过来做(一上来全开 strict)几乎必然失败,因为错误数量会淹没团队。完整方法论见 18.1 JavaScript 项目渐进式迁移
。
学习资源类型
资源按「用途」分四类,比按「形式」分更有用:
| 资源类型 | 解决什么问题 | 什么时候用 |
|---|---|---|
| 官方手册与参考 | 查语法的权威定义与行为边界 | 需要确认某个写法是否合法、语义如何 |
| 类型挑战类练习 | 把类型体操练成肌肉记忆 | 已掌握基础,想突破高级类型与类型性能 |
| 社区规范与风格指南 | 统一团队写法,避免无谓争论 | 团队协作前制定约定,或接入 lint 预设 |
| 开源项目源码 | 学习真实工程如何组织类型 | 想找「可运行的范例」而非玩具示例 |
使用建议:官方手册当字典查,类型挑战当习题做,社区规范当约束用,开源源码当范本读。四者角色不同,不要拿其中一种替代另一种——尤其是不要只靠碎片化文章学类型系统。
本书也提供了成体系的自学路径:18.3 学习路径与生态选型 。
组合建议
把上面的表拼起来,三条常见技术栈组合如下:
| 场景 | 组合 | 理由 |
|---|---|---|
| 前端应用 | vite + vitest + typescript-eslint + zod | 开发快、测试同源、边界校验统一 |
| Node 后端 | tsx(开发)+ tsc --noEmit(CI)+ fastify + prisma | 类型检查与构建解耦,数据层类型安全 |
| 发布 npm 库 | tsup + tsc --noEmit + vitest + tsd | 双格式产物、类型与行为双重回归 |
| Monorepo | pnpm + turborepo + Project References | 只构建受影响包,依赖图由 tsconfig 保证 |
无论选哪条,CI 里都必须有一条独立的 tsc --noEmit。这是整个工具链里唯一不可省略的一环:其他工具都可以替换,只有它真正做类型检查。
把选型拆成三个问题,答案会更清晰:
| 问题 | 影响的选择 |
|---|---|
| 部署到哪(Node / 边缘 / 浏览器) | 运行时、框架、是否能用 Node 专有 API |
| 产物给谁用(应用 / 库) | 打包器、是否需要产出 .d.ts、是否需要多格式 |
| 团队需要多少约束 | 是否上 nestjs 这类强约定框架、lint 规则的严格程度 |
先回答第一个问题,因为它决定的上限最多;第三个问题最容易引发争论,但通常影响最小。
三个常见反模式
选型阶段有三个反复出现的错误,值得单独列出:
| 反模式 | 表现 | 后果 |
|---|---|---|
| 用转译器代替类型检查 | 只跑 esbuild / swc,CI 里没有 tsc --noEmit | 类型错误一路漏到生产,类型系统形同虚设 |
| 为了「类型安全」引入全站校验 | 每个内部函数入口都写一遍 schema 校验 | 运行时代价翻倍,收益却集中在边界那一处 |
| 追新工具链而忽略团队 | 每季度换一次打包器与 lint 方案 | 配置知识无法沉淀,出问题无人能修 |
判断某个工具是否值得引入,只需问一句:它替代的那件事,现在真的在疼吗? 如果答案是否定的,那就是在给未来的自己增加维护面。
一份最小可用的工具清单
如果不想在一开始就做复杂选型,下面这套组合足以支撑绝大多数中小型项目,之后按需替换即可:
- 类型检查:
tsc --noEmit,接入 CI,作为唯一的类型真相来源。 - 开发运行:
tsx或bun,直接执行.ts文件,省去构建步骤。 - 构建产物:
tsup(库)或vite(应用),转译快、配置少。 - 测试:
vitest,与 TypeScript 的集成开箱即用。 - Lint:
typescript-eslint,其中带类型信息的规则能发现普通 lint 找不到的问题。 - 运行时校验:
zod,只用在接口边界与配置读取处。
这套组合的共同点是「每一件工具都只负责一件事」,替换其中任意一件都不需要动其他部分。等到某个环节真的成为瓶颈,再针对它做替换,比一开始就搭一套复杂流水线要稳妥得多。
延伸阅读
本站另有几篇偏工程实践的文章,可作为本附录的补充:
- TypeScript CLI 开发 :把 TS 用于命令行工具时的参数解析与产物组织。
- TypeScript 模块解析与 ESM/CJS 互操作 :第 8 题与第 9 题的完整背景。
- TypeScript Monorepo 与 Turborepo :工作区与增量构建的落地细节。
- TypeScript 类型安全测试 :类型测试在 CI 中的接入方式。
- TypeScript 运行时校验与类型安全 :校验库的取舍与边界划定。
- TypeScript 与 Node.js 后端 :后端技术栈的整体组织。
- TypeScript 项目架构与 tsconfig :配置文件的组织方式。
- TypeScript 构建性能优化 :编译变慢时先看什么。
小结
- TypeScript 生态只有编译器与语言服务是官方的,其余全部由社区提供,因此「同一问题多种解法」是常态而非异常。
tsc负责类型检查,esbuild/swc/bun负责快速转译,二者职责不同、必须并存,CI 中独立的tsc --noEmit不可省略。- 类型检查与 lint 是两件事,前者管类型正确性,后者管代码风格与潜在缺陷,应各跑一次。
- 运行时校验库(
zod等)存在的唯一理由是「类型在运行时被擦除」,只应在系统边界使用,不必全站铺开。 - 后端框架、ORM、前端框架的选择本质是「团队与部署环境」的选择,没有普适最优解;先确定部署环境,再确定约束强度。
- 学习资源要按用途分工:官方手册当字典、类型挑战当习题、社区规范当约束、开源源码当范本。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。