之前写的《webpack性能优化(0):webpack性能优化概况-优化构建速度》、《webpack性能优化(1):分隔/分包/异步加载+组件与路由懒加载

如果使用vue-cli,默认生成的vendor.js文件会非常大。这个时候需要进行拆包。其实打包输出后,都可以用如下工具瞧瞧包依赖情况。

  • webpack-chart:用于webpack统计的交互式饼图。

  • webpack-visualizer:可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。

  • webpack-bundle-analyzer:一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。

第二是通过 Chrome 的 Instrument converge 功能查看 js,css 的资源使用率:https://developers.google.com/web/tools/chrome-devtools/coverage

优化输出与分包,原来熟悉的配方 new webpack.optimize.CommonsChunkPlugin({}),现在可直接配置optimization项目

在研究splitChunks之前,我们必须先弄明白 module、chunk和bundle 这三个名词是什么意思:

  • module:就是js的模块化webpack支持commonJS、ES6等模块化规范,简单来说就是你通过import语句引入的代码。

  • chunk: chunk是webpack根据功能拆分出来的,包含三种情况:

    • 你的项目入口(entry)

    • 通过import()动态引入的代码

    • 通过splitChunks拆分出来的代码

      chunk包含着module,可能是一对多也可能是一对一。

  • bundle:bundle是webpack打包之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出。

代码分离 | Code Splitting

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

有三种常用的代码分离方法:

  • 入口点:使用entry配置手动分割代码。

    这种方法存在一些缺陷

    • 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。

    • 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。

  • 防止复制:使用 CommonsChunkPlugin 去重和分离 chunk。将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。

    一些对于代码分离很有帮助的插件和 loaders:

    • ExtractTextPlugin:用于将CSS从主应用程序中分离出来。

    • bundle-loader:用于分离代码和延迟加载生成的 bundle。

    • promise-loader:类似于 bundle-loader ,但是使用的是 promises。

  • 动态导入:通过模块的内联函数调用来分离代码。所谓的延迟加载|按需加载|懒加载

    • 先选择的方式是,使用符合 ECMAScript 提案 的 import() 语法。

    • 使用 webpack 特定的 require.ensure

其实我们一帮需要做的是optimization.splitChunks。以下是生产环境默认配置(webpack版本 4.29.5)

 splitChunks: {
    hidePathInfo: true,
    chunks: 'all',
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    automaticNameDelimiter: '~',
    maxInitialRequests: 3,
    name: true,
    cacheGroups: {
        default: {
            reuseExistingChunk: true,
            minChunks: 2,
            priority: 20
        },
        vendors: {
            automaticNamePrefix: 'vendors',
            test: /[\\\/]node_modules[\\\/]/,
            priority: -10
        }
    }}

splitChunks就算你什么配置都不做它也是生效的,源于webpack有一个默认配置,这也符合webpack4的开箱即用的特性。

我们需要在整个基础上进行优化,所以需要熟悉里面的配置

splitChunks配置解说

配置项也是蛮多,这里抽取一些重点说明,更加详细还是看文档。

splitChunks.maxAnyscRequests 按需加载并发最大请求数

maxInitialRequests是splitChunks里面比较难以理解的点之一,它表示允许入口并行加载的最大请求数,之所以有这个配置也是为了对拆分数量进行限制,不至于拆分出太多模块导致请求数量过多而得不偿失

这里需要注意几点:

  • 入口文件本身算一个请求

  • 如果入口里面有动态加载得模块这个不算在内

  • 通过runtimeChunk拆分出的runtime不算在内

  • 只算js文件的请求,css不算在内

  • 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来

具体参考《理解webpack4.splitChunks之maxInitialRequests

splitChunks.maxAsyncRequests

maxAsyncRequests和maxInitialRequests有相似之处,它俩都是用来限制拆分数量的。

  • maxInitialRequests是用来限制入口的拆分数量

  • maxAsyncRequests是用来限制异步模块内部的并行最大请求数的,说白了你可以理解为是每个import()它里面的最大并行请求数量。

这其中要注意以下几点:

  1. import()文件本身算一个请求

  2. 并不算js以外的公共资源请求比如css

  3. 如果同时有两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来

具体参看《理解webpack4.splitChunks之maxAsyncRequests

splitChunks.minChunks

这个配置表示 split 前单个非按需导入的 module 的并行数的最低下限

注:只对 import 或 require 直接引入的 module 有效。

简单来讲,假如 minChunks 设置为 n,那么某个 module 想要被拆分出去,那么它的共享次数(或者说并行请求次数必须 >= n):

