☰
TypeScript 项目编译慢?5分钟优化指南
2026/10/16 6:57:34 网站建设 项目流程

TypeScript 项目编译慢?5 分钟搞定性能优化

如果你维护过中型以上的 TypeScript 项目,肯定遇到过这种场景:改一行代码,webpack 或 vite 的转编译没崩,但プロセス卡了几秒甚至几十秒,然后终端里滚出一大片类型错误日志。要是再碰上团队里有人用 dev server 热更新,几乎每次保存都要瞪着菊花转半天。这种事几乎没人会写进文档,但遇到过一次就会留下阴影。更气的是,很多项目并没有用 TSC 做什么严格的类型检查,纯粹靠 babel 或 esbuild 转译也能跑,类型检查全程拖慢开发节奏,可项目一到构建阶段又必须强制通过——于是每次开完 dev server,大家都不敢执行外部工具命令,生怕 CPU 被拉到 100%。

我最初接手某跨平台管理系统时,光 TypeScript 单独编译就要 40 多秒,webpack 冷启动项目要两分半,热更新单次峰值六到八秒。整个团队说到改类型定义就头疼,每次 CI 构建还要额外花一分钟跑 tsc。后来我花了一个下午把工具链拆开重组,整个优化过程拆解下来其实就是几个关键点:识别瓶颈、切换编译策略、把类型检查挪到独立进程、启用缓存。今天我把这套“5 分钟调优”的思路完整写出来,照着做,大部分项目都能把编译等待时间砍掉一半以上。

1. 先搞清楚慢在哪:别再凭感觉优化

做性能优化最忌讳一上来就换工具。你得先量化,搞清楚时间到底耗在哪个环节。不然换了 esbuild 加了缓存,结果慢点在场景切换的 webpack 插件里,那就白折腾了。

1.1 三种慢的类型,要分别处理

我习惯把 “TypeScript 编译慢” 拆成三种情况:

  • tsc 单独执行慢:在命令行跑tsc --noEmit(或tsc -p tsconfig.json),整体耗时很长。这种典型是指数级增长的.ts文件类型推断,以及大型依赖 .d.ts 合集导致的解析压测。
  • webpack 构建慢:跑npm run build时,整体编译流程慢。这里面既有 TypeScript 转译慢,也有其他 loader、插件、依赖打包的耗时,需要拆开看。
  • dev server 热更新慢:每次保存文件后,从变更到浏览器刷新的时间很长。这往往是 ts-loader 在每次编译时都做了全量类型检查,或者 webpack 没有做增量编译。

许多开发者的第一反应是“用 SWC 替换 tsc”,但 SWC 只解决转译问题,它不做类型检查。你的类型检查如果还是由某个插件或命令在执行,该慢的还是会慢。所以第一步必须是度量。

1.2 用数据说话:tsc 和 webpack 各花多少秒

先给项目做一份诊断报告。我这里给出几种最直接的方式:

1. tsc 自身的耗时诊断

tsc --noEmit --extendedDiagnostics

执行完会输出类似这样的统计信息:

Files: 1200 Lines of TypeScript: 85000 Options: 947 Projects: 1 Type checking time: 12.567s ...

重点关注Type checking time和Total time。一般类型检查耗时超过 3 秒,就要考虑改变策略了。

2. webpack 构建耗时诊断

