想象你准备搬家。最省事的做法是什么?把所有东西一股脑塞进一个超大行李箱——衣服、书籍、锅碗瓢盆全挤在一起。箱子倒是只有一个,可你根本提不动,到了新家想要一本书,还得把整个箱子翻个底朝天。 前端工程师面对的问题和搬家如出一辙。构建工具默认会把几百个代码模块合并成一个巨大的文件,用户打开网页的第一秒就要下载并解析这个庞然大物。页面自然“提不动”——加载慢、白屏久、体验糟。 代码分割和懒加载,就是一场“搬家收纳革命”:把书放书箱、衣服放衣箱、厨具放厨具箱,彼此独立;不常用的物品放进储物间,要用时再去取;可能用到的东西提前放在门口。这篇文章就用这套家居智慧,带你理解 Webpack 的 splitChunks 插件如何做公共依赖提取、cacheGroups 分组、路由级分包和 prefetch 预取——一句话,教你优雅地“打包”,让网页真正跑起来。

岗位关联

作为前端或 AI 应用开发者,掌握代码分割与懒加载是优化 Web 应用首屏性能的必备技能。理解 splitChunks 的分包逻辑与缓存策略,你能在真实项目中把页面加载时间缩短 50% 以上,这是工程化能力的重要拼图。

目录
  1. 可视化演示
  2. 打包认知:不是越大越好
  3. 公共依赖提取:客厅的共享书架
  4. 缓存分组策略:会贴标签的收纳师
  5. 路由级分包:按房间就位的行李
  6. 预取预加载:提前放在门口
  7. 粒度权衡优化:箱子大小有学问
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

对比SGD、Momentum、Adam、RMSProp四种优化器在损失曲面上的收敛轨迹,展示动量加速和自适应学习率对优化路径的影响。支持轨迹动画与收敛曲线切换。

本节配有交互动效(进入视口后自动加载,约 3KB)。

优化器收敛轨迹对比 SGD沿梯度方向缓慢收敛 Adam自适应学习率快速逼近最优 θ←θ−η∇L(θ) 动量与自适应学习率加速收敛

打包认知:不是越大越好

搬家时,把所有东西塞进一个超大行李箱,表面省事,实则灾难:箱子超重、搬运困难、找东西要翻腾半天。前端工程里,Webpack 默认也会把全部 JavaScript 模块打包成一个巨大的 bundle 文件。用户打开网页,浏览器必须先把整个文件下载完、解析完,页面才能渲染。首页可能只用到全部代码的 20%,剩下 80% 都在“陪跑”。

这里的“模块”是代码世界的基本单元——登录逻辑、商品列表、购物车、图表组件,每个都是独立模块。Webpack 的打包动作,就是把这些零散模块“装箱”。默认一个箱子装所有,就是灾难的根源。

打包体积可以用一个简单的公式刻画:

所以,代码分割的第一步是“拆箱”:把一个大 bundle 按边界拆成多个 chunk(代码块),让用户只下载当前需要的部分。这就如同搬家时把大箱子换成多个分类小箱,每个箱子贴好标签,要什么拿什么。

Sbundle=Sruntime+∑i=1nsiS_{bundle} = S_{runtime} + \sum_{i=1}^{n} s_i
一个 bundle 的体积(KB)= Webpack 运行时固定开销 + 所有被放入的模块体积之和。模块越多越大,包就越臃肿。
  1. 模块体积累加每个 JS 模块都有体积,把 n 个模块的体积逐一加起来就得到模块总体积。
    ∑i=1nsi\displaystyle \sum_{i=1}^{n} s_i
  2. 运行时固定开销Webpack 自身有一段引导加载代码,负责在浏览器里调度 chunk 的加载,体积虽小但固定存在。
    Sruntime\displaystyle S_{runtime}
  3. 合计得到总包体积模块总体积与运行时开销相加,就是用户必须下载的 bundle 大小。
    Sbundle=Sruntime+∑i=1nsi\displaystyle S_{bundle} = S_{runtime} + \sum_{i=1}^{n} s_i