minChunks设置为n

  • 假设有m个入口点,这m个入口点都直接引入了某个模块module(通过import或require直接或间接地引入了模块),也就是共享次数为m

  • 当m至少等于n时,module才会被单独拆分成一个bundle

但是,有个特例

minChunks设置成1

  • 有一个入口点,入口点中import了一个模块,并打印了某些字符串,我们就叫它module

  • module被单独拆分成一个bundle,并且这个bundle文件中也包含了打印字符串的部分

我们注意到拆分出来的那个 bundle 包含了打印字符串的部分,那么如果入口点中仅仅包含了打印字符串的部分,没有引入 module,结果是怎样呢,结果就是打印的那部分代码被单独拆分出来了。所以当 minChunks 被设为 1 时,被拆分出来的某个 bundle 一定包含非引入模块代码,如果非引入模块代码存在的话,而当值设为大于 1 的数值时,则不会出现这种情况

最后,还有一个点需要注意,minChunks 不能设为 0,其值为 >= 1 的正整数,不然为报错

minChunks设置成Infinity

搞懂了minChunks的number属性,Infinity属性就很好理解了。Infinity是不会把任何依赖的模块提取出来打包公用

splitChunks.minChunks用法总结

  1. splitChunks.minChunks 表示 split 前单个非按需导入的 module 的并行数的最低下限,即某个模块的引用次数必须大于等于设置的数值,该模块才能被拆分出来;

  2. splitChunks.minChunks 值如果设为 1,会存在被拆分出来的 bundle 包含非引入模块代码的可能,大于 1 则不会有这种情况;

  3. splitChunks.minChunks 值必须大于等于 1;

minSize与maxSize

minSize限制拆分包的最小值(达到这个值,就拆出新包)

