webpack 4 Code Splitting 抽取公有代码 splitChunks配置

6801 4 年前
webpack 4 废弃了之前的不怎么好用的 CommonsChunk,取而代之的是 SplitChunks,webpack已经做了一些通用性优化,适用于多数使用者。

Code Splitting

总体来说,webpack提供了三种方法来实现代码拆分:

  1. 入口配置:多个 entry 入口起点;
  2. 抽取公有代码:使用 SplitChunks 抽取公有代码;
  3. 动态加载 :动态加载一些代码。

抽取公有代码是难点,也是平时项目中经常会用到的,在这里只讨论使用 SplitChunks 抽取公有代码。

SplitChunks

SplitChunks 是由 webpack 4 内置的 SplitChunksPlugin 插件提供的能力,可直接在 optimization 选项中配置。

SplitChunks 抽取公有代码,默认情况下,SplitChunks只影响按需加载的代码块(chunk),为什么这样呢,因为代码拆分会影响页面<script>的数量及加载顺序等,如果使用html-webpack-plugin之类的插件能很好自动地处理好页面<script>标签的问题的话,我们可以通过chunks属性来改变其只影响按需加载的行为。

webpack会根据满足以下条件来自动进行代码块分割:

  1. 新代码块可以被共享引用或这些模块都是来自node_modules文件夹里面
  2. 新代码块大于30kb(min+gziped之前的体积)
  3. 按需加载的代码块,最大数量应该小于或者等于5
  4. 初始加载的代码块,最大数量应该小于或等于3

例子一

// entry.js
import("./a");
// a.js
import "react-dom";
// ...

结果:webpack会创建一个包含react-dom的分离代码块。当import调用时候,这个代码块就会与./a代码被并行加载。

为什么会这样打包:

  • 条件1:这个代码块是从node_modules来的
  • 条件2:react-dom大于30kb
  • 条件3:按需请求的数量是1(小于5)
  • 条件4:不会影响初始代码请求数量

这样打包有什么好处呢?

对比起你的应用代码,react-dom可能不会经常变动。通过将它分割至另外一个代码块,这个代码块可以被独立缓存起来。

例子二:

// entry.js
import("./a");
import("./b");
// a.js
import "./helpers"; // helpers is 40kb in size
// ...
// b.js
import "./helpers";
import "./more-helpers"; // more-helpers is also 40kb in size
// ...

结果:webpack会创建一个包含./helpers的独立代码块,其他模块会依赖于它。在import被调用时候,这个代码块会跟原始的代码并行加载。

为什么会这样打包:

  • 条件1:这个代码块会被两个动态导入(import)调用依赖(指的是a.js和b.js)
  • 条件2:helpers体积大于30kb
  • 条件3:按需请求的数量是2(小于5)
  • 条件4:不会影响初始代码请求数量
  • 这样打包有什么好处呢?

将helpers代码放在每一个依赖的块里,可能就意味着,用户重复会下载它两次。通过用一个独立的代码块分割,它只需要被下载一次。实际上,这只是一种折衷方案,因为我们为此需要付出额外的一次请求的代价。这就是为什么默认webpack将最小代码块分割体积设置成30kb(译注:太小体积的代码块被分割,可能还会因为额外的请求,拖慢加载性能)。

通过optimizations.splitChunks.chunks: "all",上面的策略也可以应用到初始代码块上(inital chunks)。代码块也会被多个入口共享&按需加载。

如果想要自己控制拆分的功能,webpack提供很多选项来满足你的需求

配置

SplitChunks 其默认配置如下:

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

其选项说明如下:

  • chunks:表示从哪些chunks里面抽取代码,除了三个可选字符串值 initialasyncall 之外,还可以通过函数来过滤所需的 chunks
  • minSize:表示抽取出来的文件在压缩前的最小大小,默认为 30000;
  • maxSize:表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
  • minChunks:表示被引用次数,默认为1;
  • maxAsyncRequests:最大的按需(异步)加载次数,默认为 5;
  • maxInitialRequests:最大的初始化加载次数,默认为 3;
  • automaticNameDelimiter:抽取出来的文件的自动生成名字的分割符,默认为 ~;
  • name:抽取出来文件的名字,默认为 true,表示自动生成文件名;
  • cacheGroups: 缓存组。

cacheGroups

上面那些 SplitChunks 的参数,其实都可以不用管,cacheGroups 才是我们配置的关键。它可以继承/覆盖上面 splitChunks 中所有的参数值,除此之外还额外提供了三个配置,分别为:test, priorityreuseExistingChunk

默认情况是会将所有来自node_modules的模块分配到一个叫vendors的缓存组;所有重复引用至少两次的代码,会被分配到default的缓存组。

可以通过optimization.splitChunks.cacheGroups.default: false禁用default缓存组。

default缓存组的优先级(priotity)是负数,因此所有自定义缓存组都可以有比它更高优先级(更高优先级的缓存组可以优先打包所选择的模块)(默认自定义缓存组优先级为0)

  • test: 表示要过滤出的模块,默认为所有的模块,可匹配模块路径或 chunk 名字,当匹配的是 chunk 名字的时候,其里面的所有模块都会选中;
  • priority:表示抽取权重,数字越大表示优先级越高。因为一个模块可能会满足多个 cacheGroups 的条件,那么抽取到哪个就由权重最高的说了算;
  • reuseExistingChunk:表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。

当然,某一模块可能有被分配到多个缓存组的情况,这时,优化策略会将此模块分配到高优先级别(priority)的缓存组,或者会分配至可以形成更大体积代码块的组里。

配置实战

一般来说我们常用的配置都是 common + page 的形式。而 page 在 entry 入口的时候就已经配置好了。那么现在就只剩下 common 的处理。

cacheGroups: {
  common: {
    test: /[\\/]node_modules[\\/]/,
    name: 'common',
    chunks: 'initial',
    priority: 2,
    minChunks: 2,
  },
}

上面意思是:把所有从node_modules引入超过 1 次的模块抽取到一个名为common的代码块(chunk)

cacheGroups: {
  common: {
    name: 'common',
    chunks: 'initial',
    priority: 2,
    minChunks: 2,
  },
}

上面的意思是,把所有引入超过 1 次的模块抽取为 common代码块。

cacheGroups: {
  reactBase: {
    name: 'reactBase',
    test: (module) => {
        return /react|redux|prop-types/.test(module.context);
    },
    chunks: 'initial',
    priority: 10,
  },
  common: {
    name: 'common',
    chunks: 'initial',
    priority: 2,
    minChunks: 2,
  },
}

上面的意思是,把所有匹配test且引入超过 1 次的模块抽取为 reactBase代码块。

splitChunks: {
	cacheGroups: {
		commons: {
			test: /[\\/]node_modules[\\/]/,
			name: "vendors",
			chunks: "all"
		}
	}
}

把整个应用所有来自node_modules的代码。抽取为 vendors 代码块。

CSS 配置

同样对于通过 MiniCssExtractPlugin 生成的 CSS 文件也可以通过 SplitChunks 来进行抽取公有样式等。

如下表示将所有 CSS 文件打包为一个(注意将权重设置为最高,不然可能其他的 cacheGroups 会提前打包一部分样式文件):

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true,
          priority: 20,
        }
      }
    }
  }
}
© 2018邮箱:11407215#qq.comGitHub沪ICP备12039518号-6