公共依赖提取:客厅的共享书架

三个房间都需要读工具书,如果每间房各买一套,浪费钱又占地方。聪明的做法是把工具书放到客厅公共书架,谁需要谁去取。前端项目里,多个页面(入口)往往共用同一个第三方库——React、axios、工具函数等。默认打包方式下,每个入口都会把这个库复制一份进去,三个页面就打包三份 React,用户被白白重复下载两遍。

splitChunks 插件做的正是“客厅书架”的工作:自动识别被多个入口引用的共享模块,将它们提取成一个独立的公共 chunk。假设有 m 个公共模块,第 k 个模块被 $d_k$ 个入口引用、体积为 $s_k$,提取后节省的体积公式如下:

例如 React 体积 140KB,被三个页面共用,提取后省下 2 × 140 = 280KB 的重复传输。公共 chunk 单独成文件还有个额外好处:它几乎不变,浏览器可以长期缓存,用户第二次访问直接命中缓存。

Ssaved=∑k=1m(dk−1)⋅skS_{saved} = \sum_{k=1}^{m} (d_k - 1) \cdot s_k
节省的总体积 = 每个公共模块体积 ×(被引用次数 − 1)。被越多入口共用的模块,提取的价值越大。
  1. 引用次数统计$d_k$ 表示第 k 个公共模块被多少个入口引用,比如 React 被 3 个页面引用,$d_k = 3$。
    dk=3, 被三个入口引用\displaystyle d_k = 3, \text{ 被三个入口引用}
  2. 单模块节省量提取前每个入口都打包一份,提取后只需一份公共副本。$(d_k - 1)$ 就是省掉的重复份数。
    (dk−1)⋅sk=(3−1)×140 KB=280 KB\displaystyle (d_k - 1) \cdot s_k = (3 - 1) \times 140 \text{ KB} = 280 \text{ KB}
  3. 累加所有节省量所有公共模块的节省值加起来,就是整体节省的传输体积。
    Ssaved=∑k=1m(dk−1)⋅sk\displaystyle S_{saved} = \sum_{k=1}^{m} (d_k - 1) \cdot s_k

缓存分组策略:会贴标签的收纳师

有经验的搬家师傅不会给箱子统一命名“杂物一箱”“杂物二箱”。他们会贴标签:书、衣物、厨具、电器。找东西时看标签即知去向。splitChunks 提取公共依赖时,也不是把所有东西都塞进一个箱子,它通过 cacheGroups(缓存组)配置分组规则:用 test 匹配模块身份,用 priority 决定优先级,用 minSize 控制最小提取体积。

每个缓存组就是一个“标签”。假设有两个分组:vendors 匹配 node_modules 下的第三方库,utils 匹配 src 下的公共工具。模块 m 最终归属到所有命中组中 priority 最大的那个,用公式表达:

实际配置可能长这样:

这里 vendors 优先级 10,utils 优先级 5。React 命中 vendors 就进 vendors chunk;src/utils 下的工具函数则进 utils chunk。贴标签的本质目标是让同类模块稳定聚在一起,chunk 文件名长期不变,浏览器缓存才能发挥最大价值。

C(m)=arg⁡max⁡g∈G[priorityg⋅matchg(m)]C(m) = \arg\max_{g \in G} \left[ priority_g \cdot \text{match}_g(m) \right]
一个模块会被匹配到多个分组,但最终只进入权重(priority)最大的那个组。这就是“标签打架时,谁力气大谁说了算”。
  1. 分组命中判断$\text{match}_g(m)$ 是 0 或 1,表示模块 m 是否满足分组 g 的 test 匹配规则。1 表示命中,0 表示未命中。
    matchg(m)∈{0,1}\displaystyle \text{match}_g(m) \in \{0, 1\}
  2. 优先级加权模块命中分组后,用该组的 priority 值作为权重。priority 数字越大,分组的话语权越强。
    priorityg⋅matchg(m)\displaystyle priority_g \cdot \text{match}_g(m)
  3. 选择最大权重归属$\arg\max$ 表示在所有命中的候选中挑出权重最大的那个分组,模块最终进入这个组的 chunk。
    C(m)=arg⁡max⁡g∈G[priorityg⋅matchg(m)]\displaystyle C(m) = \arg\max_{g \in G} \left[ priority_g \cdot \text{match}_g(m) \right]
