抽取公共代码

Posted negivup

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抽取公共代码相关的知识,希望对你有一定的参考价值。

如果要彻底明白 Webpack V4 版本如何抽取公共代码,就要设计一个场景来支持抽取公共代码的多种形式,能够从代码运行的结果中查看实际的效果,从效果中深入理解各个参数的作用。

场景设计

在设计场景之前,首先要明白公共代码抽取常见的几种情况:

  1. 抽取 Webpack 运行时代码
  2. 直接加载的代码抽取(静态引入)
    • node_modules 中的代码
    • 项目中的业务代码
  3. 按需加载的代码抽取(动态引入)
    • node_modules 中的代码
    • 项目中的业务代码

经过分析会发现,现在常见的场景就五种,设计一个应用场景包含这五种情况,就可以很好的理解 Webpack V4 如何抽取公共代码了。

设计场景如下:

其中带有 ~ 的表示是动态引入
module 是一个独立的功能模块,chunk 是很多 module 打包的结果,bundle 是很多 chunk 最后生成的结果

打包入口动态和静态导入的模块:

入口 模块A 模块B 模块C 模块D
pageA moduleA moduleB moudleC moudleD~
pageB moduleA moduleB moudleC~ moudleD~
pageC moduleA moduleB~ moudleC~ moudleD~
pageD moduleA~ moduleB~ moudleC~ moudleD~

模块中动态和静态导入的 node_modules 中的模块:

模块 react vue lodash jquery
moduleA react vue lodash jquery~
moduleB react vue lodash~ jquery~
moduleC react vue~ lodash~ jquery~
moduleD react~ vue~ lodash~ jquery~

入口文件中的代码(以 pageA.js 为例,其他的入口文件中的代码类似):

import './module/moduleA';
import './module/moduleB';
import './module/moduleC';
import('./module/moduleD');

console.log('pageA');

模块中的代码(以 moduleA.js 为例,其他的模块文件中的代码类似):

import 'react';
import 'vue';
import 'lodash';
import('jquery');

console.log('moduleA');

export default 'moduleA';

最终打包之后的预期效果:

  1. node_modules 的按需加载模块在一个 chunk 中,包含 react、vue、lodash、jquery
  2. node_modules 的直接加载的模块在一个 chunk 中,包含 react、vue、lodash
  3. 项目中按需加载的模块在一个 chunk 中,包含 moduleA、moduleB、moduleC、moduleD
  4. 项目中直接加载的模块在一个 chunk 中,包含 moduleA、moduleB、moduleC
  5. 有一个 Webpack 的运行时 chunk:runtime.bundle.js

import(‘react‘);
import(‘vue‘);
import(‘lodash‘);
import(‘jquery‘);

认识配置

在 Webpack V4 中已经提供了抽取公共代码的默认配置。

官方给出的默认配置:

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

各个选项的说明:

  • optimization.automaticNameDelimiter:指定生成名字中的分隔符,Webpack 将使用 chunk 的名字和 chunk 的来源,如 vendors~main.js
  • optimization.name:分割块的名称,提供 true 会自动生成基于 chunk 和缓存组键的名称
  • optimization.maxAsyncRequests:按需加载时,并行请求的最大数量,默认是 5
  • optimization.maxInitialRequests:一个入口最大的并行请求数,默认是 3
  • optimization.minChunks:在分割之前,这个代码块最小应该被引用的次数,默认是 1
  • optimization.chunks需要关心的属性,一般可以指定三种形式 all(全部的 chunk,包含所有类型的 chunk)、async(按需加载的 chunk) 和 initial(初始的 chunk)
  • optimization.minSize需要关心的属性,一个新的 chunk 的最小体积,默认是 30000,即 30K
  • optimization.cacheGroups:该属性是一个对象,上面的属性可以在该对象中使用,如果在该对象中不使用,则默认继承上面的属性值
    • cacheGroups:是一个对象,下面的所有属性都是自定义的,一般是打包后的模块名称
    • cacheGroups.name需要关心的属性,抽取公共代码的 chunk 名字
    • cacheGroups.priority需要关心的属性,抽取公共代码的优先级,数字越大,优先级越高
    • cacheGroups.reuseExistingChunk需要关心的属性,是否重用 chunk,如果当前块包含已经从主bundle中分离出来的模块,那么它将被重用,而不是生成一个新的模块,一般设置为 true
    • cacheGroups.test需要关心的属性,匹配规则,一般使用正则表达式来匹配,如 /[\/]node_modules[\/]/ 是匹配 node_modules 中的模块

