TypeScript项目的编译速度问题,往往不是在项目初期暴露的,而是在代码量膨胀到十几万行之后突然显现:本地一次冷启动要等一两分钟,CI流水线排队半小时,改一行代码热更新也要卡好几秒。要解决这个问题,光靠升级硬件远远不够,构建工具的配置方式才是关键。webpack和vite在处理TypeScript时走了两条不同的路线,理解它们的差异,才能把优化做到点子上。

一、先搞清楚:TS编译慢的根源在哪里
很多人以为TypeScript编译慢是webpack的问题,其实要拆开看。一个TS文件从源码到可运行代码,通常经历两步:第一步是类型检查,由tsc负责,这是纯CPU密集型的计算,项目越大耗时越长;第二步是转译,也就是把TS语法降级为浏览器能识别的JS,这一步其实很快。真正拖慢速度的,绝大部分开销花在了类型检查上。
默认配置下,webpack的ts-loader会把这两步串在一起执行,每个文件都做完整检查,导致构建时间随文件数量线性增长。而vite的思路是开发阶段完全不参与类型检查,用esbuild做极速转译,把类型检查交给编辑器和单独的命令去处理。这不是偷懒,而是合理的职责分离——你在写代码时,IDE已经实时提示了类型错误,构建时再检查一遍属于重复劳动。
所以优化的第一原则是:把类型检查从构建流程中剥离出去,构建只负责转译,检查放在编辑器实时反馈和CI的独立阶段完成。后面的所有配置,基本都是围绕这个原则展开的。
二、webpack项目的优化配置详解
1. 用transpileOnly跳过类型检查
如果你还在用ts-loader,第一件事就是开启transpileOnly选项。开启后ts-loader只做语法转译,不做类型检查,速度提升立竿见影。配合fork-ts-checker-webpack-plugin,类型检查会放到独立的子进程中异步执行,既不阻塞构建,又不丢失检查能力。
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
// 只做转译,跳过类型检查
transpileOnly: true,
// 开启增量编译缓存,配合 tsconfig 的 incremental
experimentalWatchApi: true,
},
},
],
},
],
},
plugins: [
// 类型检查放到独立进程,异步执行
new ForkTsCheckerWebpackPlugin({
async: true,
}),
],
};2. 直接换用esbuild-loader
更激进的做法是把ts-loader整个替换成esbuild-loader。esbuild是Go语言编写的转译器,速度比Babel和tsc快一到两个数量级。注意esbuild同样不做类型检查,所以fork-ts-checker还是要保留。
const { ESBuildMinifyPlugin } = require('esbuild-loader');
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2018',
},
},
],
},
// 生产环境的代码压缩也可以交给 esbuild
optimization: {
minimizer: [
new ESBuildMinifyPlugin({
target: 'es2018',
css: true,
}),
],
},
};3. 持久化缓存与多进程
webpack 5的文件系统缓存是提速利器,尤其是对CI环境。配置cache.type: 'filesystem'后,二次构建只处理变化过的模块。此外,terser-webpack-plugin支持多进程压缩,thread-loader可以放在耗时的loader之前,把转译工作分摊到多个CPU核心上。
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
// 配置文件变更时让缓存失效
config: [__filename],
},
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{ loader: 'thread-loader', options: { workers: 4 } },
{ loader: 'esbuild-loader' },
],
},
],
},
};需要提醒的是,thread-loader本身有进程通信开销,模块数量少的项目用了反而更慢,建议先构建一次观察耗时分布,再用speed-measure-webpack-plugin量化确认瓶颈在哪,避免盲目堆优化项。
三、vite项目的优化配置详解
1. 理解vite的TS处理机制
vite内部已经用esbuild处理TS转译,默认就不做类型检查,所以本地开发速度通常不需要额外操心。但有两个地方值得注意:一是vite默认忽略了tsconfig.json中的compilerOptions.target,转译目标由vite的build.target控制;二是类型检查需要你在package.json中单独加一条vue-tsc --noEmit或tsc --noEmit的脚本,放到CI里执行。
{
"scripts": {
"dev": "vite",
"build": "vite build",
"typecheck": "tsc --noEmit"
}
}2. 生产构建切换rollup的esbuild插件
vite生产构建默认用rollup配合 terser 压缩,大项目打包时间可能较长。可以把压缩器换成esbuild,整体构建时间能缩短一半以上。
import { defineConfig } from 'vite';
export default defineConfig({
esbuild: {
target: 'es2018',
},
build: {
minify: 'esbuild',
// 关闭分包报告,减少开销
reportCompressedSize: false,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
});3. 依赖预构建与冷启动优化
vite的冷启动耗时主要花在依赖预构建上,也就是把node_modules里的CommonJS包转成ESM并缓存。默认缓存目录在node_modules/.vite下,只要lockfile没变就直接复用。如果发现每次启动都重新预构建,检查optimizeDeps.include是否漏掉了某些运行时动态引入的依赖,把它们显式声明出来可以避免运行时二次发现导致的刷新。
export default defineConfig({
optimizeDeps: {
include: ['lodash-es', 'dayjs'],
exclude: ['@my/local-package'],
},
});四、tsconfig层面的配合优化
构建工具的优化要配合合理的tsconfig才能发挥最大效果。以下几个选项直接影响类型检查速度。
- incremental:开启增量编译,tsc会把上次检查的状态存入
.tsbuildinfo文件,只检查变更文件,CI上配合缓存目录收益巨大。 - skipLibCheck:跳过第三方依赖的
.d.ts检查。依赖包自身的类型问题不该由你的项目买单,这个开关通常能砍掉相当一部分检查时间。 - noEmit:只检查不产出文件,配合构建工具做纯检查命令时必须开启,避免tsc输出与构建工具冲突。
- exclude:把构建产物目录、脚本目录从include中剔除,减少无效检查范围。
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"noEmit": true,
"incremental": true,
"skipLibCheck": true,
"tsBuildInfoFile": "./node_modules/.cache/tsconfig.tsbuildinfo"
},
"include": ["src"],
"exclude": ["node_modules", "dist", "scripts"]
}</ul>
}把tsBuildInfoFile指向node_modules下的缓存目录有个好处:CI系统如果配置了依赖缓存,增量编译的状态文件也会一起被缓存,第二次流水线的类型检查时间可以从几分钟降到几秒。
五、方案选型建议与总结
新项目没有历史包袱,直接上vite是最省心的选择,开发体验和构建速度都有保障,只需要记得把类型检查脚本补进CI。存量webpack项目也不必着急迁移,先用esbuild-loader加fork-ts-checker-webpack-plugin做一轮替换,再开启文件系统缓存,多数项目的构建时间能降到原来的三分之一以下,改造成本远低于整体换框架。
最后强调一点:任何优化都要先量化再动手。用speed-measure-webpack-plugin或者vite的debug日志确认耗时分布,才知道该优化转译、压缩还是类型检查。优化完成后,记得在团队文档里写清楚类型检查被移到了哪个环节,避免新成员误以为构建通过就等于类型安全,这才是工程上真正稳妥的做法。
TypeScript编译速度webpack优化vite配置修改时间:2026-09-12 05:46:36