构建工具集成
本教程共 80 篇 · 第 79 篇 · 更新于 2026-08-10 · 约 13 分钟阅读
本节目标:搞懂 TypeScript 在现代前端构建流程里到底扮演什么角色——它不是一个”独立的构建工具”,而是一套类型检查管线。学完你会配置 Vite 项目里的 TS 双检查流程,理解 Webpack 的两种 TS 集成路线,以及为什么”类型检查与转译分离”是主流理念。
TypeScript 在构建流程里的位置
先搞清楚一个容易混淆的概念:TypeScript 编译器(tsc)能做两件事——转译(把 TS 编译成 JS)和类型检查。但现代前端工具链里,这两件事通常是分开的。
为什么分开?因为转译可以用更快的工具做(esbuild、SWC、Babel),而类型检查只能靠 tsc。一个 Vite 项目里典型的流程长这样:
你的 .ts 源码
│
├──→ esbuild(Vite 内置)→ 剥离类型、转译成 JS → Vite 打包 → 浏览器
│ 只做转译、不做类型检查、超快
│
└──→ tsc --noEmit → 只做类型检查、不产出 JS 文件
在 CI 或编辑器中运行
也就是说 TypeScript 在你的项目里充当的是”静态分析器”的角色——它告诉你能不能通过类型检查,但不负责把代码跑起来。
“你的浏览器里没有 TypeScript”——这句话值得记住。浏览器只认识 JS/HTML/CSS。所有的类型标注、泛型、接口最终都会被构建工具剥掉,变成纯 JS。构建工具帮你完成了从”开发环境有类型”到”运行环境只有 JS”的翻译工作。
Vite:内置 TS 支持,但只做转译
Vite 从第一天起就内置了 TypeScript 支持。你在 Vite 项目里写 .ts / .tsx 文件,不需要额外装任何插件。
但它的 TS 支持有一个边界:**Vite 用 esbuild 做转译,不做类型检查。**这意味着:
- 你的
.ts文件会被快速转成.js,开发服务器秒启动 - 类型错误不会阻塞开发服务器——就算你有 10 个类型错误,Vite 照样把页面跑起来
- 类型检查需要一个独立的步骤,由 tsc 来完成
这就是 esbuild 的”只管转不管查”策略。esbuild 在转译时直接把类型标注删掉——它不检查类型是否匹配:
// 你写的代码
function greet(name: string): string {
return `Hello, ${name}!`;
}
// esbuild 转译后(近似)
function greet(name) {
return `Hello, ${name}!`;
}
快的原因:esbuild 是用 Go 写的原生二进制,不需要加载 Node.js 的庞大类型系统。
Vite 项目的标准 TS 配置
创建一个 Vite + TS 项目的标准方式:
npm create vite@latest my-app -- --template vanilla-ts
默认生成的 tsconfig.json 核心配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "react-jsx",
"skipLibCheck": true,
"noEmit": true,
"isolatedModules": true
},
"include": ["src"]
}
两个关键选项:
noEmit: true:tsc 不产出 JS 文件。因为 Vite 的 esbuild 已经在做转译了,用不着 tsc 再输出一份。isolatedModules: true:开启单文件转译模式。这个选项要求你的 TS 代码在不依赖全局类型的前提下能被正确转译——跟 esbuild 的”逐文件转译”行为对齐。
双管线:转译管线 + 类型检查管线
Vite 项目推荐在 package.json 里配两条独立的 script:
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"typecheck": "tsc --noEmit",
"preview": "vite preview"
}
}
dev:开发模式。Vite 负责转译 + 热更新,不做类型检查。如果你需要在开发时即时看到类型错误,依赖编辑器的 TypeScript 语言服务(VS Code 默认就有)。build:生产构建。先跑tsc -b(完整类型检查 + 构建模式),通过了再让 Vite 打包。类型错误在这里会阻塞构建——在生产环境里不让有类型问题的代码上线。typecheck:纯类型检查命令。在 CI 里单独跑,或者在提交前跑一遍。
Tip
tsc -b(构建模式)比tsc --noEmit更适合 CI。前者利用增量构建缓存(.tsbuildinfo),第二次跑会快很多。TypeScript 7.0 的增量构建在 Go 编译器下更加高效。
编辑器层面的类型检查
VS Code 的 TypeScript 扩展会在你保存文件的时候实时做类型检查。所以开发模式下(vite dev),类型检查并没有消失——只是换了一个地方运行:
| 场景 | 谁做类型检查 | 阻塞开发? |
|---|---|---|
| 写代码时 | VS Code 的 TS 语言服务 | 不阻塞,红色波浪线提醒 |
| 开发服务器 | 不做 | — |
| 生产构建 | tsc -b | 阻塞 |
| CI / pre-commit | tsc --noEmit | 阻塞 |
这种分层设计是当前前端工程化的共识——开发体验要快、发布流程要严格。
Webpack:两条路线,两种哲学
Webpack 的 TypeScript 集成比 Vite 老得多,也复杂得多。历史上主要有两条路线。
路线 A:ts-loader
ts-loader 是 TypeScript 官方维护的 Webpack loader。它直接在 Webpack 构建流程里调用 tsc:
npm install -D ts-loader typescript
// webpack.config.js
module.exports = {
entry: "./src/index.ts",
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
};
ts-loader 的优势是完整——它调用真正的 tsc,所以能做完整的类型检查。TypeScript 7.0 下 ts-loader 本身没问题,但注意:
TypeScript 7.0 没有暴露稳定的编译器 API。如果你的 ts-loader 配置使用了
transpileOnly: true(跳过类型检查仅转译),可以继续用。如果用了完整的类型检查模式(依赖编译器 API),这需要等 TypeScript 7.1 的新 API 或者继续用@typescript/typescript6。
// ts-loader 的 transpileOnly 模式
{
test: /\.tsx?$/,
use: {
loader: "ts-loader",
options: {
transpileOnly: true, // 只转译,不做类型检查
},
},
}
transpileOnly: true 模式下 ts-loader 不会做类型检查,你可以用 fork-ts-checker-webpack-plugin 额外起一个进程单独做类型检查——这就是”转译与检查分离”理念在 Webpack 里的体现。
路线 B:Babel + @babel/preset-typescript
另一条路线是完全不用 tsc 做转译,改用 Babel:
npm install -D @babel/core @babel/preset-env @babel/preset-typescript babel-loader
// webpack.config.js
module.exports = {
entry: "./src/index.ts",
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-typescript",
],
},
},
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
};
@babel/preset-typescript 做的是和 esbuild 一样的事:**把类型标注删掉,不检查类型是否正确。**它的优势在于:
- Babel 生态丰富——如果你项目里已经用了很多 Babel 插件(polyfill、JSX 转换等),加一个 preset 就能支持 TS
- 转译速度比 ts-loader 快(不调用 tsc 的完整管线)
- 类型检查完全由 tsc 单独负责,职责分离
两条路线对比
| ts-loader | Babel + preset-typescript | |
|---|---|---|
| 转译原理 | 调用 tsc 的完整编译管线 | Babel 逐文件删除类型语法 |
| 类型检查 | 可内置(有性能代价) | 必须单独跑 tsc |
| 速度 | 默认模式较慢,transpileOnly 较快 | 快 |
| 生态 | 依赖 TypeScript 编译器 | 融入 Babel 生态 |
| 适合场景 | 想要”一站式”方案的小项目 | 已有 Babel 配置的项目 |
| TS 7.0 兼容性 | transpileOnly 可继续;完整模式待 7.1 API | 完全兼容 |
目前的推荐:如果新项目用 Webpack,Babel + preset-typescript + tsc --noEmit 是最干净的方案。ts-loader 的 transpileOnly 模式也是一个不错的选择。
Note除了 ts-loader 和 Babel,还有
esbuild-loader和swc-loader这两种更快的选择。它们都遵循”只转译不检查”的模式,本质哲学一致。
类型检查与转译分离:为什么这是对的
回到本章开头抛出的那个问题——为什么要把类型检查和转译分开?
原因有三:
1. 速度
转译工具(esbuild、SWC、Babel)可以并行处理文件,因为它们不需要”理解”跨文件的类型关系。但类型检查必须构建一个完整的类型关系图——A 函数返回的类型要跟 B 函数的参数类型对上、interface 的继承链要解析完。这个过程天然是”全局”的。
把”快的事情”和”慢的事情”拆开,快的不会等慢的。
2. 职责清晰
构建工具的职责是”让我看到我的代码跑起来的样子”,类型检查器的职责是”告诉我代码有没有类型错误”。这两个目标在开发阶段是独立的——你改了一个按钮的颜色,你不需要跑一遍完整的类型检查来验证。
3. 工具替换灵活
如果转译和检查耦合在一起,你想换个更快的转译工具就得重新处理类型检查的集成。分离之后,你可以今天用 esbuild、明天换 SWC,类型检查那边完全不受影响。
这套理念在 TypeScript 7.0 时代只会更强化——tsc 变成了一个更快的 Go 原生二进制,但它仍然是一个”独立进程”,通过 LSP 协议和编辑器/构建工具通信。
TypeScript 7.0 在构建工具方面的变化
TypeScript 7.0 对构建工具的影响主要在两点:
并行检查器的引入
TypeScript 7.0 的实验性并行类型检查器(--checkers 标志)让你在做 tsc --noEmit 类型检查的时候能利用多核 CPU。对于 CI 里的类型检查步骤,这能显著缩短时间:
# TypeScript 7.0,用 8 个 worker 并行检查
npx tsc --noEmit --checkers 8
默认是 4 个 worker。具体该设多少取决于你的 CI 机器的 CPU 核心数——设太高反而会有调度开销。建议从默认值开始,根据实际情况调整。
框架项目的兼容性
Vite 的 TS 支持不受影响——它用的是 esbuild 做转译,和 TypeScript 编译器没有直接 API 依赖。
Webpack 的 Babel + preset-typescript 路线也不受影响。
受影响的是依赖 TypeScript 编译器 API 的工具:ts-loader 的完整模式、一些自定义的 Webpack plugin、以及 Angular/Vue/Svelte 框架中的编辑器语言服务。这些工具在 TypeScript 7.1 发布新 API 之前,需要继续使用 TypeScript 6.x 的 API(通过 @typescript/typescript6 包)。
详细的框架项目升级建议见第 80 章 “TypeScript 7.0 迁移指南”。
回顾全书:从类型到工程
这是本书倒数第二章。走到这里,你已经:
- 掌握了 TypeScript 的类型系统(第 8–53 章)
- 学会了把 JS 项目迁移到 TS 的策略(第 78 章)
- 理解了构建工具如何与 TypeScript 协作(本章)
你不再是一个”在 Playground 里写类型体操”的初学者了。你理解 TypeScript 在生产环境里是怎么跑起来的——它跟 Vite 怎么配合、跟 Webpack 怎么集成、为什么 CI 里要跑 tsc --noEmit。
下一章(第 80 章)是本书的最后一章——一个实际的 TypeScript 7.0 迁移指南。从 5.x/6.x 升到 7.0,你需要改什么、注意什么、做完之后能获得什么收益。把那一章读完,你就可以自信地把团队项目升级到 TypeScript 7.0 了。