代码分割与动态导入完整方案:不是拆得越碎越快
包体优化最怕“看见大 bundle 就想砍”。本文从首屏预算、路由级拆分、组件级延迟加载、预取策略与过度分包的反作用入手,讲清代码分割真正的工程方法论。
代码分割与动态导入完整方案:不是拆得越碎越快
很多人一看到包体分析图,第一反应就是:
这个太大了,拆。
然后开始疯狂上动态导入,最终得到一个看似“被优化”过的项目:
- 首屏 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. 一套够用的执行流程
- 先跑 bundle 分析,识别体积最大且非首屏必需的模块
- 结合真实访问路径,找出高频与低频场景
- 先做路由级拆分,再做组件级精准优化
- 监控首屏体积、切页耗时、动态导入失败率
- 避免一次性重构全部打包策略
这个顺序的好处是:收益稳定,回滚容易。
8. 一个朴素但常被忽略的事实
很多项目的性能问题,不是“不会分包”,而是业务边界模糊导致任何页面都像一锅大杂烩。
换句话说,代码分割最后考验的不是打包器,而是架构抽象能力。
如果你的页面模块之间互相强依赖、共享状态横飞,那任何拆分都会痛苦。因为包只是边界的投影。
总结
- 代码分割不是拆得越碎越好,而是等待管理得越合理越好。
- 优先做路由级拆分,再对重型低频模块做组件级延迟加载。
- 预取和预加载要区分优先级,不要让未来需求抢占当前带宽。
- 最终效果取决于业务边界与用户路径,而不只是构建工具配置。
小明收尾一句:
好的拆包不是把系统切成碎片,而是让每一段等待都刚好发生在用户愿意等的时候。