Webpack如何抽取公共代码

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

场景设计

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

抽取 Webpack 运行时代码

直接加载的代码抽取(静态引入)

node_modules 中的代码

项目中的业务代码

按需加载的代码抽取(动态引入)

node_modules 中的代码

项目中的业务代码

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

设计场景如下:

其中带有 ~ 的表示是动态引入

module 是一个独立的功能模块,chunk 是很多 module 打包的结果,bundle 是很多 chunk 最后生成的结果

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

入口模块A模块B模块C模块D

pageAmoduleAmoduleBmoudleCmoudleD~

pageBmoduleAmoduleBmoudleC~moudleD~

pageCmoduleAmoduleB~moudleC~moudleD~

pageDmoduleA~moduleB~moudleC~moudleD~

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

模块reactvuelodashjquery

moduleAreactvuelodashjquery~

moduleBreactvuelodash~jquery~

moduleCreactvue~lodash~jquery~

moduleDreact~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';

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

node_modules 的按需加载模块在一个 chunk 中,包含 react、vue、lodash、jquery

node_modules 的直接加载的模块在一个 chunk 中,包含 react、vue、lodash

项目中按需加载的模块在一个 chunk 中,包含 moduleA、moduleB、moduleC、moduleD

项目中直接加载的模块在一个 chunk 中,包含 moduleA、moduleB、moduleC

有一个 Webpack 的运行时 chunk:runtime.bundle.js

import(‘react‘);

import(‘vue‘);

import(‘lodash‘);

import(‘jquery‘);

认识配置

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

官方给出的默认配置:

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 打包后的代码就是预期的结果。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容