js14 行
splitChunks: {  cacheGroups: {    vendors: {      test: /[\\/]node_modules[\\/]/,      priority: 10,      name: 'vendors'    },    utils: {      test: /[\\/]src[\\/]utils[\\/]/,      priority: 5,      name: 'utils'    }  }}

路由级分包:按房间就位的行李

到了新家,书放书房、衣服放卧室、厨具放厨房——你不会把书房的工具搬到卧室用。每个房间只留自己需要的东西。对于单页应用,路由就是“房间”。用户一开始只进入“首页”这个房间,完全不需要“个人中心”“订单详情”这些房间的代码。路由级分包让每个路由页面独立成 chunk,访问时才加载。

实现方式就是动态 import。把一个普通的静态导入改为 import('./pages/Home.vue'),Webpack 会立刻明白:这个模块要单独成包。首屏体积用公式表达:

代码示例:

用户从首页跳转到订单页时,浏览器才动态下载订单页的 chunk。这就是“按房间分配行李”的工程实现——每个页面只携带自己的代码,其他代码安心躺在服务器里等待召唤。

Sfirst=Sentry+Sroute1S_{first} = S_{entry} + S_{route_1}
首屏体积 = 入口 chunk(应用启动必需代码)+ 当前路由页面 chunk。其他路由的代码完全不参与首屏下载。
  1. 入口 chunk 体积$S_{entry}$ 是应用启动时必须加载的代码,包含 Webpack 运行时、全局配置、路由表等,这部分无法再分割。
    Sentry\displaystyle S_{entry}
  2. 当前路由体积$S_{route_1}$ 是初始路由页面的代码。因为其他路由已被拆成独立 chunk,所以首屏完全不包含它们。
    Sroute1\displaystyle S_{route_1}
  3. 相加得到首屏总量入口代码 + 当前页面代码,就是首屏需要下载的全部内容。
    Sfirst=Sentry+Sroute1\displaystyle S_{first} = S_{entry} + S_{route_1}
js5 行
const routes = [  { path: '/', component: () => import('./pages/Home.vue') },  { path: '/order', component: () => import('./pages/Order.vue') },  { path: '/me', component: () => import('./pages/Me.vue') }];

预取预加载:提前放在门口

上班前知道今天要带文件,昨晚就把文件放进包里、放在门口,出门时顺手拿走,一秒不耽误。这就是预取的智慧。懒加载解决了“暂时不需要”的问题,但有些资源用户“马上可能需要”——等用户点击了才开始下载,还是会有一段白屏等待。

prefetch 会在浏览器空闲时预先下载未来可能用到的 chunk;preload 则立即加载当前页面即将使用的关键资源。Webpack 用一行魔法注释开启:

prefetch 的 chunk 在主资源加载完毕后、浏览器空闲时静默下载,不抢占首屏带宽;preload 的 chunk 与父 chunk 并行加载,优先级更高。用户感知到的等待时间可以用公式刻画:

放在门口的文件,出门时直接拿走,一分一秒都不等。prefetch 就是那个“提前放门口”的帮手——它不抢首屏资源,只在空闲时默默准备,把未来的等待悄悄抹平。

twait=ton_demand−tprefetcht_{wait} = t_{on\_demand} - t_{prefetch}
用户的等待时间 = 按需加载耗时 − 预取提前量。预取把网络请求从“点击之后”搬到了“空闲时刻”。
  1. 按需加载耗时$t_{on\_demand}$ 是用户触发导航后才开始下载 chunk 所需的完整耗时,包括网络请求、解析、执行。
    ton_demand\displaystyle t_{on\_demand}
  2. 预取提前量$t_{prefetch}$ 是浏览器在空闲时段提前下载该 chunk 所花的时间。因为发生在需要之前,所以相当于“提前量”。
    tprefetch\displaystyle t_{prefetch}
  3. 实际等待差从按需耗时中减去预取提前量,得到用户真正感知到的等待时间——如果预取已完成,这段等待接近 0。
    twait=ton_demand−tprefetch\displaystyle t_{wait} = t_{on\_demand} - t_{prefetch}
