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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容