Vite 的分包策略
本篇参考了 深入浅出Vite:Vite打包与拆分_牛客网,偶然看见,觉得写得很好,自己也整理吸收一番。
背景
随着前端项目的日益庞大和复杂,模块化是实现解耦的手段,开发阶段友好而生产环境不友好,所以我们会通过构建工具把代码给打包成 bundle,减少 JS 请求,优化网页速度。
Vite 在开发阶段就是用 ESbuild,最大化利用浏览器原生支持 ESM 的能力;但在生产阶段就采用 Rollup 进行项目的打包。目前,Vite 团队也在开发实验性的 Roll-down,旨在统一 ESbuild 和 Rollup。
下面我们还是围绕 vite 去说。
CodeSplitting
几个概念:
- bundle:指的是整体的打包产物,包含 JS 和各种静态资源。
- chunk:指的是打包后的 JS 文件,是 bundle 的子集。
- vendor:是指第三方包的打包产物,是一种特殊的 chunk。
传统的全量打包进一个 bundles 的模式:
- 单体文件太大,页面加载性能拉跨。
- 构建工具一般会根据产物的内容生成哈希值,一旦内容变化就会导致强缓存失效,所以单 chunk 打包模式下的缓存命中率极低。
要解决这个问题,主要就是几种策略:
- 1、区分 Initital Chunk 和 Async Chunk,首屏只加载必要资源,比如路由组件就可以懒加载。
- 2、最大化利用缓存,把一些不变的代码(第三方库、公共组件)单独分包,最大化利用浏览器缓存。
Vite 默认的分包策略
在生产环境下 Vite 完全利用 Rollup 进行构建,因此拆包也是基于 Rollup 来完成的, Rollup 本身是一个专注 JS 库打包的工具,对应用构建的能力还尚为欠缺,Vite 正好是补足了 Rollup 应用构建的能力,在拆包能力这一块的扩展就是很好的体现。
为什么说Rollup专注于 JS 库打包 ❓
Rollup 适合用来打包一些库,比如 UI 组件库、NPM 上的工具包;因为 Rollup 打包后会生成一个干净的、扁平的、兼容多种环境的文件:
dist/
├── my-utils.esm.js # 用于 ESM 环境
├── my-utils.cjs.js # 用于 CommonJS 环境 (Node)
└── my-utils.umd.js # 用于 <script> 直接引用 (浏览器)而应用的打包,是为了在浏览器中能够运行整个应用,包括各种资源依赖的处理、代码的分割、懒加载、缓存等能力、HMR、dev server 等功能支持。
所以说 Vite 在 Rollup 基础上增加了构建能力,处理了代码分割、动态 import 支 持、资源处理(CSS/图片)、插件系统扩展等。
为什么 Vite 要基于 Rollup 去做呢❓
Vite 整体是完全拥抱 ESM,Rollup 天然支持 ESM ,Tree-shaking 能力非常优秀,打包结果 runtime 代码量小;而 webpack 需要 runtime 辅助、为了兼容会注入 __webpack_require__ 等辅助函数,打包结果相较臃肿一些。
默认情况下 Vite 的分包表现
Vite 实现了自动 CSS 代码分割的能力,即实现一个 chunk 对应一个 css 文件,而按需加载的 chunk,也对应单独的一份 js 和 css 文件,这样做也能提升 CSS 文件的缓存复用率。
同时, Vite 基于 Rollup 的manualChunksAPI 实现了应用拆包的策略:
- 对于 Initital Chunk 而言,默认打包策略简单粗暴,将所有的 js 代码全部打包到 index.js 中。
- 对于 Async Chunk 而言 ,动态 import 的代码会被拆分成单独的 chunk。
Vite 默认拆包的优势在于实现了 CSS 代码分割与业务代码、动态 import 模块代码的分离,但缺点也比较直观,第三方库的打包产物容易变得比较臃肿。
自定义拆包策略
我之前参与的项目中,直接默认打包的结果如下: 首屏引入的文件恰恰就是这个4MB 的文件
看了一下,这个文件里 vue 的代码和 mapbox 的代码很多,我们尝试进行分包,把vue 分出来。
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('vue')) {
return 'vendor_vue';
}
// 其他第三方库打到 vendor
return 'vendor';
}
}
}
}
}
})结果如下,可以看到我们第三方库是体量最大的,vue 及其相关生态的代码达到了1.7 MB
再尝试把 mapbox 和 echart 拆一下,可以看到,我们的第三方库被拆分了,由此我们再配合着代码的按需引入,可以把首屏的资源需求降到最低。 