之前写的《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. ACTF 2023 部分WP

    来自密码手的哀嚎: 玩不了一点,太难了. CRYPTO MDH Description Malin's Diffile-Hellman Key Exchange. task.sage from has ...

  2. 殷浩详解DD系列

    第五讲:https://blog.csdn.net/Taobaojishu/article/details/115911833 内部有1-4讲链接

  3. Python 潮流周刊#25:性能最快的代码格式化工具 Ruff!

    你好,我是猫哥.这里每周分享优质的 Python.AI 及通用技术内容,大部分为英文.标题取自其中一则分享,不代表全部内容都是该主题,特此声明. 本周刊由 Python猫 出品,精心筛选国内外的 25 ...

  4. Python 既是解释型语言,也是编译型语言

    哈喽大家好,我是咸鱼 不知道有没有小伙伴跟我一样,刚开始学习 Python 的时候都听说过 Python 是一种解释型语言,因为它在运行的时候会逐行解释并执行,而 C++ 这种是编译型语言 不过我今天 ...

  5. UIPath初识和安装

    即使没有人为你鼓掌,也要优雅的谢幕,感谢自己的认真付出. 一. UiPath组成   学习UiPath,我们一定先要了解Studio,Robot和Orchestrator这3个重要组成部分. UiPa ...

  6. LabVIEW用布尔控件实现上升沿和下降沿触发

    我们利用了第三方布尔控件来记录摇杆的高低电平状态,并和摇杆布尔控件组成布尔数组,转换成十进制数进行判断上升沿和下降. 上升沿触发.例如一开始第三方布尔控件为T,夹紧松开布尔控件为F,然后我这时把摇杆控 ...

  7. 30. 干货系列从零用Rust编写正反向代理,HTTP的组装之旅(中间件)

    wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...

  8. WPS JS宏

    WPS JS宏 1 JS宏基础 1.1 JS宏基础 1.1.1 JS录制新宏 如果在WPS表格中要编写控制表格的代码,却又不知道如何编写,那么可以使用JS录制新宏功能,接下来录制几个常用的操作: 录制 ...

  9. STL常用函数

    STL简介 \(STL\)是\(Standard\) \(Template\) \(Library\)的简称,中文名称为标准模板库,从根本上讲, 就是各种\(STL\)容器的集合,容器可以理解为能够实 ...

  10. 一招MAX降低10倍,现在它是我的了

    一.背景 性能优化是一场永无止境的旅程. 到家门店系统,作为到家核心基础服务之一,门店C端接口有着调用量高,性能要求高的特点. C端服务经过演进,核心接口先查询本地缓存,如果本地缓存没有命中,再查询R ...