公司动态
构建工具选型别只看功能清单
构建工具选型别只看功能清单构建工具影响本地启动、增量更新、测试、生产打包和 CI 缓存。官方功能清单能说明工具支持什么却无法回答仓库里的自定义 Loader、动态导入、CSS 处理和浏览器目标能否原样迁移。选型前先测量现有链路再用同一个应用比较候选方案结论会比通用 Benchmark 更接近真实成本。1. 迁移成本主要藏在三处1.1 自定义生态与插件链Plugin Chain移植断层国际化提取、SVG 转换、资源重写和代码生成等自定义插件可能依赖 Webpack 的特定 Hook。候选工具即使提供相似接口也要逐个验证输入、输出和执行阶段。盘点时记录插件的业务目的不只记录包名有些插件已经没有消费者可以先删除有些则适合移到独立脚本或编译器插件中。1.2 Monorepo 体系下的隐式依赖与 Tree-shaking 破坏Monorepo 常包含 Alias、工作区软链接、条件导出和未声明的隐式依赖。解析规则变化后可能出现同一库被打入多份、CSS 被误删或开发环境能解析而 CI 失败。迁移前应先修正未声明依赖再比较模块图和最终产物不能只看首页是否打开。1.3 CI 持久化缓存Persistent Cache失灵本地热更新快不代表 CI 冷构建或远程缓存同样有效。缓存键要覆盖源码、锁文件、工具版本、环境和构建参数CI Runner 也要实际保存和恢复对应目录。比较时分开记录冷构建、热更新、无改动重建和常见改动后的增量构建。2. 先测量现有构建收集构建阶段耗时、模块数量、转换次数、压缩时间、峰值内存和缓存命中。再选几种代表性改动例如只改业务模块、改公共依赖、改样式 Token 和升级锁文件观察哪些阶段被重新执行。若瓶颈来自一个自定义插件或错误的缓存键直接修复可能比迁移整条链路更合算。3. Webpack 构建性能诊断 Plugin 实现下面的 Webpack Plugin 记录模块从buildModule到succeedModule的墙钟时间可用于发现候选慢模块。import { Compiler, Compilation } from webpack; export interface ModuleBuildTimeInfo { moduleName: string; durationMs: number; } export class WebpackBuildProfilerPlugin { private moduleTimes: Mapstring, number new Map(); private slowModules: ModuleBuildTimeInfo[] []; private thresholdMs: number; constructor(options: { thresholdMs?: number } {}) { this.thresholdMs options.thresholdMs || 100; // 默认追踪耗时 100ms 的模块 } public apply(compiler: Compiler) { const pluginName WebpackBuildProfilerPlugin; // 1. 监听 Module 构建开始 compiler.hooks.compilation.tap(pluginName, (compilation: Compilation) { compilation.hooks.buildModule.tap(pluginName, (module: any) { const modulePath module.userRequest || module.rawRequest || unknown-module; this.moduleTimes.set(modulePath, Date.now()); }); // 2. 监听 Module 构建结束计算耗时 compilation.hooks.succeedModule.tap(pluginName, (module: any) { const modulePath module.userRequest || module.rawRequest || unknown-module; const startTime this.moduleTimes.get(modulePath); if (startTime) { const duration Date.now() - startTime; if (duration this.thresholdMs) { this.slowModules.push({ moduleName: modulePath, durationMs: duration, }); } this.moduleTimes.delete(modulePath); } }); }); // 3. 构建完成输出诊断报告 compiler.hooks.done.tap(pluginName, (stats) { const totalTime stats.endTime - stats.startTime; console.log(\n Webpack 构建耗时诊断报告 ); console.log(⏱️ 构建总耗时: ${(totalTime / 1000).toFixed(2)}s); console.log(⚠️ 发现 ${this.slowModules.length} 个编译耗时超过 ${this.thresholdMs}ms 的慢模块:\n); // 按耗时降序排列前 10 个慢模块 const topSlowModules this.slowModules .sort((a, b) b.durationMs - a.durationMs) .slice(0, 10); topSlowModules.forEach((item, index) { console.log( ${index 1}. [${item.durationMs}ms] ${item.moduleName}); }); console.log(\n); }); } } module.exports WebpackBuildProfilerPlugin;4. 示例诊断插件的限制该插件用模块路径作为 Map Key。如果同一路径在并行构建或不同编译上下文中重复出现开始时间可能互相覆盖。模块构建失败时也没有在failedModule清理记录。slowModules在 Watch 模式的多轮构建之间没有重置报告会混入之前的数据。生产诊断应使用稳定的模块实例标识并在每次 compilation 开始时初始化状态。Date.now()适合粗略观察不适合做精细阶段分析阈值也应根据项目基线配置。模块墙钟时间包含调度等待不一定等于某个 Loader 的纯执行时间。发现慢模块后还需要结合 Stats、CPU Profile 或 Loader 自身计时继续定位。诊断工具本身也会增加开销因此不必在每次普通构建都开启。候选工具的比较表应由同一仓库、同一机器或 Runner、同一缓存条件生成。除了时间还要核对 JS/CSS 行为、Source Map、Chunk、资源路径、浏览器兼容和部署结果。没有这些实测不写看似精确的兼容率和提升比例。5. 打包工具选型与工程治理 Checklist做决定前完成以下评估定位瓶颈确认时间花在转换、压缩、模块解析、插件还是缓存恢复保留冷构建与增量基线。评估原地优化只对已确认的瓶颈尝试缓存、并行或替换转换器并用测试证明产物没有变化。盘点插件与语法为每个自定义 Loader、Plugin、动态导入和 CSS 处理准备迁移用例。比较产物核对模块图、公共依赖、Chunk、Source Map、CSS 顺序、资源路径和目标浏览器不只比较压缩体积。规划迁移边界可以先迁移独立应用或测试链路。开发与生产采用双轨时要评估两套解析规则带来的差异与维护成本不能默认双轨更稳。构建工具选型的结果应是一份带证据的取舍当前瓶颈是什么候选方案解决了什么哪些插件需要重写出现问题怎样退回。功能清单用于筛选候选真实项目的构建与发布才负责最后决定。