下面的例子中主要是演示上面指出的需要关心的属性。

准备工作

为了能够更好的查看程序执行的效果,需要做以下几个准备工作。

1.创建 package.json 并安装相关依赖和指定运行的 scripts

  "scripts": {
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "webpack": "^4.17.1",
    "webpack-cli": "^3.1.0"
  },
  "dependencies": {
    "jquery": "^3.3.1",
    "lodash": "^4.17.10",
    "react": "^16.4.2",
    "vue": "^2.5.17"
  }

2.创建 Webpack 的配置文件 webpack.config.js,并输入公共的配置内容

const { resolve } = require('path');

const webpackConfig = {};

// 入口
webpackConfig.entry = {
  pageA: './src/pageA.js',
  pageB: './src/pageB.js',
  pageC: './src/pageC.js',
  pageD: './src/pageD.js'
};

// 出口
webpackConfig.output = {
  path: resolve(__dirname, './dist'),
  filename: '[name].bundle.js',
  // chunkFilename 的作用就是设置 chunk 的名字,抽取公共代码的时候有用
  chunkFilename: "[id].[name].chunk.js"
};

// 优化相关
webpackConfig.optimization = {
  splitChunks: {
    automaticNameDelimiter: '~',
    name: true,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    minChunks: 1,
    minSize: 0, // 这里自定义不管文件有多小,都要抽取公共代码
    cacheGroups: {}
  }
};

module.exports = webpackConfig;

3.执行命令

$ yarn build

查看未抽取公共代码的效果

上面准备工作中配置的 webpack.config.js 是没有经过抽取公共代码的,执行命令之后,会发现控制台中输出下面的结果:

          Asset       Size                   Chunks             Chunk Names
   6.6.chunk.js  284 bytes                        6  [emitted]
   0.0.chunk.js  219 bytes                        0  [emitted]
   2.2.chunk.js   7.21 KiB                        2  [emitted]
   3.3.chunk.js   69.3 KiB                        3  [emitted]
   4.4.chunk.js   64.3 KiB                        4  [emitted]
   5.5.chunk.js   85.3 KiB                        5  [emitted]
   1.1.chunk.js  311 bytes                        1  [emitted]
   7.7.chunk.js  217 bytes                        7  [emitted]
pageA.bundle.js    143 KiB  8, 0, 2, 3, 4, 6, 7, 12  [emitted]  pageA
pageB.bundle.js    143 KiB     9, 0, 2, 3, 4, 7, 12  [emitted]  pageB
pageC.bundle.js    143 KiB       10, 0, 2, 3, 4, 12  [emitted]  pageC
pageD.bundle.js   2.22 KiB                       11  [emitted]  pageD
 12.12.chunk.js  190 bytes                       12  [emitted]

从上面的结果中可以看出,没有抽取公共代码,下面就逐步优化,来抽取公共代码。

抽取 Webpack 运行时代码

在抽取 Webpack 运行时代码的时候,需要指定 runtimeChunk 属性:

  • true:表示每个入口都抽取一个公共的运行时代码
  • ‘single‘:表示多个入口抽取一个公共的运行时代码,一般使用这种方式
// 抽取运行时代码
webpackConfig.optimization.runtimeChunk = 'single';