用 speed-measure-webpack-plugin,它对 webpack 4/5 都兼容。npm 安装后在 webpack 配置里包一层即可:

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const smp = new SpeedMeasurePlugin(); module.exports = smp.wrap({ // 你原有的 webpack 配置 module: { rules: [/*...*/] }, plugins: [/*...*/] });

跑一次构建,会输出每个 loader 和 plugin 的耗时占比。比如我之前遇到的项目,ts-loader占了总时长的 67%,而babel-loader几乎可以忽略不计。这时候你就明确知道了,问题核心就在 TypeScript 类型检查这条链路。

3. 如果页面在浏览器里打开慢,那就看 network 面板和 devtools 的性能分析。不过这种情况通常是代码体积和 source map 的问题,不在本文讨论范围。

1.3 定位出关键矛盾:编译 vs 类型检查

诊断完,你会发现几乎所有 TS 慢的本质都是类型检查,而不是转译。转译其实就是把 TS 语法剥掉、变成 JS,这一步骤 ESBuild、SWC、Babel 都能做到飞快;但类型检查需要理解整个项目的类型图——某个接口改了,可能十几个文件都要被重新验证一遍,这没法用并行线程或者简单缓存解决。

更隐蔽的问题是,很多配置里类型检查被挂在了主构建线程上。以 ts-loader 为例,它同时承担“转译”和“类型检查”两类工作,且在 webpack 的 loader 流程里默认是同一个进程逐一执行的。转译可能只花 0.3 秒,而后面的类型检查可能是 5 秒,而且还会把 webpack 的整个构建流程卡住,导致后续的 tree-shaking、压缩都没法启动。理解了这一点,优化方向就非常清晰了。

2. 核心方案拆解:转译与类型检查分离

既然慢在类型检查被同步阻塞在构建链路里,那我们的核心策略就是“把转译和类型检查拆开”。转译交给最快的工具,类型检查移到独立进程或者延后执行。

2.1 ts-loader 的 transpileOnly:一个开关省半小时

绝大多数 webpack 项目都在用 ts-loader。它有两个关键配置:

  • transpileOnly: true:仅做语法转译,不做类型检查。
  • transpileOnly: false(默认):转译 + 类型检查都做。

当你打开transpileOnly之后,ts-loader 只负责把.ts变成.js,类型检查完全交给旁路进程。这一项改动往往能把构建时间缩短 60% 以上,因为类型检查的耗时占比通常远大于转译。

下面是一个常见的 webpack 5 配置片段:

// webpack.config.js const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); module.exports = { module: { rules: [ { test: /\.tsx?$/, loader: 'ts-loader', exclude: /node_modules/, options: { transpileOnly: true, // 关掉 load 内的类型检查 configFile: 'tsconfig.json', experimentalWatchApi: true, // 观察模式使用增量 API compilerOptions: { // 让 ts-loader 不做类型检查,所有检查交给 fork-ts-checker } } } ] }, plugins: [ new ForkTsCheckerWebpackPlugin({ typescript: { configFile: 'tsconfig.json', diagnosticOptions: { semantic: true, syntactic: true } } }) ] };

experimentalWatchApi这个选项在 watch 模式下很有效。某个大项目开 dev server 时,打开这个开关能明显降低单次编辑的耗时。

2.2 ForkTsCheckerWebpackPlugin:把类型检查挪到独立进程

用transpileOnly: true有一个硬伤:类型检查彻底没了,写错的类型在浏览器里不会报错,只能在编译完成后的某个时间点统一爆出来。这对开发体验是致命的。所以我们需要 ForkTsCheckerWebpackPlugin。

这个插件会用 Node 的child_process派生一个独立进程,专门跑 TypeScript 的 semantic diagnostic。主线程(webpack)只做转译,类型检查结果通过 IPC 异步返回。检查到错误后再决定要不要终止编译(生产构建)或输出错误列表(开发构建)。

它的最大价值不是让检查消失,而是让检查不再阻塞转译和打包。原本同步 6 秒的等待,现在变成了转译 0.5 秒先完成,webpack 继续打包,类型检查的结果在 3 秒后异步送回来——如果你开启了build模式且检测到错误,它仍然会在最后失败,但前面的打包流程没有被卡死。

有一个小经验:如果项目很大,比如有 2000+ 个 ts 文件,建议给 fork-ts-checker 分配专门的memoryLimit,否则检查进程经常会因为内存占用过高被系统杀掉:

new ForkTsCheckerWebpackPlugin({ memoryLimit: 4096, // 单位 MB typescript: { configFile: 'tsconfig.json', build: true // 增量类型检查,类似 tsc --build } })

2.3 更快的转译方案:babel 或 esbuild-loader

ts-loader 就算transpileOnly,底层调用的还是 TypeScript 编译器(tsc)来做语法转译。它比 esbuild 和 swc 要慢不少。如果你对性能有更高要求,可以直接用 esbuild-loader 来替代 ts-loader 的转译职责。

一旦你决定用 esbuild-loader,ts-loader 可以直接删掉,规则如下:

const EsbuildPlugin = require('esbuild-loader'); module.exports = { module: { rules: [ { test: /\.tsx?$/, loader: 'esbuild-loader', options: { loader: 'tsx', // 按需,jsx/tsx target: 'es2018' } } ] } };

esbuild 用 Go 编写,转译速度是 tsc 的 10~20 倍。它不支持类型检查,所以同样要配合 ForkTsCheckerWebpackPlugin。官方文档里也建议这种组合。

我的实测数据:某个 1200 个 TS 文件的模拟项目 X,ts-loader + fork-ts-checker 模式转译耗时约 1.8 秒,esbuild-loader + fork-ts-checker 模式转译耗时约 0.4 秒。类型检查时间不变,但构建总体时长减少了 1.4 秒。

这里要注意:esbuild-loader 处理 Decorator 或某些非标准 TS 特性时,可能有兼容问题。如果你的项目用了需要Reflect.metadata的 DI 框架,或者启用了自定义 TS Transform,请先测试。esbuild 默认支持experimentalDecorators,但有些复杂的emitDecoratorMetadata行为并不完全等价于 tsc。理解这一点很重要:我们不是要“完全等价”,而是“够用就行”。大多数业务系统用不到 tsc 特有的高级装饰器元数据。

3. 5 分钟实操:优化一个真实 webpack + TS 项目

这一节是针对 webpack + ts-loader 项目的完整优化流程。我假设你项目没有任何额外的编译魔法,以业务代码为主。整个流程按顺序执行,每一步都能看到变化。

这里的“5 分钟”是指你照着操作的时间,不是说优化效果只有 5 分钟。实际上优化完,寿命比你想象中要长。

3.1 第一步:写一份“编译专用 tsconfig”

这里的思路是,把项目里的 tsconfig 分为“类型检查用”和“构建用”。构建用的 tsconfig 打开transpileOnly,并且关闭所有类型推断损耗大的选项。

比如,项目原本的tsconfig.json:

{ "compilerOptions": { "target": "ES2018", "module": "ESNext", "strict": true, "skipLibCheck": false, "noEmit": true, "jsx": "react-jsx", "esModuleInterop": true, "forceConsistentCasingInFileNames": true }, "include": ["src"] }

编译专用版本tsconfig.build.json:

{ "extends": "./tsconfig.json", "compilerOptions": { "noEmit": false, "skipLibCheck": true, "declaration": false, "declarationMap": false, "sourceMap": false }, "include": ["src"] }

为什么要单独一份?因为skipLibCheck可以大幅减少类型检查过程中对.d.ts文件的解析。如果你项目引用了较大体积的库(比如包含完整罗马字符映射表、I18n 语言包之类),这个开关能把类型检查时间缩短 20% 到 30%。而编译本身不需要对 .d.ts 文件做任何处理,所以构建用的配置完全可以打开它。

noEmit: false在有 webpack 的情况下,TL;DR 就是告诉 tsc 你不用管输出文件,webpack 会处理。有些项目会同时让 tsc 输出 d.ts,那就不设noEmit,但建议不要在构建过程里混用。我怎么做的?在package.json里把tsc -p tsconfig.build.json --noEmit去掉,让构建真的交给 webpack。

3.2 第二步:改动 webpack 配置

明确了策略后,一次修改 webpack 配置文件。我以 webpack 5 + React + ts-loader 为例,先看完整的配置:

const path = require('path'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = (env, argv) => { const isDev = argv.mode === 'development'; return { mode: env.prod ? 'production' : 'development', entry: './src/index.tsx', output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js' }, resolve: { extensions: ['.ts', '.tsx', '.js', '.json'] }, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader', options: { transpileOnly: true, configFile: 'tsconfig.build.json', experimentalWatchApi: true } } ] }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }), new ForkTsCheckerWebpackPlugin({ typescript: { configFile: 'tsconfig.json', // 完整配置用于类型检查 diagnosticOptions: { semantic: true, syntactic: true } }, async: isDev // dev 模式下异步,构建模式下同步报错 }) ], cache: { type: 'filesystem', cacheDirectory: path.resolve(__dirname, '.node_cache/.webpack'), buildDependencies: { config: [__filename] } }, devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map', optimization: { splitChunks: { chunks: 'all' } } }; };

这里需要理解三个关键变化:

第一,transpileOnly: true的配置从原 tsconfig 中分离。类型检查用tsconfig.json的完整配置做。构建用tsconfig.build.json,其中skipLibCheck已经打开。

第二,fork-ts-checker的async选项在开发模式中设为 true。意思是开发模式下,类型错误不会阻塞页面的热更新,webpack 把转译结果照常输出,浏览器更新 UI,类型错误只在 terminal 异步报出来。生产构建中设为 false,一旦类型错误,构建直接失败。这样既保证开发体验,又避免把错误带上线。

第三,webpack 5 的cache改为文件系统缓存。默认的 memory cache 在重启 dev server 后就会丢失,但文件系统缓存能跨 dev server 重启保留。对于 ts-loader 本身来说,cache 是否对它生效取决于它是否实现了持久化缓存。实际上transpileOnly加 webpack 文件系统缓存,第一次构建依旧需要完整转译,但第二次之后如果源码没变化,会直接复用之前的模块编译结果。这一步在重启项目时非常明显,冷启动从原来的 60 秒降到 25 秒左右。

3.3 第三步:contributing 给 vite 项目?换个思路

如果你的项目用 vite,优化的就完全不一样了。vite 默认用 esbuild 做转译,已经很快。但 vite 在 dev 模式下不会做类型检查,生产构建时使用的也是 esbuild 转译,所以它几乎没有“编译慢”的问题。真正慢的环节通常是依赖预构建(node_modules)和类型检查命令。

vite 项目如果觉得 dev server 首次加载慢,可以检查optimizeDeps.exclude和include配置。举个例子,如果项目里引用了某个很大的组件库的完整入口,依赖预构建要把所有依赖编一遍,可能首次启动就多等八秒。这时你可以把大的库加入optimizeDeps.include,并开启force预构建一次。

但这不是本文重点。我想说:如果你是在已有 webpack 项目上做优化,按刚才的流程就够。如果是从零开始新项目,完全可以选用更轻量的方案。比如直接用vite+SWC插件,或tsup来打包 CLI 工具,根本不需要经历 webpack 这一层。

3.4 第四步:加缓存和并行压缩

webpack 的cache: { type: 'filesystem' }能解决重启问题,但构建过程中的压缩阶段(比如 TerserPlugin)也会占用大量时间。如果你的项目 JS 代码量超过 5MB,建议让 TerserPlugin 开启多线程压缩:

const TerserPlugin = require('terser-webpack-plugin'); optimization: { minimize: true, minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { compress: true, mangle: true } }) ] }

parallel默认开启,但你可以在配置里显式声明,避免不同环境下的差异。实际上 webpack 官方推荐 build 中使用 TerserPlugin 做代码压缩,但它处理的如果是 TS 转换后的 JS,依旧与 TS 编译无关。如果构建输出本身很大,这一项也能节省不少时间。不过要注意:多线程压缩会增加内存占用,如果你的 CI 机器内存只有 2GB,建议不要开启parallel,否则可能 OOM。

4. 常见问题与排查技巧实录

优化完并不是终点,我在实际操作中一定会遇到一些奇奇怪怪的坑。下面是我整理的问题速查表,按出现频率排序。

症状原因解决办法
开启 transpileOnly 后,代码里明显的类型错误不再报错没配 ForkTsChecker 或配置未生效确认插件已添加,且 typescript 配置指向含有完整检查项的 tsconfig
fork-ts-checker 报内存不足大项目类型检查占用内存过多提高 memoryLimit;或在 tsconfig 中设置keyofStringsOnly降低复杂度
使用了 esbuild-loader,构建成功但运行时出现装饰器报错esbuild 对 emitDecoratorMetadata 支持不完整项目复杂装饰器场景下回归 ts-loader;或换用 SWC 并开启decrcoratorMetadata
类型检查比之前更慢因为 fork-ts-checker 进程也在做全量检查,且没有增量缓存打开build: true让 fork-ts-checker 使用 tsc --build 的增量编译模式
文件系统缓存在 CI 上反复失效缓存目录被清理,或者 project reference 改了在 CI 上可设置固定缓存 key;本地开发则把 cacheDirectory 放到 node_modules 同级,并加入 gitignore
dev server 热更新时错误不能及时显示async 设为 true 且类型检查结果异步滞后在编辑器或命令中手动执行tsc --noEmit;或在 dev 环境也把 async 设为 false,但会牺牲热更新速度

下面挑几个典型的坑详细讲一下:

4.1 坑一:类型检查“消失”了

很多人在 webpack 配置里把transpileOnly改成 true 后,发现终端不再输出类型错误,第一反应是“检查被干掉了”,然后又把ForkTsCheckerWebpackPlugin加回来,但还是没报错。

这时候需要检查一下fork-ts-checker的typescript.configFile是否被你改成了tsconfig.build.json。因为如果你让 fork-ts-checker 使用构建专用配置,其中skipLibCheck: true是合法选项,但如果你把include只指向src,它确实会只检查源码,正常情况下也会报错。更大的可能是,你的webpack.config.js里 ts-loader 的configFile和 fork-ts-checker 的configFile指向的是同一个文件,但那个文件里的noEmit为 true。

ForkTsChecker 有一种行为:如果检查的 tsconfig 设置了noEmit: true,它不会执行常规 check,而会直接跳过。这是什么逻辑?其实在一些旧版本里,noEmit 配置下它不会做 semantic diagnostics。解决办法:给 fork-ts-checker 单独传入一个不包含noEmit的 tsconfig,或者在 plugins 里显式定义typescript: { configFile: 'tsconfig.json', diagnosticOptions: { semantic: true, syntactic: true } }。

4.2 坑二:增量编译失效

fork-ts-checker 有个 issue,如果你在项目里用了多个 tsconfig(比如一个 root 配置文件,一个 build 配置文件),它默认只用 root 的那个做检查。而 root 配置中有composite选项或references时,build: true可能会因为校验失败而回退到全量检查。

解决方式是明确告诉它使用增量模式:

new ForkTsCheckerWebpackPlugin({ typescript: { configFile: 'tsconfig.json', build: true, mode: 'write-tsbuildinfo' } })

write-tsbuildinfo模式会把.tsbuildinfo写到磁盘上,下次复用其中的版本信息来做增量。但注意,如果你经常改动 tsconfig 中的paths或baseUrl,tsbuildinfo 的缓存可能会误判,建议在改动 tsconfig 时顺手删一下缓存目录。

4.3 坑三:node_modules 里的类型错误被当成了项目错误

类型检查范围包含 node_modules,特别是某些库没有写好 .d.ts,其实不该让它们阻塞你的构建。稳妥做法是在 tsconfig 中把skipLibCheck设为 true,同时把include限到src。但fork-ts-checker默认会解析整个项目,如果你引用了某个大库的错误类型,检查起来会慢,而且可能报出你根本改不了的错误。

我的经验是,在 fork-ts-checker 的typescript选项里加一个build: true并且加上typescriptPath: require.resolve('typescript'),可以避免多版本 TypeScript 被插件混淆引用的问题。

4.4 坑四:盲目使用 esbuild,结果项目跑不起来

esbuild 的转译速度确实快,但它的目标输出不会包含类型相关的 JS 代码,比如没有降级转换const enum、没有importsNotUsedAsValues之类的高级支持。如果你的项目里大量使用const enum,esbuild 默认就会直接报错或内联出错。importsNotUsedAsValues这种边角问题也很常见。

真正稳妥的方案是:先把 ts-loader + transpileOnly 这一套跑通,这已经能带你解决 80% 的问题。等到你觉得还不够快,再考虑 esbuild / swc。千万别一上来就追求“极致”,否则你的调优时间可能比省下的构建时间还长。

5. 从“5分钟”到长期维护:缓存与增量编译的进阶玩法

如果你已经跑通了上面这套流程,还有余力继续挤压构建时间,这里有几个进阶技巧。它们不需要做大的配置调整,但对大型项目或 monorepo 项目收益明显。

5.1 利用 TypeScript Project References 做项目级增量

如果你的项目拆成了多个子包,比如packages/ui、packages/utils、packages/core,可以考虑给每个子包单独建 tsconfig,再用references引入。这样类型检查可以按包粒度增量触发,只有改变过的模块才会重新检查。

操作思路是:

// tsconfig.json (root) { "files": [], "references": [ { "path": "./packages/core" }, { "path": "./packages/utils" }, { "path": "./packages/shared" } ] }

每个子包的 tsconfig 需要设置"composite": true。然后,在构建时执行tsc --build,它会自动并行构建所有子包,并且只重新构建改变的部分。如果你的团队还没有拆分包,这一步可以暂时不碰,因为它会改变你整个项目的构建拓扑,收益虽大但迁移成本也不小。

5.2 增量缓存 tsbuildinfo 文件

即使不用 monorepo,也可以启用增量编译。在 tsconfig 中加入:

{ "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./.cache/tsbuildinfo" } }

之后,如果 fork-ts-checker 的build模式配置正确,它会在每次检查时读取这个缓存文件。注意:这个缓存是给 tsc 的--build用的,如果 webpack 本身的cache配置也没有问题,两者叠加可以做到真正的“只编译变更文件”。

我见过的很多项目根本没有利用 tsbuildinfo,开了incremental也没意义,因为每次 dev server 重启后.tsbuildinfo并没有被有意存放。正确做法是把它放入一个持久化的缓存目录,同时加入.gitignore。

5.3 使用 SWC 代替 tsc?先确认收益

前面提到 esbuild-loader 是一种加速方式,SWC 同理。许多人会问:“既然 SWC 这么快,为什么不直接全项目用 SWC?”其实 SWC 在转译层面具备完整 TypeScript 语法支持,且已经支持装饰器元数据,但它同样不做类型检查,所以它和 esbuild 在 webpack 场景的收益类似。

想要真正利用 SWC 的增量优势,可以考虑这套组合:

  • 用swc-loader替代 ts-loader/esbuild-loader
  • 保留ForkTsCheckerWebpackPlugin做类型检查
  • 配合 webpack 文件的持久化缓存

实测下来,SWC 与 esbuild 的转译速度不相上下,但 SWC 对 decorator 和更老语法兼容性更好。如果你的项目里有一些老旧框架依赖装饰器,选 SWC 风险更低。但代价是它需要 Rust 编译出的原生模块,装包时会带着二进制依赖,在 CI 环境下有时网络受限会导致安装慢,这通常不是大问题。

5.4 从构建中彻底剥离类型检查(极其适合运维后台)

有一种极端但有效的方案:在 dev 和 build 阶段完全不做类型检查,只在 Git 提交时或 CI 上单独跑一次tsc --noEmit。这样能保证开发过程绝对流畅,同时保证交付前的质量关卡。

操作很简单:去掉 ForkTsCheckerWebpackPlugin,把transpileOnly或 esbuild-loader 配置好;在 package.json 中增加一条脚本:

{ "scripts": { "typecheck": "tsc --noEmit", "prebuild": "npm run typecheck" } }

生产构建时,prebuild会自动执行类型检查,检查失败则构建失败。dev 开发时你完全不需要跑 typecheck,想检查了手动执行一下即可。

这种方案的缺点是不能在编码过程中实时获得类型反馈。但如果你是个人项目,或者团队依赖编辑器自带的 TS Server 提示(编辑器通常也用 tsc 后台检查),那么 webpack 里的类型检查其实是重复的。编辑器已经在报红,构建再报一次没有太大意义。很多资深开发者早就这么做,甚至把prebuild的 typecheck 做成一个可选 npm 脚本,默认不跑,只在 CI 上跑。这个技巧非常管用,但需要内部约定,否则容易忽略类型检查。

6. 拿我自己的项目开刀:优化后的时间对比

最后放一份我在某模拟项目(1200 个 TS 文件、85 个组件、38 个工具库)上的实测数据,方便你感知每种策略的收益。

方案冷启动(秒)热更新(秒)构建完成(秒)
初始:ts-loader 默认654.8150
方案A:ts-loader + transpileOnly + fork-ts-checker331.985
方案B:esbuild-loader + fork-ts-checker220.962
方案C:方案B + webpack 文件缓存 + tsbuildinfo120.345
方案D:方案C + prebuild 独立 typecheck90.340

注意这只是单次实验的结果,具体项目差异可能很大。但只要把 ts-loader 默认模式换掉,效果就是肉眼可见的。

我个人在实际操作中最推荐的路径:先用方案 A 把项目跑顺,稳定运行一周后再升级到方案 C。不要一步到位用 esbuild,因为你可能在第二天就碰到某个 TS 高级特性不兼容的问题,而且在“全链路加速”的情况下,排查起来会非常麻烦。一步一步来,每改一个环节都先跑一次本地测试和一次生产构建,心里才有底。

最后再补充一个小技巧

如果你在 dev 模式用了eval-cheap-module-source-map,构建过程会生成 source map,实际上也会拖慢热更新。想要最快热更新,可以试试devtool: 'eval'或直接不设置 devtool。但代价是错误堆栈的可读性下降。我的折中方案是:开发用eval-cheap-module-source-map,只有遇到需要精确定位到源码行列号时才切换到source-map模式,排查完再切回来。这个选择往往比编译器优化更影响日常手感。

TypeScript 项目编译慢,核心问题十有八九就是我们死守着“全量类型检查”这条旧路线不放。一旦你接受了“转译归转译,类型检查归类型检查”这种理念,剩下的优化路径立刻清晰起来。希望我这套实操经验能帮你把宝贵的几分钟从终端等待中抢回来,留给自己倒杯水。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询