文章目录
- [一、为什么 用vite进行分包后,可以通过浏览器强制缓存提高性能?](#一、为什么 用vite进行分包后,可以通过浏览器强制缓存提高性能?)
-
- [1、 什么是浏览器强制缓存?](#1、 什么是浏览器强制缓存?)
-
- [1.1 协商缓存(需要举手提问 🙋♂️)](#1.1 协商缓存(需要举手提问 🙋♂️))
- [1.2 强制缓存(霸道总裁,直接拿来用 ⚡)](#1.2 强制缓存(霸道总裁,直接拿来用 ⚡))
- [2、 强缓存这么好,那它有什么致命缺点?](#2、 强缓存这么好,那它有什么致命缺点?)
- [3、 为什么用 Vite 分包后,能通过强缓存大幅提高性能?](#3、 为什么用 Vite 分包后,能通过强缓存大幅提高性能?)
-
- [场景 A:不分包(所有的东西揉成一个 `index-[hash].js`)](#场景 A:不分包(所有的东西揉成一个
index-[hash].js)) - [场景 B:用 Vite 分包(利用 `manualChunks` 拆分)](#场景 B:用 Vite 分包(利用
manualChunks拆分))
- [场景 A:不分包(所有的东西揉成一个 `index-[hash].js`)](#场景 A:不分包(所有的东西揉成一个
- [二、路由懒加载进行的分包与 vite进行的分包有什么不同?](#二、路由懒加载进行的分包与 vite进行的分包有什么不同?)
-
- [1. 什么是路由懒加载?](#1. 什么是路由懒加载?)
- [2. 什么是 Vite/Rollup 的 manualChunks 分包?](#2. 什么是 Vite/Rollup 的 manualChunks 分包?)
- [3. 两者有什么不同?(核心对比)](#3. 两者有什么不同?(核心对比))
- [4. 它们是如何协同工作的?(1 + 1 绝对大于 2)](#4. 它们是如何协同工作的?(1 + 1 绝对大于 2))
一、为什么 用vite进行分包后,可以通过浏览器强制缓存提高性能?
要想完全理解为什么 Vite 分包能利用强制缓存大幅提升性能,我们得先拆开来看:先明白浏览器缓存的"游戏规则",再看 Vite 是怎么利用这个规则在生产环境"作弊"的。
1、 什么是浏览器强制缓存?
浏览器缓存分为两种:协商缓存 和强制缓存 。它们的区别就在于要不要去问服务器。
1.1 协商缓存(需要举手提问 🙋♂️)
浏览器每次用这个文件前,都要发个请求问服务器:"服务器大哥,我本地有这个文件,它过期了吗?" 服务器对比一下,如果没变,返回一个 304 Not Modified,浏览器再读取本地缓存。
- 缺点 :虽然传输体积变小了,但依然发出了一次 HTTP 网络请求。只要有网络请求,就会受到网络延迟(RTT)的限制。
1.2 强制缓存(霸道总裁,直接拿来用 ⚡)
服务器在第一次返回文件时,在响应头(Response Headers)里加上一个标记,比如:
Cache-Control: max-age=31536000(告诉浏览器:这文件一年内都不会变)。
当用户第二次访问或者刷新页面时,浏览器一看这个标记:
"哦,还在保质期内呢,我连网络请求都不发了,直接从本地内存(Memory Cache)或硬盘(Disk Cache)里把文件秒秒钟拿出来。"
- 状态码 :在开发者工具的 Network 面板里,你会看到状态码是
200 OK (from disk cache)。 - 收益 :网络请求数直接变成 0 ,耗时接近 0ms。这是最极致的网络性能优化。
2、 强缓存这么好,那它有什么致命缺点?
强缓存唯一的缺点就是:太死板 。
如果服务器上的代码更新了,但是浏览器以为文件还在"保质期"内,它就绝对不会向服务器发请求,而是继续使用本地的老代码。这就导致了用户打不开新功能、或者页面直接报错(俗称"发版后缓存不更新")。
现代前端(Vite/Webpack)解决这个问题的终极武器就是:内容哈希(Content Hash)。
当 Vite 打包时,会根据文件的内容计算出一个唯一的哈希值作为文件名,比如 index-b87a2c.js。
- 只要你改了代码,打包出来的文件名就会变成
index-f49e1a.js。 - 因为 HTML 页面是绝对不走强缓存 的(HTML 通常配置
Cache-Control: no-cache),浏览器每次都会拉取最新的 HTML。最新的 HTML 里引用了新的 JS 文件名,浏览器就会去下载新文件;而没变的文件名,浏览器继续走强缓存。
3、 为什么用 Vite 分包后,能通过强缓存大幅提高性能?
这就聊到你问的核心了。如果不分包,强缓存的威力会被严重削弱。
场景 A:不分包(所有的东西揉成一个 index-[hash].js)
你把 Vue 核心库、Pinia、Axios、Echarts 还有你写的几百个业务组件,全部打进了一个巨大的 index-v1.js 里。这个包很大(比如 2MB),你给它设置了强缓存。
- 问题来了 :第二天,你发现首页有个错别字,你把
"欢荧光临"改成了"欢迎光临"。 - 打包结果 :由于内容变了,整个 2MB 的大包文件名瞬间变成了
index-v2.js。 - 用户代价 :用户重新访问时,浏览器发现文件名变了,强缓存全部失效 。用户不得不为了你改动的一个错别字,把原本完全没变的 Vue、Echarts 等第三方大库重新下载了一遍! 用户的首屏加载又变慢了。
场景 B:用 Vite 分包(利用 manualChunks 拆分)
你通过 Vite 配置,把包拆开了:
bash
dist/assets/
├── vendor-d7c3a1.js # 包含 vue, vue-router, pinia, axios (150kb)
├── echarts-8f4e2a.js # 包含完整的 echarts 图表库 (500kb)
└── index-b87a2c.js # 纯粹是你自己写的业务代码 (50kb)
这时候,你同样给 assets 目录下的所有 JS 文件在服务器(如 Nginx)上配置了强制缓存。
同样是改了一个首页错别字,你再次打包:
vendor里的代码没变,打包出来依然叫vendor-d7c3a1.js。echarts里的代码没变,打包出来依然叫echarts-8f4e2a.js。- 你的业务代码变了,打包出来变成了
index-f49e1a.js。
此时用户重新进站,神奇的事情发生了:
浏览器请求最新的 HTML,发现里面引用了这三个文件。
vendor-d7c3a1.js匹配本地缓存 ➡️ 命中强缓存,0ms 搞定!echarts-8f4e2a.js匹配本地缓存 ➡️ 命中强缓存,0ms 搞定!index-f49e1a.js找不到,发请求下载 ➡️ 只下载了 50kb 的核心业务包。
最终结论 :Vite 分包的本质,是把"轻易不会变动的重型资产(第三方库)"与"天天都在变动的轻量资产(业务代码)"在物理上隔离开 。这样就能保证那些体积最大、下载最慢的第三方库,可以在用户本地的浏览器里永久命中强缓存,只有真正更新了的业务小包才走网络下载。首屏速度自然迎来了质的飞跃。
二、路由懒加载进行的分包与 vite进行的分包有什么不同?
路由懒加载 和 rollupOptions.manualChunks(配置分包) 虽然最终结果都是"把代码拆成了多个 JS 小包",但它们的触发时机、控制维度和核心目的截然不同。
我们可以把它们比作"业务层面的按需索取"与"物理层面的资产归类"。
1. 什么是路由懒加载?
简单来说,路由懒加载是业务逻辑驱动的代码拆分(Code Splitting)。
如果不用懒加载(全量打包):
当你访问项目的登录页 http://localhost/login 时,浏览器会把主页、个人中心、后台数据大屏等所有页面的 JS 代码一次性全部下载下来。
结果: 首屏巨慢,用户明明只看了一个登录页,却要为整个系统的代码买单。
用了懒加载:
javascript
const Dashboard = () => import('./views/Dashboard.vue')
当打包工具(Vite/Rollup)看到 import() 这种动态导入 语法时,它会敏锐地意识到:"哦!这个组件不需要一上来就加载。那我单独把它切出来,打包成一个独立的 dashboard-[hash].js 文件吧。"
- 什么时候下载? 只有当用户真正点击了"控制台"路由,引发路由跳转时,浏览器才会通过网络去请求下载这个
dashboard-[hash].js。
2. 什么是 Vite/Rollup 的 manualChunks 分包?
配置分包(如 manualChunks)是物理/依赖层面驱动的包体积合并与归类。
它是纯粹的工程化手段,不管你业务怎么跳转,它只关心的文件依赖关系 和缓存命中率。
为什么需要它?
如果你只配置了路由懒加载,那么每个路由页面里引用的第三方库(比如 lodash、echarts 等)可能就会被散落打包到各个路由小包里;或者有些公用组件被重复打包。
通过 manualChunks,你可以硬性规定:
javascript
manualChunks(id) {
if (id.includes('node_modules')) {
// 把所有来自 node_modules 的第三方依赖,单独打包成一个叫 vendor 的大包
return 'vendor';
}
}
结果: 把业务代码和第三方生态彻底剥离。因为
node_modules里的依赖基本不会变,这样打包出来的vendor.js就可以完美触发浏览器的强缓存(304 或 Cache-Control) 。即使你天天改业务代码发布新版本,用户也只需要重新下载小巧的业务包,vendor.js直接走本地缓存,速度飞快。
3. 两者有什么不同?(核心对比)
为了更直观,我们通过一张表来看看它们在编译和运行时的本质区别:
| 维度 | 路由懒加载(动态导入 import()) |
Vite/Rollup 配置分包 (manualChunks) |
|---|---|---|
| 决定权在谁 | 开发者(业务层):由你的路由代码怎么写决定。 | 打包工具(构建层):由你的打包配置文件决定。 |
| 主要目的 | 减少首屏加载体积,不下载当前不需要的页面。 | 优化浏览器缓存,把变动频繁的业务码和稳定的第三方库分开。 |
| 工作阶段 | **运行时(Runtime)**按需异步加载。 | **构建时(Build time)**做物理文件分类。 |
| 典型代表 | () => import('./Detail.vue') |
将 axios、vue 统一打包进 vendor.js。 |
4. 它们是如何协同工作的?(1 + 1 绝对大于 2)
在实际的 Vite 项目中,我们不是二选一,而是两者结合使用。
设想一个标准的 Vue + Vite 项目打包后的文件结构:
bash
dist/assets/
├── index-AX432j.js # 主包(包含 Vue 核心、main.js、全局状态,首屏必须)
├── vendor-BC892k.js # 由 manualChunks 拆出来的第三方库大包(走强缓存,首屏必须)
├── Home-CD123f.js # 路由懒加载拆出的首页(首屏必须)
├── About-EF456g.js # 路由懒加载拆出的关于页(用户点到 About 时才下载)
└── Dashboard-GH789h.js # 路由懒加载拆出的后台页(用户点到 Dashboard 时才下载)
完美的加载流程:
- 用户首次访问首页。
- 浏览器只下载了
index.js、vendor.js、和首屏对应的Home.js。 - 页面瞬间加载完成(首屏速度优化成功 ⚡)。
- 用户点击进入"关于我们"页面,浏览器默默发了一个网络请求,拉取了
About.js(路由懒加载生效 ⚡)。 - 第二天,你修改了首页的一个文案,重新打包发布。用户再次访问,浏览器发现
vendor.js没变,直接读取本地缓存;只下载了变动的一点点业务包(manualChunks缓存优化成功 ⚡)。