本文讲解的是最近在做的一个多页面项目, 结合 webpack4 的 splitChunks 进行代码包分拆的过程
项目框架
项目有 home 和 topic 两个入口文件, 主要包括:
react,mobx,antd 作为项目的基本框架,
echarts 和 d3(画图) 是项目中部分页面用到比较大的组件库
src 下的工作的组件和代码
其他的非公共代码.
两个入口文件都用 react-loadable 做了异步加载
- import Loadable from 'react-loadable';
- ...
- const LoadableLogin = Loadable({
- loader: () => import('../../common/components/login'),
- loading: Loading,
- });
- ...
webpack 部分配置相关如下:
- module.exports = {
- ...
- mode: 'production',
- entry: { // 多入口
- home: './src/home',
- topic: './src/topic',
- },
- output: {
- path: config.build.assetsRoot,
- filename: '[name].js',
- publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
- },
- plugins: [
- new htmlWebpackPlugin({ // home 页面
- filename: 'home.html',
- template: './template.html',
- }),
- new HtmlWebpackPlugin({ // topic 页面
- filename: 'topic.html',
- template: './template.html',
- inject: true,
- }),
- ],
- ...
- }
- splitChunks
- chunks:
all: 不管文件是动态还是非动态载入, 统一将文件分离. 当页面首次载入会引入所有的包
async: 将异步加载的文件分离, 首次一般不引入, 到需要异步引入的组件才会引入.
initial: 将异步和非异步的文件分离, 如果一个文件被异步引入也被非异步引入, 那它会被打包两次 (注意和 all 区别), 用于分离页面首次需要加载的包.
minSize: 文件最小打包体积, 单位 byte, 默认 30000
比如说某个项目下有三个入口文件, a.JS 和 b.JS 和 c.JS 都是 100byte, 当我们将 minSize 设置为 301, 那么 webpack 就会将他们打包成一个包, 不会将他们拆分成为多个包.
比如说某个项目下有三个入口文件, a.JS 和 b.JS 和 c.JS 都是 100byte, 当我们将 minSize 设置为 301, 那么 webpack 就会将他们打包成一个包, 不会将他们拆分成为多个包.
automaticNameDelimiter: 连接符
假设我们生成了一个公用文件名字叫 vendor,a.JS, 和 b.JS 都依赖他, 并且我们设置的连接符是 "~" 那么, 最终生成的就是 vendor~a~b.JS
maxInitialRequests 入口点处的最大并行请求数, 默认为 3
如果我们设置为 1, 那么每个入口文件就只会打包成为一个文件
maxAsyncRequests 最大异步请求数量, 默认 5
如果我们设置为 1, 那么每个入口文件就只会打包成为一个文件
cacheGroups 定制分割包的规则
test 可以配置正则和写入 function 作为打包规则. 其他属性均可继承 splitChunks, 这里必须说一下 priority, 设置包的打包优先级, 非常重要!(后面结合实践)
minChunks
最少引入的次数
优先级关系
maxInitialRequest / maxAsyncRequests <maxSize <minSize.
实践
我们以一个最简单的配置开始, 将公共代码打包出来
- splitChunks: {
- chunks: 'all', // initial,async 和 all
- minSize: 30000, // 形成一个新代码块最小的体积
- maxAsyncRequests: 5, // 按需加载时候最大的并行请求数
- maxInitialRequests: 3, // 最大初始化请求数
- automaticNameDelimiter: '~', // 打包分割符
- name: true,
- cacheGroups: {
- vendors: {
- minChunks: 2, // 引入两次及以上被打包
- name: 'vendors', // 分离包的名字
- chunks: 'all'
- },
- }
- },
两个入口文件的公共代码被打包到 vendor 文件夹下面, 包括 echarts d3 amcharts 等一些三方包和 src 下的公共代码.
这当然不是我们想要的结果! 存在以下问题:
其实当我们进入网站, 一般第一步都是进入一个登陆页面, 需要的只是项目的基本框架代码, 例如 react,react-dom.antd 等, 我们可以用 all(或者 initial) 将它们单独打包, 作为首页必须载入的包
我们打包的公共包, 首次加载页面的时候, 只想把同步加载的加载进来, 所以需要一个同步的 Common 包
像 echarts,d3, 以及一些 src 下面一些异步加载的包, 将它们利用 async 将打包成一个独立异步加载包
我们修改 cacheGroups 为:
- cacheGroups: {
- vendors: {
- chunks: 'all',
- test: /(react|react-dom|react-dom-router|babel-polyfill|mobx)/,
- priority: 100,
- name: 'vendors',
- },
- 'async-commons': {
- chunks: 'async',
- minChunks: 2,
- name: 'async-commons',
- priority: 90,
- },
- commons: {
- chunks: 'all',
- minChunks: 2,
- name: 'commons',
- priority: 80,
- },
- }
这次 webpack 帮我们打出来的包主要有:
async-common: 是两个入口文件都异步加载的三方包和利用 react-loader 做的懒加载代码, 有 echarts,d3 等
vendors: 包括 react,react-dom,antd 等
commons: 引用超过两次的同步代码
这里说两个需要注意的地方:
注意这里我们 priority 的设置, vendors>async-commons>commons, 我们首先将 react,react-dom 等优先打包出来, 然后再打包公共部分, 如果将 vendors 的优先级设置小于两个 Common 的优先级, 那么 react,react-dom 将会打包到 common 包, 将不会再生成 vendors 包.
如果我们这里将 commons 的配置去掉, 将会生成一个 topic~home 的包, 我们配置了 async-common 提取出异步加载的公共包后, 将会默认将同步加载的公共包打包生成以 automaticNameDelimiter 连接符'~'生成的名字'topic~home'包, 内容其实和 commons 包内容完全一样,
ok! 按照我们的要求, 这样首次页面加载只会引入 vendors,commons 包, 而不会引入 async-common 包, 还是挺棒的! 追求更精致的我们, 再认真想想, 是不是还可以做一些更好的优化?
到目前为止我们打包文件的打包是这样的:
用 gzip 压缩后, 最大的 async-common 包有 391kb. 公司说最近因为一些状况, 布置到生产后速度慢的时候, 有时候只能有 20kb/s 的下载速度 ==.... 于是继续 split!
分析一下:
async-common 中包含了自己写的 src 组件和第三方组件
async-common 中比较大是 echarts,zrender(echarts 引入) 和 d3, 结合项目来说, 只有部分页面我们需要 echarts(d3 同), 所以我们可以考虑将 d3 和 echarts 这两个比较大的包提取出来, 等到某个页面需要的时候, 再让其异步加载, 这样就大大减小了 async-common 的体积了.
修改
- cacheGroups: {
- vendors: {
- chunks: 'all',
- test: /(react|react-dom|react-dom-router|babel-polyfill|mobx)/,
- priority: 100,
- name: 'vendors',
- },
- d3Venodr: {
- test: /d3/,
- priority: 100,
- name: 'd3Venodr',
- chunks: 'async'
- },
- echartsVenodr: {
- test: /(echarts|zrender)/,
- priority: 100,
- name: 'echartsVenodr',
- chunks: 'async'
- },
- 'async-commons': {
- chunks: 'async',
- minChunks: 2,
- name: 'async-commons',
- priority: 90,
- },
- commons: {
- chunks: 'all',
- minChunks: 2,
- name: 'commons',
- priority: 80,
- },
- }
当然, 每次修改后, 需要在 htmlWebpackPlugin 中配置 chunk 需要的包
- plugins: [
- new HtmlWebpackPlugin({ // home 页面
- filename: 'home.html',
- template: './template.html',
- chunks: ['vendors', 'commons', 'commons', 'home'],
- }),
- new HtmlWebpackPlugin({ // topic 页面
- filename: 'topic.html',
- template: './template.html',
- chunks: ['vendors', 'commons', 'commons', 'topic'],
- }),
- ],
后期还做了其他的拆分和优化, 大概最大的包保持在 100k 左右, 当然也不建议拆的特别小, 因为浏览器 http1 可能一次性支持 6 次下载文件, 太多可能会适得其反. 大家可以根据自己的项目做不同的拆分方法, 总而言之, 就是为了让项目更完美的在线上运行, 给用户更好的体验~
来源: https://juejin.im/post/5c00916f5188254caf186f80