2021-01-03

介绍

chunk(module 的集合)在 webpack 解析的依赖图中以父子关系联系起来的。最初CommonsChunkPlugin被设计用于 chunk 之间避免重复依赖,但是性能远远不是最优解。

在 webpack 4 中,内置了ChunkSplitPlugin用于替代CommonsChunkPlugin

以下基于官方 demo进行梳理,将主要的数据结构结合图示和个人的理解进行总结。

揭开此插件的设计思路和源码神秘面纱

思路

准备

在梳理之前建议先了解一下 webpack 内部基于 tap 事件流架构。
以及 Chunk(

观察 demo 中文件结构,可以简单得出以下结论

  • 共有 7 个入口文件 a ~ f.js,也就是最少七个 chunk(分别通过索引 1 ~ 7 来表示)
  • 共有 14 个 js 文件,则共有 14 个 module
  • stuff/*下的 s1.js~s8.js 和 node_modules/m1~m7.js 之间是没有相互依赖的

插件被注入在 compiler 的thisCompilation的 tap 事件以及 compilation 的optimizeModules的 tap 事件下

thisCompilation:Executed while initializing the compilation, right before emitting the compilation event.
optimizeModules:Called at the beginning of the module optimization phase. A plugin can tap into this hook to perform optimizations on modules.
为了方便后续调试和梳理,特意将 demo 的 14 个文件之间的依赖关系整理出来
[图片上传失败...(image-17842f-1609774367116)]

分别将 7 个入口文件 entryPoint 按颜色区分开来,箭头指向为依赖关系,橘黄色的区域为经过ChunkSplitPlugin生成的newChunk

以及将执行前的所有 Chunk 快照
[图片上传失败...(image-afe7ac-1609774367116)]

./webpack.config.js

module.exports = {
  // mode: "development || "production",
  entry: {
    pageA: "./pages/a",
    pageB: "./pages/b",
    pageC: "./pages/c",
    pageD: "./pages/d",
    pageE: "./pages/e",
    pageF: "./pages/f",
    pageG: "./pages/g",
  },
  optimization: {
    splitChunks: {
      chunks: "all",
      maxInitialRequests: 20, // for HTTP2
      maxAsyncRequests: 20, // for HTTP2
      minSize: 40, // for example only: chosen to match 2 modules
      // omit minSize in real use case to use the default of 30kb
    },
  },
};

前置逻辑

在插件分析整个依赖图整理 module 之间的重复依赖之前,进行了大量的额外的优化,通过空间换时间,将大部分数据状态缓存起来,

以下就通过代码顺序和逻辑一步步进行剖析。

chunkSetsInGraph

通过遍历 14 个 module,分析 module.chunksIterable 被依赖的 Chunk,通过 Chunk 的索引整合成 key 值指向 Chunk 的集合,类型为Map<string, Set<Chunk>>

[图片上传失败...(image-b3db48-1609774367116)]



例如:遍历到 m1 时,该模块被 Chunk1 和 Chunk2 以及 Chunk3 引用,则如图所示 "1,2,4"->set<Chunk>{Chunk1,Chunk2,Chunk3}

当遍历到 m2 时,发现改模块也被 Chunk1 和 Chunk2 以及 Chunk3 引用,"1,2,3"已经被缓存,则跳过继续遍历

chunkSetsByCount

chunkSetsInGraph进行遍历,将chunkSet通过 length 聚合。类型为Map<number, Array<Set<Chunk>>>
[图片上传失败...(image-2085c0-1609774367116)]

中推入 key 值为 2 指向[{Chunk1,Chunk3}]数组。
当遍历

combinationsCache

对所有的 ChunkSet 进行遍历,将 ChunkSet 及它的子 Set 进行聚合,通过聚合中的 ChunkSet 最大 key 进行引用。类型为Map<string, Set<Chunk>[]>

[图片上传失败...(image-baf9b6-1609774367116)]

例如:遍历 chunkSetInGraph,当 key 值为1,2,3时,指向[{chunk1,chunk2,chunk3}],同时后续遍历中将所有 subSet 推入,最后"1,2,3"指向[{chunk1,chunk2,chunk3},{Chunk1,Chunk3},{Chunk1},{Chunk2},{Chunk3}]

selectedChunksCacheByChunksSet

所有的 chunkSet 对应的不同的配置过滤后的结果,基于 webpack.config.js。类型WeakMap<Set<Chunk>, WeakMap<ChunkFilterFunction, SelectedChunksResult>>

在我们的 demo 中chunks: "all"的配置,所有的filterFunction都是const ALL_CHUNK_FILTER = chunk => true;,所以SelectChunkResult都是 chunkSet 本身

[图片上传失败...(image-81589e-1609774367116)]

核心

基于以上的生成的缓存数据,接下来就是开始生成最重要的数据结构chunksInfoMap,同时遍历这个对象剥离出新的 Chunk 并且在 ChunkGroup 中建立联系。类型Map<string, ChunksInfoItem>,ChunksInfoItem 类型为{modules,cacheGroup,name,chunks,chunksKeys,blabla...}

对 modules,combinationCache,以及 cacheGroups 进行三层嵌套循环找出每个被依赖的 module 的重复 chunk 关系(可以尝试对照第一张图逆向推导)

cacheGroups 是中间数据, 遍历每个 module 时生成的配置项,主要用于区分 module 的类型用于过滤
例如:在chunk:all的配置下,module 默认存在配置{key:normal,minChunk: 2},但是node_modules/*下的 m1 ~ m7 除了默认配置额外{key:vendor,minChunk: 1}

例如:遍历到 m1 时,存在两个 cacheGroup 配置项[{key:normal},{key:vendors}],且在 combinationCache 被{chunk1,chunk2,chunk3} 及子集[{chunk1,chunk3},{chunk1},{chunk2},{chunk3}]依赖。所以基于 key 值分别推入 7 条数据(另外 3 条由于 vendor 的 minChuck 为 2 被过滤)

[图片上传失败...(image-e02411-1609774367116)]

最后对chunksInfoMap循环检索

在每次循环检索,推出优先级最高的ChunksInfoItem(通过 name/path/chunks.lengthd 长度等等)。

通过校验后,使用compilation.addChunk生成一个 newChunk,并且GraphHelpers.connectChunkAndModule将 newChunk 注入 Graph 中,并且通过chunk.removeModule移走旧 chunk 中 newChunk 中的 module。

直至 chunksInfoMap 为空

注意:进入下一次循环前,会将剩余的 ChunksInfoItem 的 module 进行清理,已被封装进入 newChunk 的 module 移除,如果 ChunksInfoItem 的 module 为 0,则这个 chunkInfoItem 被 delete

总结

webpack 极大的解放了前端工程化的劳动力,一个插件中的逻辑都能给予我们大量的启发,并且对于阅读知名的开源项目,源码结构也确实十分严谨和美观,收获颇丰。

由于 demo 比较基础,插件中很多边界情况并没有涉及到,但是主流程梳理的比较完善。

研习大佬的代码,就像阅读一本著作,虽然很痛苦但是却很享受~

作者:洪春
链接:https://juejin.cn/post/6844903823840919565
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容

  • 0 HTML5相关 websocket WebSocket 使用ws或wss协议,Websocket是一个持久化的...
    可爱多小姐阅读 876评论 0 0
  • 2018web前端最新面试题总结 一、Html/Css基础模块 基础部分 什么是HTML?答:​ HTML并不是...
    duans_阅读 4,667评论 3 27
  • 前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们...
    咸小七阅读 388评论 0 2
  • Vue最全知识点,面试必备(基础到进阶,覆盖vue3.0,持续更新整理,欢迎补充讨论) 参考文章传送:1.童欧巴对...
    Robertbiu阅读 395评论 0 3
  • Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需...
    ui设计小姐姐阅读 160评论 0 0