webpack4之splitChunks拆拆拆

本文讲解的是最近在做的一个多页面项目,结合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次下载文件,太多可能会适得其反。大家可以根据自己的项目做不同的拆分方法,总而言之,就是为了让项目更完美的在线上运行,给用户更好的体验~

发表评论

电子邮件地址不会被公开。 必填项已用*标注