js2 行
import(/* webpackPrefetch: true */ './pages/Order.vue');import(/* webpackPreload: true */ './components/Modal.vue');

粒度权衡优化:箱子大小有学问

箱子分得太细,100 个小箱子搬运时容易丢还累人;箱子太少太大,搬起来费劲。搬家师傅会根据物品的贵重程度与使用频率权衡箱子配置。代码分割同样讲究“粒度”:粒度太细,chunk 数量爆炸,HTTP 请求过多,浏览器缓存容易失效;粒度太粗,单个 chunk 过大,首屏依旧缓慢。

衡量分割效果的核心指标是缓存命中率。假设 $S_{stable}$ 是长时间不变化的模块体积(第三方库、公共工具),$S_{total}$ 是所有 chunk 的体积和,则缓存命中率公式如下:

配置实践上:给第三方库设置较大的 minSize(比如 30KB),避免小库被反复提取;业务代码设置较小 minSize,保持独立性;chunk 文件用 contenthash 命名,内容不变文件名就不变,缓存长期有效。最优的搬家方案,就是让常动的物品进小箱子、不动的物品进大箱子——稳定代码长期命中缓存,易变代码小而精。

Hcache=SstableStotal×100%H_{cache} = \frac{S_{stable}}{S_{total}} \times 100\%
缓存命中率 = 稳定代码体积 ÷ 全部代码体积。命中率越高,用户升级版本时只需下载变化的小部分代码。
  1. 稳定代码体积$S_{stable}$ 是版本迭代中几乎不动的模块——React、axios 这类第三方库,以及被广泛复用的公共工具。
    Sstable\displaystyle S_{stable}
  2. 全量代码体积$S_{total}$ 是所有 chunk 的体积总和,包括易变业务代码和稳定公共代码。
    Stotal\displaystyle S_{total}
  3. 计算百分比命中率稳定代码占比越高,浏览器缓存起作用的范围越大,用户每次更新需要重新下载的内容越少。
    Hcache=SstableStotal×100%\displaystyle H_{cache} = \frac{S_{stable}}{S_{total}} \times 100\%

AI 应用场景

大型电商首页首屏优化

阶段:构建部署、性能优化

解决的问题:电商首页聚合了商品列表、推荐流、购物车、优惠券等多个模块,传统全量打包导致首页 JS 超过 2MB,弱网下白屏 3 秒以上。AI 团队在构建环节用 splitChunks 提取 react/react-dom/axios 到 vendor chunk,按路由拆包首页推荐流与购物车组件,首屏体积从 2MB 降到 450KB。

具体案例:某头部电商前端团队在构建配置中加入缓存分组与路由级分包后,配合 prefetch 预取搜索页 chunk,首屏加载时间从 4.2s 降到 1.3s,转化率提升 12%。

中后台管理系统路由懒加载

阶段:代码架构、工程化

解决的问题:中后台系统常包含几十个业务页面(用户管理、权限配置、数据报表等),一次性全部打包让登录后的首屏加载很慢。传统方案需要手动维护每个页面的动态导入,易出错。

具体案例:某 AI 数据标注平台有 30 多个标注管理页面,团队用动态 import 包裹路由组件,配合 cacheGroups 把良构的标注组件库提取为公共 chunk,首屏从 6s 降到 2s,后续页面切换时按需加载。

移动端 H5 活动页弱网优化

阶段:前端开发、交付

解决的问题:移动端 H5 在网络不稳定的环境下,大 bundle 下载失败率高。拆分后的 chunk 更小,单个请求失败代价低,且配合 preload 让关键交互组件优先加载。

