4.3 构建工具集成


4.3 构建工具集成

这节在地图里把"类型检查"和"代码转译"两件事拆开。很多慢和错,源于把 tsc 和打包器职责混在一起。我们讲清边界,你就不会再纠结"为什么 Vite 不报类型错"。

两件事,两种工具

  • 类型检查:tsc 的职责,慢但准。
  • 代码转译:把 TS/ESNext 翻成目标 JS,Babel/esbuild/SWC 更快,但默认不检查类型。
# 仅类型检查(不产出) npx tsc --noEmit # 仅转译(不检查),esbuild 极快 npx esbuild src/index.ts --bundle --outfile=dist/index.js

输入输出:tsc --noEmit 报类型错误但不出文件;esbuild 出文件但不管类型。两者职责正交,可并行跑。

Vite/esbuild:转译快,类型交给 tsc

现代前端构建链(Vite 用 esbuild)默认只剥离类型、不检查。类型安全靠独立的 tsc --noEmit 在脚本里跑。

// package.json { "scripts": { "dev": "vite", "build": "tsc --noEmit && vite build" } }

背景:Vite 启动快是因为跳过了类型检查。操作:把检查塞进 build 前的钩子。结果:开发时秒启,提交/构建时仍卡类型。解读:这是"速度"与"安全"的正确分工,而非二选一。

webpack + ts-loader / babel-loader

webpack 两种接法:

// 方案A:ts-loader,由 tsc 转译并可选检查(慢但一体化) module.exports = { module: { rules: [{ test: /\.ts$/, loader: "ts-loader", options: { transpileOnly: true } }] } }; // transpileOnly: true 时只转译不检查,速度接近 babel,检查另跑 tsc // 方案B:babel-loader + @babel/preset-typescript,只剥类型

transpileOnly 是 webpack 下的常见折中:构建快,类型靠外挂 tscfork-ts-checker-webpack-plugin 异步检查,不阻塞打包。

类型声明产物:库开发的 publish 配置

库要给别人用,得产出 .d.ts

// 库的 tsconfig.json { "compilerOptions": { "declaration": true, "declarationMap": true, "outDir": "dist", "emitDeclarationOnly": false } }
npx tsc # 同时产出 .js 与 .d.ts

declarationMap 生成 .d.ts.map,让消费方报错时能跳回你的源码,调试体验好。这张图区分"检查"与"转译"两条轨道:

04-03-fig01-2

实战:CI 里分离检查与构建

# CI 流水线(伪代码) - run: npm run typecheck # tsc --noEmit,失败则阻断 - run: npm run build # 仅转译打包

把检查独立成一步,失败信息清晰,也比"打包到一半才因类型错中断"省时。

自定义类型检查脚本与并发

tsc --noEmit 包成 typecheck 脚本,并和测试、lint 并行,是主流做法。

// package.json scripts { "scripts": { "typecheck": "tsc --noEmit", "lint": "eslint . --ext .ts", "test": "vitest run" // CI 里可并行:npm run typecheck & npm run lint & npm run test } }

背景:类型检查、lint、测试三者互不依赖可并行。操作:拆成独立脚本,CI 用并发跑。结果:总耗时取三者最长而非相加。解读:检查与转译分离后,检查本身也能被进一步并行化,是大型仓库省 CI 时间的关键。

tsc --build 与项目引用的增量加速

多包仓库里,tsc --build-b)只重新构建"改动及其下游",配合 composite 项目引用大幅快于全量。

# 只构建受影响的项目 npx tsc -b packages/core packages/web # 首次会全量,之后只增量重建改动链
// 每个子包 tsconfig 需 composite: true { "compilerOptions": { "composite": true, "incremental": true }, "references": [{ "path": "../core" }] }

背景:改一行 core,全量检查要扫整个大仓。操作:用 tsc -b + 项目引用。结果:只重建 core 与依赖它的 web。解读:这是第九章编译性能优化的"工程层"实现,把"类型边界"变成增量单位,详见 9.1。

watch 模式与编辑器联动

开发时 tsc --watch 或编辑器内置语言服务持续检查,把错误从"提交时才发现"提前到"敲下就标红"。

npx tsc --watch --noEmit # 文件变动即重新检查,不产出

背景:等待 CI 才看到类型错,反馈链太长。操作:本地开 watch 或依赖编辑器(VS Code 等)的 TS 服务。结果:错误在输入时就近提示。解读:类型检查的价值很大程度取决于"反馈距离"——距离越短,契约越容易被遵守,这正是工程化要缩短的环路。

工程取舍:一体化还是分离

小项目 tsc 一把梭最简单。大前端项目用 esbuild/Vite 转译 + 独立 tsc 检查,开发体验和 CI 都更稳。库项目必须 declaration: true 产类型。别让打包器替你做类型检查——它本就不擅长,也容易漏。

实战:tsc 只出类型、打包器出代码(库双产物)

库作者常要把 .d.ts 与运行时代码分开产出,避免消费方被迫跑 tsc。

# 只生成声明文件,不生成 .js npx tsc --emitDeclarationOnly --outDir dist/types # 运行时用 esbuild 单独打包成 .js(速度快) npx esbuild src/index.ts --bundle --format=esm --outfile=dist/index.mjs

输入输出:消费方 import 你的包时,既能拿到 dist/index.mjs 运行,又从 dist/types 拿到类型提示,且不必装你的 devDependencies。两条轨道各司其职:tsc 管契约,esbuild 管产物。

实战:monorepo 里按包缓存检查

多包仓库里每个包独立 tsconfig,检查可并行,不必全仓重查。

// packages/web/tsconfig.json { "compilerOptions": { "composite": true, "incremental": true }, "references": [{ "path": "../core" }] }
# 在仓库根用 build 模式,按依赖图增量 npx tsc -b

背景:单仓多包,改 web 不想重查 core。操作:core 开 composite,web 用 references 指向它。结果:第一次建好缓存后,只查改动包及其下游。解读:这与第九章的"项目引用提速"同源,是大型工程把"检查成本"压到包级别的关键手段。

本节要点回顾

  • 类型检查(tsc)与转译(esbuild/babel)职责正交,可并行
  • Vite 默认不查类型,靠 tsc --noEmit
  • webpack 用 transpileOnly + 外挂检查提速
  • 库需 declaration: true.d.ts

⚠️ 别以为前端构建过了类型就对了。Vite/esbuild 默认剥离类型不检查,构建绿灯不等于类型安全;必须单独跑 tsc --noEmit

💡 把 tsc --noEmit 同时接进 build 脚本和 CI 第一步,开发能秒启,发布前又能卡住类型错误,两全。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U