前端工程化 - 聊聊 Webpack v3 到 Webpack v5 的核心架构变迁

2,107 阅读8分钟

前言

随着前端工程化的持续发展, Webpack 作为一个核心框架, 在整个打包构建中占据了主导地位, 但同时 Webpack 从最初的相对简单的配置也变得日益复杂和庞大, 从我的经历来讲, 到目前的 v5 版本, 我所理解的 Webpack 架构经历了 3 次比较大的变迁, 由于职业病的关系, 本着对于架构的关注和热爱, 我将这些架构变迁整理成文, 希望和各位一起聊聊其中一些有趣的设计, 和变迁的细节, 遂有此文

正文

v1 - v3 单图架构

Webpack 诞生之初的口号是一切皆可打包, 正如官网首页上的那张图所显示的那样, 可以说从 Webpack 开始, 前端工程才有真正意义上的 bundle, 过去的 grant, gulp 解决了流程问题, 但始终没有解决如何将资源打成一个包, 这个最朴素的问题, Webpack 补齐了前端工程化的这块短板, 也同时将前端工程化推向了一个崭新的领域.

这个时期差不多是 v1 - v3 这三个版本, 我记忆中, 从 v1 - v3 Webpack 的升级没有太过重大的变更, 都还是比较平滑, 这种平滑的背后是核心架构在设计上的稳定, 先让我们来看看, 这一时期 Webpack 带来的几个主要概念

  • entry
  • output
  • Module
  • chunk

在早期的核心架构中, 将这 4个概念融合成一张图差不多是这样

解释下这张图的意思, Webpack 通过 entry 指定的入口文件进行分析, 这个分析过程么, 一言难尽, 反正你只要知道, 对于 Webpack 来讲 require, import 等等各种模块导入语法都要支持, 这里 Webpack 主要用的是

acorn.js 这是个用 JavaScript 写的非常快的高性能解释器, 用于将 JavaScript 代码转成抽象语法树, 你说为啥不用 babel ? 首先 babel 主要针对的还是各种版本的 JavaScript 的语法转换, 但是 babel 其实不解决模块解析这个问题, 所以对于 Webpack 来说用 babel 太重了. 而且也不能彻底解决问题, 何况 v1 的时候不也没 babel 啥事么.

让我们回到上面的图, 这里重点要说下 chunk module 以及连起来的那几条线, 在 Webpack 的核心架构里, 最核心的内容就是 chunk, 可以说 chunk 这个概念, 以及围绕 chunk 的一系列设计是整个 Webpack 的核心的核心, 基础的基础, 没有 chunk Webpack 可以说就等于没有了灵魂. 然后是 module, 在 Webpack 的设计里 module 是文件的抽象映射, 配合 loader 组成了 bundle 的实体内容, 不像 chunk 这么抽象, module 非常具体, 一个文件 → 一个module.

上面讲了这么多, 其实还没点到题, 标题里我写了一个 单图架构 这个单图指的就是 ChunkGraph, 一个有 chunk 构建的图结构, chunk 内包裹了 module, 这个基于 ChunkGraph 的单图架构就是当时 Webpack 的核心架构, Webpack 的依赖分析, 包括 CommonsChunkPlugin 的公共代码优化, 打包等等都是基于这个核心架构来完成的.

那为什么后来到 v4 就变了呢, 经历过 v3 到 v4的同学应该有感触, Webpack v4 升级在当时是非常不平滑的, 好多插件不兼容, 迁移成本很大, 这里一个核心原因其实在于这个 CommonsChunkPlugin 的优化已经不能适应当时前端工程化对于拆包的诉求, 于是就有了 v4 的重大变更以及后来引入的 SplitChunksPlugin

v3 - v4 从单图架构到双图架构

随着技术发展, Webpack 团队逐渐发现 CommonsChunkPlugin 已经没有太多的优化空间, 在官网上有一篇文章解释了无法继续优化的原因, 其中的原因和优化策略有关, 简单来说就是在 ChunkGraph 中, 所有的 Chunk 都有一个 parent info, 标注了自己的父级 Chunk 是哪一个, 然后假设有一个 module 它所在的 Chunk 的 parent 是全的, 那说明这个 module 是一个可以被 common 化的 module, 所以你看这个 CommonsChunk 就知道这插件干吗的, 就是搞一个 Chunk 把这些公共的 module 都存起来, 然后呢? 再添加回 ChunkGraph, 然后因为每个 Chunk 都得有 parent 那个 info嘛, 所以 Webpack 默认 CommonsChunk 是 parent chunk, 这么做可能早期问题不大, 不过后来就有问题了, 主要是 2 点.

  • 因为 CommonsChunk 里面是一堆公共依赖, 但是没啥先后顺序, 所以很难异步加载, 你必须尽可能同步
  • 另外么就是反正你要不要不重要哈, 不管里面的 module 你要不要, 反正这个 chunk 一加载就全来了, 拖家带口的非常不友好