具体案例:某 AI 营销活动页使用路由级分包 + prefetch 预取分享模块,在 4G 弱网环境下首屏加载成功率从 82% 提升至 97%,用户分享转化率提升 20%。

Python 演示

python64 行
"""splitChunks 分割决策模拟器对比"全量打包"与"公共依赖提取 + 分包"的首屏体积差异"""  class Module:    """一个 JS 模块"""     def __init__(self, name: str, size: int, pages: list):        self.name = name        self.size = size          # 体积(KB)        self.pages = set(pages)   # 使用该模块的页面  class SplitChunksSimulator:    """模拟 splitChunks 的提取逻辑"""     def __init__(self, min_shared=2, min_size=30):        self.min_shared = min_shared  # 至少被几个页面共享才提取        self.min_size = min_size      # 低于该体积不提取     def extract_commons(self, modules):        """筛选出值得提取为公共 chunk 的模块"""        return [            mod for mod in modules            if len(mod.pages) >= self.min_shared and mod.size >= self.min_size        ]  def main():    modules = [        Module("react", 140, ["首页", "订单页", "个人页"]),        Module("axios", 28, ["首页", "订单页"]),        Module("utils", 45, ["首页", "订单页", "个人页"]),        Module("charts", 95, ["订单页", "个人页"]),        Module("home-banner", 60, ["首页"]),        Module("order-list", 80, ["订单页"]),        Module("user-info", 50, ["个人页"]),    ]     splitter = SplitChunksSimulator(min_shared=2, min_size=30)    commons = splitter.extract_commons(modules)    common_size = sum(m.size for m in commons)    common_names = {m.name for m in commons}     home_modules = [m for m in modules if "首页" in m.pages]    home_before = sum(m.size for m in home_modules)    home_private = [        m for m in home_modules if m.name not in common_names    ]    home_after = sum(m.size for m in home_private) + common_size     print("===== splitChunks 分割决策模拟 =====")    print(f"全站模块数:{len(modules)},提取公共模块数:{len(commons)}")    print(f"公共 chunk:{', '.join(m.name for m in commons) or '无'}")    print(f"公共 chunk 体积:{common_size} KB")    print(f"首页分割前体积:{home_before} KB")    print(f"首页分割后体积:{home_after:.0f} KB")    print(f"首屏下降:{(1 - home_after / home_before) * 100:.1f}%")  if __name__ == "__main__":    main()
目的:用 Python 模拟 Webpack splitChunks 的核心决策——哪些模块应被提取为公共 chunk,以及提取后首页首屏体积下降多少。
  1. if len(mod.pages) >= self.min_shared and mod.size >= self.min_size:模块必须同时满足“被多个页面共享”和“体积够大”两个条件才值得提取,否则提取收益太低。
  2. common_names = {m.name for m in commons}把公共模块名称放进集合,后续判断首页模块是否属于公共部分时,O(1) 即可完成查找。
  3. home_private = [m for m in home_modules if m.name not in common_names]首页分割后只需加载自己独有的模块,公共模块统一从公共 chunk 获取。
  4. home_after = sum(m.size for m in home_private) + common_size分割后的首页体积 = 首页独有模块体积 + 公共 chunk 体积,公共部分只下载一次。

常见误区

误区把 node_modules 里所有库都放进一个 vendors chunk,认为这是 splitChunks 的标准配置,一劳永逸。

正解把所有第三方库合并成一个巨大 chunk,会让所有页面都加载这份完整代码。有些库只被某个页面使用,比如图表库只在报表页用到,打进 vendors 后其他页面也要白下载。正确做法是用 cacheGroups 细分:核心库(react、vue)放一个组,按需库(图表、日期选择器)单独分组或用 minSize 过滤。相当于搬家时“工具书”和“儿童绘本”也分类,而不是混成一堆。

为何常见:网上教程和脚手架模板常以“一个 vendors 分包”作为示例,初学者照搬后误以为这是最优解。

误区既然代码分割能减小体积,那把每个模块都拆成独立 chunk,不是能做到“用多少下载多少”吗?

