webpack 之 splitChunks分包策略

webpack 之 splitChunks分包策略

一、为什么需要拆包

随着应用程序规模的增长,JavaScript 文件的大小也越来越大。一个大的 JavaScript 文件会导致页面加载时间过长,影响用户体验。良好的拆包策略可以将一个大的 JavaScript 文件分割成多个较小的代码块,将公用的代码抽离成单独的 chunk,在需要的时候按需加载,并充分利用浏览器的缓存机制,如果使用合理,会极大影响加载时间。

二、拆包方式

  1. 入口起点(entry )
    通过使用入口起点的方式,可以将代码分离成多个单独的文件,这些文件可以通过 webpack 配置一个数组结构的 entry 选项指定。并且在其中传入不同类型的文件,可以实现将 CSS 和 JavaScript(和其他)文件分离在不同的 bundle。但是,这种方式有一些缺点,如无法动态加载,无法共享模块等。
  2. 代码分离(Code Splitting)
    代码分离指将代码分成不同的包/块,然后可以按需加载,而不是加载包含所有内容的单个包。用户只需要下载当前他正在浏览站点的这部分代码,代码分离可以使用ES6模块中的 import() 函数动态的加载模块。使用动态导入的模块会被分割到一个单独的 chunk 中。
  3. 打包分离(Bundle Splitting)
    webpack 为单个应用程序生成多个 bundle 文件。如果你有一个体积巨大的文件,并且只改了一行代码,用户仍然需要重新下载整个文件。但是如果你把它分为了两个文件,那么用户只需要下载那个被修改的文件,而另一个文件浏览器可以从缓存中加载,从而减少重新发布并由此被客户端重新下载的代码量。

综上所述,合理的拆包策略可以显著提升应用程序的性能。代码分离通过将代码按需加载,减小初始下载量;而打包分离将应用程序拆分成多个块,实现增量更新,减少不必要的下载。根据具体场景和需求,选择合适的拆包方式可以最大程度地优化应用程序的加载性能。

三、splitChunks介绍

splitChunks 将满足拆分规则的构建内容抽出来单独打包,从而达到抽离公共模块,减少重复打包的目的。splitChunks 中的配置项用来确定具体的拆分规则,其中的 cacheGroups 配置项必须同时满足其下的所有条件才能生效。

webpack5 中 splictChunks 的默认配置为:

javascript 复制代码
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async',
      // 生成的 chunk 的最小体积,单位为字节(bytes)。内容超过了minSize的值,才会进行打包
      minSize: 20000,
      minRemainingSize: 0,
      // 在拆分之前,必须共享的模块的最小 chunk 数。
      minChunks: 1,
      // 在按需加载时,允许的最大并行请求数。
      maxAsyncRequests: 30,
      // 入口点允许的最大并行请求数。
      maxInitialRequests: 30,
      // 超过这个值就会进行强制分包处理,无视minRemainingSize,maxAsyncRequests,maxInitialRequests
      enforceSizeThreshold: 50000,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

其中chunks属性可配置值有 all、 async 、 initial ,默认值是 async

  1. async : 异步加载进来的包才会校验分包规则,进行分包抽离。如果动态加载的包也同时引用了其它包且命中分包规则也会被抽离出来。

  2. initial : 表示只从入口模块进行拆分。

  3. all : 表示入口模块和异步加载的模块都要进行拆分。

四、splitChunks 拆包策略

  1. split-by-experience: 根据经验制定的拆分策略,自动将一些常用的 npm 包拆分为体积适中的 chunk。适用于大多数项目。
    如下示例,react、antd 会被包拆分到了单独的chunk,其余三方包会在vendorjs。其余的包也可以配置缓存组。如果拆分力度比较细的话还是需要写上一些缓存组的
javascript 复制代码
splitChunks: {
          // chunks、minSize、minChunks 将对所有缓存组生效
          chunks: 'all', // 对所有的chunk进行拆分 
          minSize: 20000, // 拆分 chunk 的最小体积 20000 bytes
          minChunks: 2, // 需在两个模块中共享才进行拆分
          cacheGroups: {
            vendor: {
              name: 'vendor', // chunk 的名称 vendor
              test: /[\\/]node_modules[\\/]/i,	// 匹配node_modules下所有的chunk
              priority: 10,	// 优先级10 优先将node_modules下的chunk拆分到vendor组
              reuseExistingChunk: true, // 重用模块,而不是重新生成
              enforce: true, // 强制拆分
            },
            default: {	// 默认组 非node_modules下的文件块 将执行default缓存组规则
              reuseExistingChunk: true,
              priority: -10, // 优先级 -10 
              enforce: true, // 强制拆分
            },
            react: { // react组
              name: 'react',
              test: /[\\/]node_modules[\\/]react[\\/]/,	// 匹配node_modules下的react库
              priority: 20,	// 优先级20 优先将node_modules下的react拆分出去
              minChunks: 2,
              reuseExistingChunk: true,
            },
            antd: {	// antd组
              name: 'antd',
              test: /[\\/]node_modules[\\/]antd[\\/]/,	// 匹配node_modules下的antd库
              priority: 20,	// 优先级20 优先将node_modules下的antd拆分出去
              minChunks: 2,
              reuseExistingChunk: true, // 重用模块,而不是重新生成
            },
          }
        }
  1. split-by-module: 按 NPM 包的粒度拆分,每个 NPM 包对应一个 chunk。
    假如应用程序添加或升级了某个 npm 包,那么 vendor.js 的哈希值将会发生改变,这意味着用户在访问页面时需要重新下载 vendor.js文件。这个时候如果对antd进行了升级或新增/删除了一个antd组件,那么用户不需要重新下载vendor.js文件,而只需要下载antd模块对应的chunk。因为每个chunk都有一个独立的哈希值,当我们对单独的chunk进行修改时,只会影响到对应的chunk文件,而不会影响其他chunk文件。这样就实现了对npm包的增量更新。
    这种情况下如果npm包比较多的情况下,会产生高并发请求。对于支持http2的浏览器是没有问题的。但是浏览器并不支持http2的话,也就是http1.1会存在对头阻塞的问题。不过http2兼容性挺好的
javascript 复制代码
splitChunks: {
          chunks: 'all',
          enforceSizeThreshold: 50000,
          cacheGroups: {
            vendors: {
              priority: -10,
              test: /[\\/]node_modules[\\/]/,
              name(module2) {
                return module2.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)?.[1];
              },
            },
          },
          minSize: 0,
          maxInitialRequests: Infinity,
        },
  1. single-vendor: 将所有 NPM 包的代码打包到一个单独的 chunk 中。
    这种情况的话,只要三方包有改动,文件hash就会改变。如果频繁改动npm包的话,浏览器缓存效率相对较低
javascript 复制代码
 vendor: {
              name: 'vendor', // chunk 的名称 vendor
              test: /[\\/]node_modules[\\/]/i,	// 匹配node_modules下所有的chunk
              priority: 10,	// 优先级10 优先将node_modules下的chunk拆分到vendor组
              reuseExistingChunk: true, // 重用模块,而不是重新生成
              enforce: true, // 强制拆分
    },
  1. split-by-size:根据模块大小自动进行拆分。
    符合大小范围内的包就会被拆出来
javascript 复制代码
  splitChunks: {
      chunks: 'all',
      minSize: 20000, // 20 KB
      maxSize: 50000, // 50 KB
      cacheGroups: {
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },

五、总结

要根据业务场景来选择合适的分包策略,拆到什么力度、什么程度需要自己斟酌。要注意拆包之后相同的模块不要被重复打包。

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试