跳到主要内容

代码分割

  • 更好地按需加载

  • 更好的利用缓存

  • 提高页面加载性能

在生产环境下,为了提高页面加载性能,构建工具一般将项目的代码打包(bundle)到一起,这样上线之后只需要请求少量的 JS 文件,大大减少 HTTP 请求

  • 但随着前端工程的越来越复杂,单份的打包产物体积越来越庞大,会出现一系列应用加载性能问题,而代码分割可以很好地解决它们

bundle、chunk、vendor 构建领域的专业概念

  • bundle 指的是整体的打包产物,包含 JS 和各种静态资源。
  • chunk指的是打包后的 JS 文件,是 bundle 的子集。
  • vendor是指第三方包的打包产物,是一种特殊的 chunk

代码分割code splitting解决了什么问题?​

在传统的单 chunk 打包模式下,当项目代码越来越庞大,最后会导致浏览器下载一个巨大的文件,从页面加载性能的角度来说,主要会导致两个问题:

  • 无法做到按需加载,即使是当前页面不需要的代码也会进行加载。
  • 线上缓存复用率极低,改动一行代码即可导致整个 bundle 产物缓存失效。

Code Splitting可以将按需加载的代码拆分出单独的 chunk,避免了冗余的加载过程,使页面性能得到提升。

Code Splitting可以更好地利用缓存,让浏览器复用本地的强缓存,大大提升线上应用的加载性能

打包完产物体积太大,怎么拆包?​

Vite 默认拆包​

  • Vite 默认拆包的优势在于实现了 CSS 代码分割与业务代码、第三方库代码、动态 import 模块代码三者的分离
  • 缺点也比较直观,第三方库的打包产物容易变得比较臃肿,vendor.js的体积大小会随着项目复杂程度变得越来越大
    • 显然是有进一步拆包的优化空间的,这个时候我们就需要用到 Rollup 中的拆包 API ——manualChunks

这个默认拆包策略是以插件的方式存在的,由于 2.9之后就不使用该策略了,所以如果还要用的话就得自己导入插件进行使用:

javascript
import { splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [splitVendorChunkPlugin()]
})

可以通过配置 build.rollupOptions.output.manualChunks 来自定义 chunk 分割策略(查看 Rollup 相应文档)

从 Vite 2.9 起,manualChunks 默认情况下不再被更改。你可以通过在配置文件中添加 splitVendorChunkPlugin 来继续使用 “分割 Vendor Chunk” 策略

  • splitVendorChunkPlugin() 插件的主要作用是在构建过程中输出关于拆分情况的警告信息,并可以通过配置的方式对拆分行为进行控制

实现自定义拆包​

vite在生产环境是通过rollup来打包, 提供了manualChunks来实现自定义拆包策略

manualChunks在对象格式的配置中,key代表 chunk 的名称,value为一个字符串数组,每一项为第三方包的包名

自定义拆包之后,原来的 vendor 大文件被拆分成了我们手动指定的几个小 chunk

  • 当第三方包更新的时候,也只会更新其中一个 chunk 的
  • 而不会全量更新,从而提高了对第三方模块的缓存
js
// vite.config.ts
{
build: {
rollupOptions: {
output: {
// manualChunks 配置
manualChunks: {
// 将 React 相关库打包成单独的 chunk 中
'react-vendor': ['react', 'react-dom'],
// 将 Lodash 库的代码单独打包
'lodash': ['lodash-es'],
// 将组件库的代码打包
'library': ['antd', '@arco-design/web-react'],
},
},
}
},
}

拆包会出现的问题​

  • 容易导致循环引用

如何解决拆包导致循环引用的问题?​

比如将react包单独拆包

  • 确定 react 相关包的入口路径
  • 在 manualChunks 中拿到模块的详细信息,向上追溯它的引用者,如果命中 react 的路径,则将模块放到 react-vendor中

vite集成拆包插件​

  • vite-plugin-chunk-split 是一个 Vite 插件,支持多种拆包策略,可避免手动操作manualChunks潜在的循环依赖问题
ts
// vite.config.ts
import { chunkSplitPlugin } from 'vite-plugin-chunk-split';

export default {
chunkSplitPlugin({
// 指定拆包策略
customSplitting: {
// 1. 支持填包名。`react` 和 `react-dom` 会被打包到一个名为`render-vendor`的 chunk 里面(包括它们的依赖,如 object-assign)
'react-vendor': ['react', 'react-dom'],
// 2. 支持填正则表达式。src 中 components 和 utils 下的所有文件被会被打包为`component-util`的 chunk 中
'components-util': [/src\/components/, /src\/utils/]
}
})
}