maxSize限制每个拆分出来的包的最大文件体积(超过这个大小,再做包拆分

cacheGroups 缓存组

cacheGroups其实是splitChunks里面最核心的配置,一开始我还认为cacheGroups是可有可无的,这是完全错误的,splitChunks就是根据cacheGroups去拆分模块的,包括之前说的chunks属性和之后要介绍的种种属性其实都是对缓存组进行配置的。

缓存组可以继承重写SplitChunks中的任何属性;但是test、priority和ReuseExistingChunk只能在缓存组级别配置 . 也就是说真正起作用的是cacheGroups, SplitChunks中设置属性仅仅是基本配置而已。

CacheGroups之外设置的约束条件比如说默认配置里面的chunks、minSize、minChunks等等都会作用于cacheGroups,除了test, priority and reuseExistingChunk,这三个是只能定义在cacheGroup这一层的。

因为默认的minChunks=1,这个属性会作用于所有的cacheGroups,但是cacheGroups也可以将上面的所有属性都重新定义,就会覆盖外面的默认属性,比如default这个缓存组就设置了minChunks=2,他会覆盖掉默认值1。

splitChunks默认有两个缓存组:vender(webpack5 默认为defaultVendors)和default,

optimization.runtimeChunk

优化持久化缓存的, runtime 指的是 webpack 的运行环境(具体作用就是模块解析, 加载) 和 模块信息清单,

模块信息清单在每次有模块变更(hash 变更)时都会变更, 所以我们想把这部分代码单独打包出来, 配合后端缓存策略, 这样就不会因为某个模块的变更导致包含模块信息的模块(通常会被包含在最后一个 bundle 中)缓存失效.

optimization.runtimeChunk 就是告诉 webpack 是否要把这部分单独打包出来.

何为运行时代码?

形如import('abc').then(res=>{})这种异步加载的代码,在webpack中即为运行时代码。

设置runtimeChunk是将包含chunks 映射关系的 list单独从 app.js里提取出来,因为每一个 chunk 的 id 基本都是基于内容 hash 出来的,所以每次改动都会影响它,如果不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。设置runtimeChunk之后,webpack就会生成一个个runtime~xxx.js的文件。

然后每次更改所谓的运行时代码文件时,打包构建时app.js的hash值是不会改变的。如果每次项目更新都会更改app.js的hash值,那么用户端浏览器每次都需要重新加载变化的app.js,如果项目大切优化分包没做好的话会导致第一次加载很耗时,导致用户体验变差。

现在设置了runtimeChunk,就解决了这样的问题。所以这样做的目的是避免文件的频繁变更导致浏览器缓存失效,所以其是更好的利用缓存。提升用户体验。

runtimeChunk作用是为了线上更新版本时,充分利用浏览器缓存,使用户感知的影响到最低。

具体参看《webpack之optimization.runtimeChunk作用

下面是一个参考配置:

// 当我们运行项目并且打包的时候,会发现chunk-vendors.js这个文件非常大,那是因为webpack将所有的依赖全都压缩到了这个文件里面,这时我们可以将其拆分,将所有的依赖都打包成单独的js。
config.optimization = {
  mergeDuplicateChunks: true, // 合并相同的 chunk
  // runtimeChunk: 'manifest',
  // runtimeChunk: 'single',
  splitChunks: {
    chunks: 'async',//表示将选择哪些块进行优化。提供字符的有效值为all、async和initial,默认是仅对异步加载的块进行分割。
    minSize: 100 * 1024,//模块大于minSize时才会被分割出来。默认100k
    maxSize: 0,//生成的块的最大大小,如果超过了这个限制,大块会被拆分成多个小块。
    minChunks: 1,//拆分前必须共享模块的最小块数。
    maxAsyncRequests: 5,//按需加载时并行请求的最大数目。
    maxInitialRequests: 3,//入口点的最大并行请求数
    automaticNameDelimiter: '~',//默认情况下,webpack将使用块的来源和名称(例如vendors~main.js)生成名称。此选项允许您指定要用于生成的名称的分隔符。
    automaticNameMaxLength: 30,//允许为SplitChunksPlugin生成的块名称的最大长度
    name: true,
    cacheGroups: {
      elementUI: {
        priority: 20,
        name: 'element-ui',
        test: /element-ui/,
        reuseExistingChunk: true
      },
      vendor: {
        name: `chunk-vendors`,
        test: /[\\/]node_modules[\\/]/,//控制此缓存组选择的模块。省略它将选择所有模块。它可以匹配绝对模块资源路径或块名称。匹配块名称时,将选择块中的所有模块。
        minChunks: 1,
        // maxInitialRequests: 12,
        maxAsyncRequests: 5,
        minSize: 100 * 1024,
        maxSize: 5 * 1000 * 1024,
        priority: -10//一个模块可以属于多个缓存组,模块出现在优先级最高的缓存组中
      },
      common: {
        name: `chunk-common`,
        minChunks: 2,
        priority: -20,
        chunks: 'initial',
        reuseExistingChunk: true//如果当前块包含已经从主包中分离出来的模块,那么该模块将被重用,而不是生成新的模块
      }
    }
  }
}

如果有更好的办法,请赐教。

externals配置

启用CDN,提高缓存效率与打包分析,具体参看《webpack性能优化(0):webpack性能优化概况-优化构建速度 》

路由懒加载分组

分组修改方法如下:

const Role = () => import(/* webpackChunkName: "group-role" */'./views/system/Role.vue')
const AddRole = () => import(/* webpackChunkName: "group-role" */'./views/system/AddRole.vue')
const EditRole = () => import(/* webpackChunkName: "group-role" */'./views/system/EditRole.vue') const User = () => import(/* webpackChunkName: "group-user" */'./views/system/User.vue')
const AddUser = () => import(/* webpackChunkName: "group-user" */'./views/system/AddUser.vue')
const EditUser = () => import(/* webpackChunkName: "group-user" */'./views/system/EditUser.vue')
const UserAllotRole = () => import(/* webpackChunkName: "group-user" */'./views/system/UserAllotRole.vue')

每个功能模块打包后的 js 大概有十几kb,文件数量也大大减少。

performance性能监控

这些限制告诉webpack如何/何时拆分块,它们仅定义了限制值,在限制值以上,警告在控制台中显示,仅此而已。

entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.

performance: {
    maxEntrypointSize: 512000,
    maxAssetSize: 512000
}

参考文章:

webpack4 之 splitChunks.minChunks https://zhuanlan.zhihu.com/p/110175375

理解webpack4.splitChunks https://www.cnblogs.com/kwzm/p/10314438.html

vuecli3项目中webpack4配置(三)代码分割 https://juejin.im/post/6844903879046332424

转载本站文章《webpack性能优化(2):splitChunks用法详解》,
请注明出处:https://www.zhoulujun.cn/html/tools/Bundler/webpackTheory/8554.html

webpack性能优化(2):splitChunks用法详解的更多相关文章

  1. mysql服务性能优化—my.cnf配置说明详解

    MYSQL服务器my.cnf配置文档详解硬件:内存16G [client]port = 3306socket = /data/3306/mysql.sock [mysql]no-auto-rehash ...

  2. Vue1.0用法详解

    Vue.js 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能实现的 ECMAScript 5 特性. 开发环境部署 可参考使用 vue+webpack. 基本用法 1 2 3 ...

  3. mysql中event的用法详解

    一.基本概念mysql5.1版本开始引进event概念.event既“时间触发器”,与triggers的事件触发不同,event类似与linux crontab计划任务,用于时间触发.通过单独或调用存 ...

  4. c++中vector的用法详解

    c++中vector的用法详解 vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间 ...

  5. 【Ext.Net学习笔记】03:Ext.Net DirectEvents用法详解、DirectMethods用法详解

    Ext.Net通过DirectEvents进行服务器端异步的事件处理.[Ext.Net学习笔记]02:Ext.Net用法概览.Ext.Net MessageBus用法.Ext.Net布局 中已经简单的 ...

  6. jQuery 事件用法详解

    jQuery 事件用法详解 目录 简介 实现原理 事件操作 绑定事件 解除事件 触发事件 事件委托 事件操作进阶 阻止默认事件 阻止事件传播 阻止事件向后执行 命名空间 自定义事件 事件队列 jque ...

  7. linux dmesg命令参数及用法详解(linux显示开机信息命令)

    linux dmesg命令参数及用法详解(linux显示开机信息命令) http://blog.csdn.net/zhongyhc/article/details/8909905 功能说明:显示开机信 ...

  8. Ext.Net学习笔记23:Ext.Net TabPanel用法详解

    Ext.Net学习笔记23:Ext.Net TabPanel用法详解 上面的图片中给出了TabPanel的一个效果图,我们来看一下代码: <ext:TabPanel runat="se ...

  9. ava下static关键字用法详解

    Java下static关键字用法详解 本文章介绍了java下static关键字的用法,大部分内容摘自原作者,在此学习并分享给大家. Static关键字可以修饰什么? 从以下测试可以看出, static ...

  10. Tomcat 优化方案 和 配置详解(转)

    转自 Tomcat 优化方案 和 配置详解 http://201605130349.iteye.com/blog/2298985 Server.xml配置文件用于对整个容器进行相关的配置. <S ...

随机推荐

  1. acwing第75场周赛

    这次题比较水,但是还是没能ak,自己小结一下吧 第一道题就是自己枚举相加就行 第二道题是一个多关键字排序,wa了几次,是因为优先级有两个是相同的需要特判一下,然后可以把字符转化为数字的优先级,我用了一 ...

  2. golang在win10安装、环境配置 和 goland开发工具golang配置 及Terminal的git配置

    前言 本人在使用goland软件开发go时,对于goland软件配置网上资料少,为了方便自己遗忘.也为了希望和我一样的小白能够更好的使用,所以就写下这篇博客,废话不多说开搞. 一.查看自己电脑系统版本 ...

  3. string函数部分解释

    ```c1. 运算符重载+.+= 连接字符串= 字符串赋值>.>=.<.<= 字符串比较(例如a < b, aa < ab)==.!= 比较字符串<<. ...

  4. 快速排序(quick_sort)

    快速排序大体分为三个步骤: 1.确定分界点 q[(l+r) >> 1] 或者 q[(l+r+1) >> 1] ,两者得看情况而定,不能用 q[l] 或者 q[r] 了 因为会超 ...

  5. python之封装及私有方法

    目录 封装 简洁 私有方法 封装:提高程序的安全性 将属性和方法包装到类对象中,在方法内部对属性进行操作,在类对象外部调用方法,使得程序更加简洁 在python中,如果该属性不希望在类对象外部被访问, ...

  6. 图文剖析 big.js 四则运算源码

    big.js,一个小型.快速的用于任意精度的十进制算术的JavaScript 库. big.js 用于解决平常项目中进行算术运算时精度丢失引起的结果不准确的问题.和 big.js 类似的两个库 big ...

  7. 【uniapp】【外包杯】学习笔记day04 | 学习模板+vue相关知识+环境搭建

    没啥好说的,人与人的悲欢并不相同,我只觉得吵闹. 好烦啊,虽然不应该总说一些低气压的话,不过目前预见的就是有很多工作要做,并且对于完成的希望也有点没有,就这样吧,没啥好说的. 昨天做了python的作 ...

  8. 【Javaweb】五(Service类)

    一般Spring项目中处理业务的层为Service层,称为业务层.目前常见的风格有: 写法:Service层=Service接口+ServiceImpl实现类 AdminServiceImpl.jav ...

  9. 通过.NET Core+Vue3 实现SignalR即时通讯功能

    .NET Core 和 Vue3 结合使用 SignalR 可以实现强大的实时通讯功能,允许实时双向通信.在这个示例中,我们将详细说明如何创建一个简单的聊天应用程序,演示如何使用 .NET Core ...

  10. Go:条件控制语句

    在 Go 语言中,主要的条件控制语句有 if-else.switch 和 select.以下是对它们的简单介绍: 1. if 语句: if 语句用于根据条件执行不同的代码块.它的基本形式如下: if ...