另外 chunk 作为一种抽象设计, 但却隐含了 module 的依赖关系, 这种实现其实比较难以理解, 因为两者的抽象维度并不相同, 这也会导致后续的其他优化在这一设计上遇到困境.

于是就有了 v4 里的双图架构, 这里的双图是原有的 ChunkGraph, 另一个是 ModuleGraph. 然后 CommonsChunk 变成了 SplitChunk, 并且把原来的 parent chunk 和 child chunk 的关系变成了 parent chunk group 和 child chunk group 的关系, 这里 chunk group 也是个新增的概念, 其实就是为了把 chunk 变纯粹, 父子关系抽象到 chunk group 上去了, Webpack 管这个叫启发式, React 也有个启发式, 感觉比较相似, 双图架构的好处显而易见, 相当于把搅和在一起的两张网给拆开了, Chunk 依然包含 Module 但是在依赖分析上, ModuleGraph 自己玩自己的, Chunk 成了一个纯粹的抽象概念

因为 Chunk 的分析变独立了, 也没有父子结构了, 扁平了, 对 Chunk 的拆分组合以及异步化就变得非常容易了, 想怎么组合怎么组合, 想异步就异步, 只要把 Chunk 本身做个分类, 比如有 inital chunk 和 non-inital chunk, 后者就是用来搞异步的, 前者就是通过 entry 形成的那个 chunk

所以从 v3 到 v4 Webpack 的拆包功能提升了很多, SplitChunk 非常灵活, 可控性也强了很多, 不过前端工程化发展的也非常快, 从 v4 到现在 2年多的时间, 微前端 被抛了出来, 这对前端工程化又提出了新的要求, 自然作为与时俱进的 Webpack 团队也不能落后不是, 于是就有 v5, 新鲜出炉的热乎特性 Module Federation , 同样的 v5 也带来了新的架构设计

v5 - 基于容器的架构

因为 v5 目前依然处于 beta 阶段, 所以这里写的一些概念可能随后都有可能发生变化, 最终以官网发布的版本为准, 我会尽可能更新文章以匹配官方的内容

其实之所以有 v5, 如果没有 v4 的 ModuleGraph 架构升级也是做不到的, 因为你看 Module Federation, 是基于 Module 的, 如果还是 v3 的 单图架构, 包都拆不出来不要说搞联盟了, 最多是个独裁, 所以架构是演进出来的, 不存在跳跃式的架构, 每一次演进都是有迹可循, 相互联系的 , 所以 Module Federation 要解决什么问题?

Module Federation 要解决的核心问题是, 如何在生产环境的 runtime 里支持构建, 并且还不影响原有的线下的构建模式, 从而支持线上构建结果的水平拆解. 简单来讲呢就是你可以线上加载别的 bundle 里的模块而不用线下重新 build 了, 很神奇是不是, 如果你受够了 npm 引用, 对方一升级, 你要重新打包构建发布, 那确实很神奇, 解决了大问题.

所以要做到这一点, 原有的架构设计肯定不满足嘛, 于是就有了新概念 Container

Container 里有一个 fileName , 就是 output 的别名, 然后 Container 也有自己的 entry, 通过 name 来映射到你自己配置里的 entry, Container 还有一个重要的概念就是拿 module 当接口, 没错, 你没看错, 你就把 Container 想象成啥一个服务, 对 api 是啥就是里面的 module, 所以你要这个 module 就很简单, 去请求这个 Container 就好了嘛, 所以 Container 有一个 remotes 属性用来标识一个请求源.

所以在 v5 的核心架构里, 除了原有的双图架构, Webpack 增加了 Container 这个概念, 有点像 docker? 而内部的 module 类似服务接口, 这些设计确实很有脑洞, 也能看出来 Webpack 团队在前端工程化上积累多年的技术理解和创新意识.

不过因为 v5 还未发布, 所以后续是否有变化不好说, 从目前看, 这套架构设计上有些概念还比较模糊, 所以我觉得还有很大的优化空间, 也就意味着还会发生一些变化, 至少在我看的上个月的相关内容, 在这个月就有些不太一样了, 如果你感兴趣, 可以自行查阅相关的文档, 包括示例, 官方给的还是挺全的, 同时也清保持对 Webpack v5 持续关注吧.

后话

Webpack 的架构设计非常有趣也很值得深入学习, 对于理解前端工程化有很大的帮助, 通过学习和探索, 也让我获益良多, 也希望以此文唤起大家对架构设计上的探索和兴趣

本文使用 mdnice 排版