范文杰从接手慢工程的经历切入:面对自研框架或体量较大的旧项目,仅仅知道“构建慢”,不足以决定改什么。他把优化分成连续的三步——理解工作流程,找到时间花在哪里,再采用相应手段。
这段分享来自深圳“前端的挑战与机遇”录播第 5 段,所属稿件发布于 2022 年 5 月 9 日。当时讨论的是 webpack 4/5 及相关工具,演示数字不构成通用性能保证。
核心流程:初始化、构建与生成
初始化阶段读取和合并配置,建立 Compiler 并安装插件,随后进入运行。分享没有把这里作为主要优化目标,因为在他观察的项目中,更明显的耗时出现在后面的模块处理和生成阶段。
构建阶段先定位文件、读取内容、运行 loader,再解析代码中的依赖,继续处理依赖模块。模块越多,文件访问、转换与解析工作就越多;某些转换过程还会重复经过字符串和语法树表示。最终建立的模块及其关系,为后续打包提供基础。原片 02:21—07:13
生成阶段进一步组织 chunk,执行优化、压缩等操作,再输出文件。代码压缩和分包都可能需要明显时间。范文杰强调,要先知道工具在完成什么任务,才能理解某个配置为什么会耗时,而不是看到一次等待就认为工具在做无用功。原片 07:14—09:10
Stats 帮助回答构建里到底有什么
第一条分析路线是导出构建统计信息,按需要启用 profiling。范文杰重点介绍了 assets、chunks 和 modules:它们分别帮助查看产物文件、组织后的代码块和具体模块,以及这些对象之间的关联。
模块的 profiling 信息还能用于观察解析定位和构建等耗时。把这些信息联系起来,可以追查一个体积大或构建重的产物由哪些内容组成。不过,关系图和计时口径需要分别理解:共享模块、并行执行和重叠阶段存在时,简单相加不等于实际经过的总时间。原片 09:39—14:57
不同分析工具,回答不同问题
分享逐一展示了几类工具,并没有推荐只安装一种就完成全部诊断。
- webpack analyse 一类工具呈现模块与依赖关系,信息较多,但复杂工程里的阅读和使用体验也可能有负担。
- Statoscope帮助按入口和模块关系检查内容,查看是否有不必要或重复打包,并比较两次统计结果的变化。
- webpack-bundle-analyzer通过可视化面积展示模块与包体占比,便于发现较大的依赖,但体积大并不直接等于编译慢。
- speed-measure-webpack-plugin更关注插件与 loader 规则的运行耗时,可以补上另一类线索;它不负责解释全部产物体积分布。
- webpack-dashboard改善开发过程中的信息呈现,片中也提到其数据进一步利用上的局限。
这些评价对应当时版本和范文杰的使用经验。核心方法是根据要回答的问题组合工具:产物包含什么、哪些转换耗时、改动前后发生什么变化,不应混成同一个指标。原片 14:58—24:00
Stats 之外,还能在 hooks 上记录阶段时间
Stats 未必直接提供想知道的所有内部阶段信息。例如某一步优化到底消耗多久,可能需要沿生命周期再定位。范文杰提出在合适的开始、结束节点记录时间,通过 hooks 观察特定工作段。
这需要理解钩子的真实触发范围,不能仅凭名字就认定两点之间覆盖某个完整阶段。他展示了自己收集模块构建、生成与优化时间的工具原型,但明确说当时尚未完成,现场也没有提供一个已成熟可用的发布版本。原片 24:02—27:56
缓存:复用的是中间信息,不只是最终文件
优化措施首先是缓存。范文杰以 webpack 5 文件系统缓存为例,解释首次构建保存部分信息,后续符合复用条件时恢复它们,从而跳过重复工作。
他用 Three.js 做过个人测试,展示首次与后续构建之间的明显差异。这个例子说明缓存命中可能带来的收益,但不能把热缓存结果与另一工具的冷启动混在一起,也不能把示例秒数当成任意工程都能达到的成绩。原片 27:56—30:45
随后他比较当时 webpack 4 生态中的做法:loader 结果缓存、保存更多构建状态的插件,以及 Babel 等工具各自的缓存。不同层级复用的范围不同,复杂度和兼容要求也不同。这里记录的是历史方案,旧插件是否仍维护、与所用版本是否兼容,需要另行判断,不能原样拼成今天的配置清单。原片 30:46—33:45
并行:先确定哪部分能够独立执行
另一类措施是多进程处理。片中分别讨论 loader 转换、多个独立 webpack 配置实例,以及压缩任务的并行。它们解决的是不同问题:多个配置各起进程,并不会自动让单个配置内部的每一步都变快。
范文杰也提醒 loader 兼容性。例如某些需要特定编译上下文或发出资源的操作,不能直接放到工作进程中。并行并非把所有规则套一层就完成,需要对照所用工具能力和具体转换任务。原片 33:48—36:08
少处理不需要的内容,把检查放到合适环节
通过 include、exclude 等规则缩小转换范围,可以减少重复处理;但不能简单认为所有第三方代码都不需要转换,仍要看目标环境。noParse 也依赖明确前提:确认相关内容无需继续进行相应依赖解析,不能为省时间盲目跳过。
TypeScript 可以把转译与类型检查拆开,通过独立过程继续检查,而不是为了快就不再检查。lint 同样可以在编辑器、提交或 CI 等环节安排,不必让每次本地构建承担全部检查工作;团队仍需有实际能执行的质量关口。原片 36:08—38:00
source map 的生成策略和图片处理也有成本。范文杰建议按开发与生产需要选择适当方式,避免每轮构建重复做本可提前完成的资源处理。他还提醒关注旧依赖、运行环境与工具版本之间的限制。需要优化的是整体路径,不是只盯着一个插件。原片 38:01—38:59
延迟编译与项目拆分:减少一开始必须完成的工作
最后,分享回到构建工具的不同策略。除了算法和内部数据结构改进,webpack 5 当时的 Lazy Compilation 实验能力,尝试把部分编译延后到真正需要时;项目拆分与 Module Federation 等能力,则可以帮助开发者集中处理正在修改的部分。
范文杰又以 Vite 的开发启动方式作对照:不在一开始完成整个应用的打包,按请求处理模块,可以改变启动等待的组成。这是在解释开发阶段为什么可能更快,不是说所有生产构建也采用同一流程,或某个工具在所有场景都优胜。
整场分享最终落在同一种排查习惯:分清冷启动与复用、体积与耗时、当前要做的工作与可以延后的工作,再衡量修改是否解决了真实瓶颈。该分段以附录内容结束,没有收录现场问答。原片 39:00—42:26
