代码分割与动态导入完整方案:不是拆得越碎越快

包体优化最怕“看见大 bundle 就想砍”。本文从首屏预算、路由级拆分、组件级延迟加载、预取策略与过度分包的反作用入手,讲清代码分割真正的工程方法论。

14 分钟阅读
小明

代码分割与动态导入完整方案:不是拆得越碎越快

很多人一看到包体分析图,第一反应就是:

这个太大了,拆。

然后开始疯狂上动态导入,最终得到一个看似“被优化”过的项目:

  • 首屏 bundle 变小了
  • 但页面切换变得卡顿
  • 网络请求数量暴涨
  • 组件边界越来越复杂
  • 实际体验没变好,甚至更差

原因很简单:代码分割不是拆文件,而是在管理等待。

你拆得越多,等待被切成的段也越多。如果这些等待恰好落在用户最敏感的时候,那优化就会变成重新分配痛苦。


1. 先明确:什么代码应该待在首屏里

首屏预算的核心不是“尽量少”,而是“足够完成首屏任务”。

可以留在首屏的代码通常满足这几个条件:

  • 首屏一定会用到
  • 延后加载会直接影响首屏感知
  • 体积相对可控

适合延后的代码通常是:

  • 某些路由独有模块
  • 低频交互弹层
  • 管理后台重型图表
  • 富编辑器、地图、可视化面板

一句话:

首屏只养“今天就要上场的人”,不要把全公司都带上台。


2. 代码分割的三个层级

2.1 路由级分割:收益最大,也最稳

这是最优先做的。

因为用户不可能同时打开所有页面,按路由拆分能直接避免无意义的预加载。

const UserSettingsPage = () => import('./pages/UserSettingsPage.vue')
const AdminDashboardPage = () => import('./pages/AdminDashboardPage.vue')

路由级拆分的好处在于业务边界天然清晰,团队也容易理解和维护。

2.2 组件级分割:针对重型模块精准下刀

适用于:

  • 只有用户展开时才需要的面板
  • 首屏不出现的复杂图表
  • 依赖大体积第三方库的模块
const ChartPanel = defineAsyncComponent(() => import('./ChartPanel.vue'))

但要注意,组件级分割越多,页面状态切换的等待片段就越多。需要精挑对象,别把一堆普通组件都异步化。

2.3 库级分割:谨慎对待大依赖

像编辑器、图表、地图这类库,往往值得单独拆。

但如果某个库会在多个关键页面重复被用到,过度拆分也会增加总请求成本。这里要看真实访问路径,而不是凭感觉。


3. 动态导入真正解决的,是“低概率需求别占高优先级资源”

这句话很关键。

动态导入不是为了显得高级,而是为了把低频功能从高优先级路径里移走。

例如一个后台页面:

  • 列表主体每次必看
  • 导出 Excel 每天可能点几次
  • 高级筛选弹窗不是每个用户都会开

那导出模块和高级筛选,就不该压在首屏上。


4. 预取和预加载:别把“未来可能会用”错当成“现在必须加载”

4.1 preload 是高优先级承诺

你告诉浏览器:这个资源很快就要用到,请优先安排。

这适合:

  • 下一帧就会显示的资源
  • 当前页面非常关键的后续依赖

4.2 prefetch 是低优先级押注

你告诉浏览器:用户可能稍后会用到,有空再拉。

这适合下一路由的预测性加载,但它不应该抢走当前页面的关键带宽。

很多性能事故恰好来自把大量“也许会用”的代码,用 preload 提前运进来,结果首屏用户反而被挤慢。


5. 一个更成熟的拆包原则:按用户旅程拆,不按目录拆

很多代码库按技术目录很好看,但不一定适合性能。

例如:

  • 从首页到商品详情是高概率路径
  • 从详情到支付是高价值路径
  • 从支付到投诉中心是低概率路径

那你的拆包策略,就应该优先服务前两条路径。

也就是说:

代码分割最应该听产品路径的话,而不是听文件夹话语权。


6. 过度分包的副作用,很多团队都吃过亏

6.1 请求数量暴增

HTTP/2 和 HTTP/3 确实改善了并发请求问题,但它们没有把“管理更多资源”变成零成本。

6.2 切换时抖动变多

如果每个操作都要现拉一个小包,用户会感到界面像在“临时借零件”。

6.3 错误边界更复杂

动态导入失败、弱网超时、缓存更新不一致,都会让用户看到更奇怪的问题。

因此成熟方案不是“尽量多拆”,而是:

  • 大块高频路径,提前组织好
  • 大而低频模块,延后加载
  • 中间地带,靠数据验证

7. 一套够用的执行流程

  1. 先跑 bundle 分析,识别体积最大且非首屏必需的模块
  2. 结合真实访问路径,找出高频与低频场景
  3. 先做路由级拆分,再做组件级精准优化
  4. 监控首屏体积、切页耗时、动态导入失败率
  5. 避免一次性重构全部打包策略

这个顺序的好处是:收益稳定,回滚容易。


8. 一个朴素但常被忽略的事实

很多项目的性能问题,不是“不会分包”,而是业务边界模糊导致任何页面都像一锅大杂烩。

换句话说,代码分割最后考验的不是打包器,而是架构抽象能力。

如果你的页面模块之间互相强依赖、共享状态横飞,那任何拆分都会痛苦。因为包只是边界的投影。


总结

  • 代码分割不是拆得越碎越好,而是等待管理得越合理越好。
  • 优先做路由级拆分,再对重型低频模块做组件级延迟加载。
  • 预取和预加载要区分优先级,不要让未来需求抢占当前带宽。
  • 最终效果取决于业务边界与用户路径,而不只是构建工具配置。

小明收尾一句:

好的拆包不是把系统切成碎片,而是让每一段等待都刚好发生在用户愿意等的时候。