正解每个 chunk 都对应一次 HTTP 请求,浏览器对并发请求数有限制,chunk 太多会让加载变成排队串行。而且 chunk 之间有依赖关系,浏览器需要按依赖顺序逐个请求,反而拖慢速度。搬家也是同理——把所有物品拆成 100 个小袋子,搬运时更容易丢、更费时间。最优粒度是“稳定代码进大箱、易变代码进小箱”的平衡。

为何常见:直观上觉得“需要时才加载”就是最优,忽略了网络请求本身的成本和依赖链的串行开销。

误区只要把静态 import 改成动态 import(),Webpack 就会自动做最优分割,配置 splitChunks 是多余的。

正解动态 import() 只是语法层面的“异步加载信号”,分割成多大的 chunk、哪些模块合并、哪些单独拆出,这些决策由 splitChunks 配置决定。如果没有合理配置,Webpack 可能按默认策略把相关模块都打包进一个 chunk,或者生成大量碎片 chunk。就像搬家时你能决定“这个箱子是书”,但箱子具体怎么打包、哪些东西共享一个箱子,需要收纳师的规划。

为何常见:初学者容易把“语法糖”当作完整解决方案,没有意识到代码分割是“语法触发 + 配置规划”两个环节配合的结果。

误区给所有路由 chunk 都加上 `webpackPrefetch: true`,让浏览器提前下载一切,首屏岂不是秒开?

正解prefetch 是在浏览器空闲时加载“未来需要”的资源,它不改变首屏已有的加载路径。给所有 chunk 加 prefetch,会在空闲时段把大量资源下载下来,反而抢占带宽、消耗流量,甚至影响首屏之后的交互性能。首屏关键资源应该用 preload 或正常加载,prefetch 只适合精准预测用户下一步会访问的页面。就像提前放门口的东西,只放确定要带的,而不是把整个家都搬到门口。

为何常见:把“预取”误解为“提前加载所有”,忽略了浏览器资源加载的优先级和带宽分配机制。

面试题

Q1. Webpack 中 splitChunks 插件的主要作用是什么?

单选题 · easy

  • A.压缩 JavaScript 代码体积
  • B.将代码拆分为多个 chunk← 正确答案
  • C.将 ES6 语法转译为 ES5
  • D.自动生成源码映射文件

答案:B。splitChunks 的核心职能是代码分割,把一个大 bundle 拆成多个 chunk,让公共依赖单独成包。代码压缩是 TerserPlugin,ES6 转译是 Babel,source-map 生成也不是 splitChunks 的目标。用搬家的比喻:splitChunks 是决定“哪些东西放哪个箱子”的收纳师。

Q2. 动态 import() 语句的返回值是什么?

单选题 · easy

  • A.一个普通的模块对象
  • B.一个 Promise 异步对象← 正确答案
  • C.一个包含全部依赖的数组
  • D.一个 chunk 配置对象

答案:B。动态 import() 返回的是 Promise 对象,代码加载完成后通过 then 或 await 拿到模块对象。它让 Webpack 知道这个模块需要被单独分割成 chunk。就像你打电话叫搬家公司,电话本身(Promise)只是通知,箱子送到家才拿到东西(模块)。

Q3. 以下哪些属于代码分割的常见方式?

多选题 · easy

  • A.提取公共依赖到独立 chunk← 正确答案
  • B.按路由动态 import 分包← 正确答案
  • C.删除代码中的所有注释
  • D.使用 gzip 压缩传输体积

答案:AB。提取公共依赖和按路由动态 import 都是“改变代码分箱结构”的分割手段。删除注释是代码压缩,gzip 是传输层压缩,两者都不改变代码的打包边界。就像搬家时纸箱分类是分割,把毛巾拧干是压缩——目的不同,手段也不同。

Q4. cacheGroups 中 test 字段的作用是什么?

单选题 · mid

  • A.指定输出的文件名称格式
  • B.匹配模块归属哪个分组← 正确答案
  • C.设置 chunk 最小体积
  • D.配置浏览器缓存过期时间