执行命令之后,查看控制台:

            Asset       Size                   Chunks             Chunk Names
     7.7.chunk.js  284 bytes                        7  [emitted]
     0.0.chunk.js  219 bytes                        0  [emitted]
     2.2.chunk.js   7.21 KiB                        2  [emitted]
     3.3.chunk.js   69.3 KiB                        3  [emitted]
     4.4.chunk.js   64.3 KiB                        4  [emitted]
     5.5.chunk.js   85.3 KiB                        5  [emitted]
runtime.bundle.js   2.16 KiB                        6  [emitted]  runtime
     1.1.chunk.js  311 bytes                        1  [emitted]
     8.8.chunk.js  217 bytes                        8  [emitted]
 9.pageA.chunk.js    141 KiB  9, 0, 2, 3, 4, 7, 8, 13  [emitted]  pageA
10.pageB.chunk.js    141 KiB    10, 0, 2, 3, 4, 8, 13  [emitted]  pageB
11.pageC.chunk.js    141 KiB       11, 0, 2, 3, 4, 13  [emitted]  pageC
12.pageD.chunk.js  328 bytes                       12  [emitted]  pageD
   13.13.chunk.js  190 bytes                       13  [emitted]

这时,会发现多了一个 runtime.bundle.js 文件,这个文件就是 Webpack 的运行时代码。

抽取公共代码

下面抽取的公共代码就只包含了四部分:项目中的静态导入、项目中的动态导入、node_modules 中的静态导入、node_modules 中的动态导入。

这四种情况,自己划分了一下优先级,可以在代码中看出来。

node_modules 中的直接加载的代码:

webpackConfig.optimization.splitChunks.cacheGroups.nodeSrc = {
  name: 'nodeSrc',
  reuseExistingChunk: true,
  test: /[\/]node_modules[\/]/,
  chunks: 'initial', // 指定为初始的 chunk
  priority: 3
};

node_modules 中的按需加载的代码:

webpackConfig.optimization.splitChunks.cacheGroups.nodeAsync = {
  name: 'nodeAsync',
  reuseExistingChunk: true,
  test: /[\/]node_modules[\/]/,
  chunks: 'async', // 指定为按需加载的 chunk
  priority: 2
};

项目中的直接加载的代码:

webpackConfig.optimization.splitChunks.cacheGroups.commonSrc = {
  name: 'commonSrc',
  reuseExistingChunk: true,
  chunks: 'initial', // 指定为初始的 chunk
  priority: 1
};

项目中的按需加载的代码:

webpackConfig.optimization.splitChunks.cacheGroups.commonAsync = {
  name: 'commonAsync',
  reuseExistingChunk: true,
  chunks: 'async', // 指定为按需加载的 chunk
  priority: 0
};

执行命令之后,可以看到打包之后的最终效果:

                 Asset       Size  Chunks             Chunk Names
0.commonAsync.chunk.js  946 bytes       0  [emitted]  commonAsync
  1.nodeAsync.chunk.js    226 KiB    1, 4  [emitted]  nodeAsync
  2.commonSrc.chunk.js   1.22 KiB       2  [emitted]  commonSrc
     runtime.bundle.js   2.19 KiB       3  [emitted]  runtime
    4.nodeSrc.chunk.js    141 KiB       4  [emitted]  nodeSrc
      5.pageA.chunk.js   74 bytes       5  [emitted]  pageA
      6.pageB.chunk.js   74 bytes       6  [emitted]  pageB
      7.pageC.chunk.js   74 bytes       7  [emitted]  pageC
      8.pageD.chunk.js   72 bytes       8  [emitted]  pageD

如果感觉这种带数字的名字查看不直观,就修改 output 中的 chunkFilename 的值为 "[name].chunk.js"

最终经过 Webpack 打包后的代码就是预期的结果。

参考资料

以上是关于抽取公共代码的主要内容,如果未能解决你的问题,请参考以下文章

使用thymeleaf模板引擎抽取公共页面

抽取公共代码

thymeleaf引入公共页面的某个片段

thymeleaf

Thymeleaf静态资源引入方式及公共页面代码抽取

谈谈CommonsChunkPlugin抽取公共模块