答案:B。test 字段通过正则表达式匹配模块路径,决定模块归属哪个缓存组。比如 test: /node_modules/ 匹配所有第三方库。输出文件名称由 name 字段控制,minSize 才控制最小体积,浏览器缓存过期时间与 Webpack 配置无关。

Q5. 路由级分包的核心思想是什么?

单选题 · mid

  • A.将路由配置写入独立 JSON 文件
  • B.路由代码独立为 chunk 按需加载← 正确答案
  • C.将所有路由改为服务端渲染模式
  • D.减少路由文件中冗余的注释代码

答案:B。路由级分包让每个路由页面的代码独立成为 chunk,用户访问哪个路由就加载哪个 chunk。这就像按房间分配行李——书房的书只在书房取用。路由配置写入 JSON 或服务端渲染是另一回事,不会改变代码分割的粒度。

Q6. 关于 prefetch 与 preload 的说法,正确的是?

单选题 · mid

  • A.prefetch 并行加载当前页必需资源
  • B.preload 空闲时加载未来资源
  • C.prefetch 空闲时加载未来资源← 正确答案
  • D.preload 与 prefetch 没有区别

答案:C。prefetch 在浏览器空闲时预取未来需要的资源(如下一个路由页),preload 则立即加载当前页面马上要用的高优资源。两者的加载时机和优先级不同。比喻:prefetch 是把文件提前放门口,preload 是出门时顺手拿起身边的人递来的钥匙。

Q7. 关于懒加载的说法,正确的有?

多选题 · mid

  • A.懒加载的 chunk 在需要时才下载← 正确答案
  • B.懒加载可以减少首屏请求体积← 正确答案
  • C.懒加载的 chunk 不会被浏览器缓存
  • D.懒加载需要动态 import() 配合← 正确答案

答案:ABD。懒加载的 chunk 在触发时才下载,因此首屏体积更小,且需要动态 import() 来实现。但一旦加载完成,浏览器同样会缓存该 chunk,下次访问直接命中缓存。就像储物间的箱子,取一次之后放回原处,下次要再拿更快。

Q8. 以下哪种配置最有利于缓存命中率的提升?

单选题 · hard

  • A.将全部代码合并打包为一个 chunk
  • B.第三方库单独提取为固定 chunk← 正确答案
  • C.为每个模块单独生成一个 chunk 文件
  • D.把第三方库混合进每个页面入口 chunk

答案:B。第三方库内容长期不变,单独提取成固定 chunk 后文件名用 contenthash 命名,版本更新时文件名保持不变,浏览器长期命中缓存。全部合并则任何小改动都会让整个 chunk 失效;每个模块一个 chunk 请求数过多;混合打包会让第三方库跟着页面一起更新。

Q9. 关于 cacheGroups 的匹配与优先级,正确的有?

多选题 · hard

  • A.模块可能同时命中多个分组← 正确答案
  • B.priority 值大的分组优先获得模块← 正确答案
  • C.test 规则可以匹配模块路径← 正确答案
  • D.minSize 越大提取的 chunk 越多

答案:ABC。一个模块的路径可能同时满足多个分组的 test 规则,此时 priority 值大的分组胜出。minSize 的作用正好相反:阈值设得越大,会被提取的模块越少(体积不够的模块不参与提取)。就像搬家时,贵重物品箱和书籍箱都可能想“收留”一套精装书,但优先级高的那个说了算。

Q10. 影响分割粒度效果的因素有哪些?

多选题 · hard

  • A.HTTP 请求带来的额外开销← 正确答案
  • B.浏览器缓存 chunk 的能力← 正确答案
  • C.模块之间的依赖关系← 正确答案
  • D.电脑屏幕的尺寸大小

答案:ABC。分割粒度受三方面制约:请求数过多会带来 HTTP 开销,粒度合适才能最大化缓存命中,模块依赖关系决定了哪些模块必须一起加载或可以独立拆包。屏幕尺寸与代码分割完全无关,属于典型干扰项。