简单说一下 Webpack分包

最近在看有关webpack分包的知识,搜索了很多资料,感觉这一块很是迷惑,网上的资料讲的也迷迷糊糊,这里简单总结分享一下,也当个笔记。 如有错误请指出。

为什么需要分包

我们知道,webpack的作用,是将各种类型的资源,统一转换成浏览器可以直接识别的 Js Css Html 图片 等等,正如其官网图描述的一样

在这个过程中,webpack需要兼容各种模块化标准 (CommonJs & ESModule),将各种模块化标准实现的各个模块,统一打包成一个bundle.js文件,这个文件可以直接被script标签引用, 被浏览器直接识别。

如果你了解webpack的实现原理,就可以知道,其打包结果本质上是一个自执行函数。 各个模块被封装成一个 路径 => 代码 的Map,通过入参传入。 自执行函数内部,有一套逻辑可以读取,执行这些模块代码,并且兼容统一了各种模块化标准。

说了这么多,webpack给我们的印象是一个 多 归 一 的构建工具,无论我们有多少的模块,最后输出的结果只有bundle.js一个。这就带来一个问题,如果我们的模块体积很大,其中可能还包含了图片等信息,那么从服务端将其下载到本地就需要很大的开销,可能导致用户浏览器出现长时间白屏的现象。我们需要将逻辑上一个大的bundle.js拆散,根据用户的需求,动态的下载,保证用户的体验。

完成这个拆分工作,就需要用到分包相关的技术。

分包的工作,通常由SplitChunksPlugin完成,其是webpack内部提供的一个插件,我们不需要额外的从NPM上下载这个插件,只需要在 optimization中配置splitChunks属性即可。

我们可以看到,SplitChunksPlugin是在optimize阶段通过钩入 optimizeChunks钩子实现分包的

chunks属性

很多介绍splitchunks的文章都是从 chunks开始介绍的,说这个属性有三个值

async(默认)| initial | all

分别对应,拆分异步模块,拆分同步模块,拆分同步和异步模块

由于默认值是 async 所以通过 import() 动态导入的模块会被单独拆分!

现象如此,但是这个理解完全是错误的!

webpack默认行为

首先你需要知道,webpack对异步模块的拆分是webpack的默认行为,和splitChunks无关

我们很容易验证,设置splitChunk: false 关闭拆包,对于如下例子:

javascript 复制代码
webpack.config.js
  // 单一入口
  entry: {
    main: "./src/main.js",
  },

main.js
// 通过 import动态导入add模块 
import("@utils/add").then((module) => {
  console.log(module.add(100, 200));
});

add.js
// 导出一个简单的 add 函数
export function add(x,y){
    return x + y
}

入口main.js动态引入了一个 add函数,由于关闭了拆包优化,最后应该只有一个js文件被输出,但是最终的结果为:

可以观察到,无论开不开分包优化,webpack对于异步导入的模块,都会将其单独作为一个chunk拆分出去,这样也确保了请求资源的时候不把异步模块同主模块一同加载进来,这个是默认行为

javascript 复制代码
add.js
import './commonUtils'
export function add(x,y){
    return x + y
}

我们改动一下,在add.js中同步引入 commonUtils模块,打包结果如下:

可以发现,commonUtils模块被打入了 add.js一起。

对于webpack来说,被异步模块引入的模块,如果这个模块没有被同步模块引用过,那么在异步模块中无论如何被引用,都是异步模块。

如果我们在 main.js中同步引入 commonUtils

javascript 复制代码
main.js
import '@utils/commonUtils'
import("@utils/add").then((module) => {
  console.log(module.add(100, 200));
});

可以看到,最终的打包结果中,commonUtils被和main这个同步模块打包到一起了,如下:

其实这也好理解,如果一个模块都已经被同步载入过了,那么就没必要再费网络资源去异步请求了,直接复用即可。

虽然同步模块和异步模块在默认状态下可以直接复用,但是这种复用仅仅存在于 同一入口的情况下,看下面例子: 我们增加一个app模块,在app中异步引入commonUtils模块,在main中同步引入commonUtils模块

javascript 复制代码
webpack.config.js
  entry: {
    app: "./src/app.js",
    main: "./src/main.js",
  },

app.js
import("@utils/commonUtils").then((module) => {
  console.log(module);
});


main.js
import '@utils/commonUtils'

可以看到,在多入口的情况下,commonUtils被打包了两次

为什么多入口之间不能复用,因为webpack要保证每一个入口都是独立可用的,对于main.js 其单独使用的时候如果要获取commonUtils.js 需要将整个main.js完整下载下来,无法保证每个入口的独立性,所以会单独打包一份出来。

我们把main.js中的同步引入commonUtils也改成异步,可以发现最终结果中只有一个commonUtils模块,也就是说异步模块在不同入口之间是可以复用的

这也符合逻辑,对于每个入口,由于对于commonUtils的载入都是异步的,在一开始载入的时候都不需要同步载入commonUtils,所以自然使用一个独立模块即可。

说完了这些,我们做个小总结 避免混乱

首先,这些都是在不进行任何分包优化下的情况,一定要明确前提

  1. webpack在默认情况下,会 并且 仅仅会对异步载入的模块进行独立分包处理
  2. 对于同步的模块,webpack默认情况下都会打入主包中同步引入,不会独立拆包
  3. 异步模块中无论同步,还是异步的模块引入 都算成是异步引入
  4. 在相同入口的情况下,同步引入和异步引入会共享同步引入的模块,不会单独异步拆包
  5. 在不同入口下,同步和异步引入相对独立,无法共用,会打两份,但是异步模块之间可以互相共用。

我认为,了解webpack默认行为会对后面的学习减少很多的"弯路",在理解分包现象的时候需要理解到底是splitChunk的优化结果,还是webpack的默认行为。

比方说chunks: async虽然是默认值,但是造成分包的并不是这个属性的作用,属于webpack的默认行为!

chunks属性的真正含义

chunks属性的真正含义如下

  • async: 仅仅对异步导入的模块进行splitChunks优化,同步导入的模块会被忽略,也是chunks的默认值 (默认情况下,splitChunks只对webpack默认拆分出来的异步模块生效)
  • initial: initial即初始化的意思,浏览器初始化加载模块的时候,会将引用的所有同步模块都载入,所以initial的含义是,对于同步导入的模块(即 初始化阶段会被载入的模块) 进行splitChunks优化,对于异步导入的模块,会被忽略。 我们知道webpack的默认状态下不会对同步模块单独拆包,所以initial就是用来增强其处理同步模块的功能

浏览器初始化的时候,会将同步模块一口气都载入,那么分包的意义何在呢?

其实主要目的是对于打包多入口时,在默认情况下,如果两个如果都引用了某个同步模块, 为了保证两个入口的独立性,这个模块会被重复两次打包。initial的作用就是可以把这个共同引用的同步模块拆分出来,减小打包文件的总体积。

  • all: 对于同步,异步引入的模块,都会进行splitChunks的优化,这个也是被官方推荐的方式。

很多教程中,说把chunks设置为 all 的作用是除了拆分async 还能拆分第三方vendor(即 node_modules)模块 这个也是不对的,只是all很强大,开启之后可以达到拆分第三方模块的效果,但是绝对不是其作用就是为了拆分第三方模块。 具体实现原理下面会说

我们看一些例子,如下,我们简单的设置splitChunks的chunks: async 为了不影响我们观察打包结果,我们先将minSize minChunks分别设置为 0 和 1 也就是让拆包大小和引用chunks数不影响chunks本身的效果。

javascript 复制代码
// webpack.config.js
   
  entry: {
    app1: "./src/app1.js",
    app: "./src/app.js",
    main: "./src/main.js",
  },

    splitChunks: {
      chunks: "async",
      minSize: 0,
      minChunks: 1,
    }

// app1.js
import"@utils/commonUtils"

// app.js
import("@utils/commonUtils").then((module) => {
  console.log(module);
});

// main.js
import"@utils/commonUtils"

未开启splitChunks优化

开启 chunks: async 优化异步模块

可以看到,打包结果和不设置 splitChunk没什么两样,但是这并不代表其未生效,我们来分析一下

首先,在进行拆包优化之前,由于在三个入口分别同步和异步引入了 commonUtils模块。 由于在两个不同入口中,同步异步引用无法复用,webpack的默认行为会把异步的commonUtils拆分出来,并且把同步引入的commonUtils合并到主包中。

优化开始,由于chunks: async 此时只对拆分出来的commonUtils进行拆分,忽略同步的commonUtils 所以此时没什么可优化拆分的,splitChunks不会对打包结果有什么改变。

我们把chunks改成 initial 即 只对同步模块进行优化, 可以看到,此时,两个commonUtils被拆分出来了,由于有两个入口同步引用了commonUtils,initial模式下,会把这个同步模块单独拆分进行复用。但是由于initial模式会忽略异步模块,所以会导致异步的commonUtils和同步的commonUtils无法复用。

为了解决同步异步的commonUtils不能复用的问题,我们把chunks设置为 all 即 对同步引入和异步引入的模块都开启分包优化,此时的打包结果如下:

可以看到,由于对于同步异步的模块splitChunk都会识别处理,所以commonUtils会被单独拆分成一个chunk,供同步和异步的引入使用,这样就达到了对commonUtils的最小化拆分。

再看一个例子,我们引入两个入口,其中:

app1.js引入一个node_modules下的 lodash模块

main中引入我们自己定义的 utils/commonUtils.js模块

javascript 复制代码
webpack.config.js
entry: {
    app1: "./src/app1.js",
    main: "./src/main.js",
  },

chunks: all

// app1.js
import "lodash";

// main.js
import"@utils/commonUtils"

打包结果如下:

你会发现,同样是被一个入口引用1次,为什么lodash就被单独拆分了,我们自己定义的包就没被拆分,而我们已经设置了minSize: 0 也就是模块大小不会影响拆包,那么为什么第三方模块就被拆分了,我们自定义的模块就不会? 这个就要引入我们下面要说的 cacheGroup 缓存组的概念了

cacheGroups缓存组

缓存组用来定义哪些模块被拆分成一组,拆分成一组的模块可以被浏览器单独的缓存,不用在每次更新的时候重新从服务器获取。

cacheGroups需要配置一个对象,key未缓存组的名称,value为一个配置对象,其中使用 test属性匹配模块的名称,我们举个例子:

javascript 复制代码
  splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      cacheGroups: {
        MyUtils: {
          test: /\/utils\//,
          minSize: 0,
          minChunks: 1,
          name: 'MyUtils'
        }
      }
}

上述例子,我们设置一个MyUtils缓存组,其中test匹配路径中包含 utils的模块,并且设置其单独分包名称为 MyUtils。 其含义是,需要webpack对路径中包含 utils的模块单独拆出来并且合并到一个包里,这个包的名称为 MyUtils.

可以看到,这时我们自己定义的 utils/commonUtils.js被单独打包了。

那么回到上面的问题,为什么node_modules的模块就会被单独拆分,我们自己写的模块就需要我们自定义一个缓存组才能实现拆分? 这是因为webpack内置了两个 defaultVendors和default两个缓存组 如下:

javascript 复制代码
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', // 代码分割的初始块类型,默认为异步加载的模块
      minSize: 20000, // 生成 chunk 的最小体积(以字节为单位)
      minRemainingSize: 0, // Webpack 5 新增,确保拆分后剩余的最小大小
      maxSize: 0, // 尝试将大于 maxSize 字节的 chunk 拆分为较小的部分
      minChunks: 1, // 共享该模块的最小 chunks 数
      maxAsyncRequests: 30, // 按需加载时的最大并行请求数
      maxInitialRequests: 30, // 入口点的最大并行请求数
      automaticNameDelimiter: '~', // 生成名称时使用的分隔符
      enforceSizeThreshold: 50000, // Webpack 5 新增,强制执行拆分的体积阈值
      cacheGroups: { // 缓存组配置
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 中的模块
          priority: -10, // 优先级
          reuseExistingChunk: true // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则重用该模块
        },
        default: {
          minChunks: 2, // 被至少两个 chunks 共享的模块
          priority: -20, // 优先级低于 vendors
          reuseExistingChunk: true // 重用已存在的 chunk
        }
      }
    }
  }
};

可以看到,对于路径包含 /node_modules/的模块,splitChunks插件会默认将其分到一个缓存组中去,而对于default缓存组,其没有设置test属性,意味着没有匹配到任何缓存组的模块都会被default缓存组匹配,其优先级priority: -20 低于defaultVendors的-10 为最低优先级的缓存组,可以被当成是一组兜底的缓存策略。

这也就解释了为什么我们自己定义的commonUtils模块没有被单独拆包,虽然其匹配到了默认的default缓存组,但是由于其内部配置了 minChunks: 2 也就是说当前模块必须被至少两个chunk匹配到,才会单独拆包,而上述例子commUtils只被引用了一次,所以没有被单独划分。

你也许会问? lodash也只被匹配了一次啊? 为什么也被单独拆包了? 这时因为defaultVendors缓存组没有配置minChunks,那么会直接使用顶层的minChunks配置,也就是minChunks:1 任何模块只要被一个chunk引用,就会被单独拆包 (注意这里是chunks 不是 modules)

注意,cacheGroups这个名字很容易误导人,其并不是把test匹配到的模块都合并到一个chunk,还要考虑其中的 minSize minChunks name等等属性的作用

你可以将其理解为 缓存规则组 把一组决定模块是否缓存 如何缓存的规则 放到一个组里面,test匹配到某个group就使用这个group的规则。

cacheGroups的作用原理 & 如何禁用cacheGroups

回忆一下我们上面的例子,当我们设置cacheGroups时,如果cacheGroups的名称和默认的 defaultVendors / default 不重复,那么不会覆盖默认的cacheGroups配置

javascript 复制代码
  splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      cacheGroups: {
        MyUtils: {
          test: /\/utils\//,
          minSize: 0,
          minChunks: 1,
          name: 'MyUtils'
        }
      }
}

等价于 

  splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      cacheGroups: {
        MyUtils: {
          test: /\/utils\//,
          minSize: 0,
          minChunks: 1,
          name: 'MyUtils'
        },
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true 
        },
        default: {
          minChunks: 2, 
          priority: -20, 
          reuseExistingChunk: true 
        }
      }
}

所以,如果我们不想使用默认配置,不能直接 cacheGroups: {} 而是需要

javascript 复制代码
// 关闭默认cacheGroups
      cacheGroups: {
        default: false,
        defaultVendors: false
      }

禁用缓存组之后,我们再来看分包结果

可以看到,lodash和commonUtils都没有被拆分。你也许会疑问了? 即便什么缓存组都没匹配到 那顶层的默认配置不是还写着 minChunks:1 呢么?

你可以简单的理解为,cacheGroups是拆包的 "引擎" 只有匹配到了cacheGroups才会启动分包流程,如果任何的缓存组都没匹配到,光有顶层的那些配置是没有任何意义的。

所以你需要记住,在没有匹配到任何缓存组的情况下,splitChunk不会对模块进行拆包优化!

所以,回到前面的问题,有些博客说,把chunks设置为all就开启了对第三方模块的分包优化 这是不严谨的,很可能让人误认为拆分第三方模块是chunks all的效果,但是其实不是的。

由于我们引用第三方模块大多是同步引入,而chunks默认值为 async,所以默认情况下,我们不会对同步引入的第三方模块进行分包优化。

当开启了all的时候,第三方模块也会被纳入分包优化的范围,而splitChunks又内置了defaultVendors的缓存组,并且没有设置其minChunks

所以才会拆分出第三方内容,设置chunks: all仅仅是整个拆分流程中的一个环节而已。

minSize

设置chunk的最小尺寸,单位字节(Byte),只有在模块尺寸大于这个属性值的时候,才会拆包

commonUtils的尺寸约为40B 当设置顶层minSize: 40 / 50 时,拆分结果如图

同时,我们还可以在缓存组的内部设置minSize,其会覆盖顶层的minSize配置

minChunks

minChunks限制了模块的最小chunks引用数,也就是模块被至少 minChunks 个chunk引用,才会被分割出去。

这里的chunk计数,取决于chunks的配置,如果chunks配置为 async 那么只有异步模块会进来匹配这个minChunks 如果是inital 那么只有同步模块会被计算进chunks 如果是all则都被计算。我们看例子

javascript 复制代码
webpack.config.js
    splitChunks: {
      chunks: "initial",
      minSize: 0,
      minChunks: 1,
      cacheGroups: {
        // 关闭默认缓存组
        defaultVendors: false,
        default: false,
        utils: {
          test: /\/utils\/commonUtils.js/,
          minChunks: 3,
          name: 'MY_UTILS'
        }
      }
}

  entry: {
    app1: "./src/app1.js",
    app: "./src/app.js",
    main: "./src/main.js",
  },


// main.js
import"@utils/commonUtils"

//app.js
import("@utils/commonUtils").then((module) => {
  console.log(module);
});

// app1.js
import"@utils/commonUtils"

main和app1 同步引用了commonUtils模块,app异步引入commonUtils

我们设置commonUtils的缓存组minChunks: 3 chunks: initial 此时打包如下

可以看到 commonUtils被打包了3份 为什么?

  1. webpack默认情况下,会把异步app.js中的common模块单独拆分

  2. 由于chunks为initial 所以只会统计同步引入common模块的chunks数 只有app和main两个,所以不满足minChunks: 3的配置,不分包,会对app1 main 两个chunk都生成一份commonUtils

当我们设置minChunks: 2

可以看到,main和app1中的common模块被抽取出来了,但是由于chunks为initial 没有app中异步分离出来的commonUtils复用。 由于异步模块会被忽略,splitChunks不知道有一份可以被复用的commonUtils已经被生成了。

为了解决,我们设置chunks为all,并且设置minChunks:3 如下:

javascript 复制代码
    splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      cacheGroups: {
        defaultVendors: false,
        default: false,
        utils: {
          test: /\/utils\/commonUtils.js/,
          minChunks: 3,
        },
      },}

此时,只有一份common被抽取,如下:

其过程为

  1. webpack默认抽取app中的异步common模块

  2. 分包优化的时候 由于同步异步模块都会被记入minChunks统计,满足minChunks: 3的条件 所以commonUtils会被单独拆包,但是发现webpack默认已经拆了一份common出来 就直接复用即可。

而对于chunks initial的情况下,由于忽略了异步的common模块,所以无法对已经拆出来的模块进行复用。

enforce 强制拆分

enforce通常用在缓存组内,如果某个缓存组内设置了enforce: true 那么这个缓存组会忽略全局的 minSize minChunks等等这些限制,仅以缓存组内部的配置为准

需要注意的是 enforce是用来忽略全局的配置的,无法忽略缓存组内部的配置

看以下例子

javascript 复制代码
// webpack.config.js
    splitChunks: {
      chunks: "all",
      minSize: 999999,
      minChunks: 999999,
      cacheGroups: {
        defaultVendors: false,
        default: false,
        utils: {
          test: /\/utils\/commonUtils.js/,
        },
      },
}

// 入口
import"@utils/commonUtils"

由于外层的 minChunks minSize设置的非常大,所以打包出来的结果是不进行任何拆分

此时,如果我们设置了enforce: true 如下

javascript 复制代码
   splitChunks: {
      chunks: "all",
      minSize: 999999,
      minChunks: 999999999,
      cacheGroups: {
        defaultVendors: false,
        default: false,
        utils: {
          test: /\/utils\/commonUtils.js/,
          enforce: true,
        },
      },

外层的配置将会被直接忽略,可以看到 common模块还是被正常分离出来了。这就是enforce直接忽略了外层的配置。

但是,如果我们对缓存组内部设置minChunks: 2 此时enforce就无法对内部的限制产生效果了。

所以,当你想忽略掉顶层配置强制拆分某个模块的时候,可以尝试使用enforce属性!

maxInitialRequests

maxInitialRequest代表最大同步并发数量,也就是限制了单个entry下加载的最大initial chunk数量

需要注意 maxInitialRequests 默认包含入口chunk,当对某个缓存组做更精细化的配置时,要减去1

在看例子之前,需要先将全局的 enforceSizeThreshold设置为0,即没有强制尺寸分包上限,方便我们观察

javascript 复制代码
webpack.config.js
   
 splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      maxInitialRequests: 1,
      enforceSizeThreshold: 0,
      cacheGroups: {
        defaultVendors: false,
        default: false,
        react: {
          test: /react/,
          minSize: 0,
          minChunks: 1,
        }
     }

// 入口 
import react from 'react'

maxInitialRequest默认包含了入口chunk,所以在某个缓存组内分析时要先减去入口chunk

比如对于react缓存组,其没设置maxInitialRequests 所以继承全局的为 1 也就是说,对于react缓存组,其最多可以分出 maxInitialRequest - 入口chunk(1) = 0 个chunk,也就是说react不能被单独分包,哪怕满足minSize minChunks这些, 最终分包结果如下:

可以看到,react收到maxInitialRequests的限制,被合并到了入口chunk中。

若修改react缓存组内的maxInitialRequests: 2 那么其会覆盖全局的配置,将react单独分包 如下:

如果无法满足 maxInitialRequest的要求,那么会尽可能把大的模块拆分出来,小的合并,看下面例子

javascript 复制代码
// webpack.config.js
    splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      maxInitialRequests: 2,
      enforceSizeThreshold: 0,
}

entry: {
    app1: "./src/app1.js",
    app: "./src/app.js",
    main: "./src/main.js",
}

// app.js
import 'lodash'


// app1.js
import 'lodash'
import 'react'

//main.js
import"react"

其引用关系如下图:

可以看到,app1模块的initialRequest为 = app1入口chunk + lodash + react = 3 不满足 maxInitialRequest = 2

webpack会把其中更大的lodash模块拆分出来,更小的react模块合并到入口chunk内, 如下

可以看到,对于 app 和 main 由于只引用了一个模块 满足maxInitialRequest: 2

但是对于 app1 更大的lodash被拆出来共用 ,更小的react被合并进app入口了。

注意!在 webpack 中,runtimeChunk: true不包含在 maxInitialRequests 的限制内。

maxAsyncRequest

maxAsyncRequest和maxInitialRequest类似,区别在于,其限制的是 对于每个 import() 动态导入 最大并发的可以下载的模块数, 其中 import() 本身算一个并行请求

有点绕,举个例子

我们在入口动态引入一个 testModule.js 其中同步引入add sub两个模块 如下

javascript 复制代码
// main.js
import ('@utils/testModule')

// testModule.js
import "./add";
import "./sub";


// add.js
export function add(x,y){
    return x + y
}

//sub.js
export function sub(x,y){
    return x - y
}

其依赖图为

我们设置缓存组,让各个模块之间都相互独立

javascript 复制代码
    splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      enforceSizeThreshold: 0,
      cacheGroups: {
        utils: {
          test: /\/utils\//,
          minChunks: 1,
          minSize: 0,
          priority: 10,
          name: ()=>'UTILS'+Math.random()
        },
      },

打包结果如下

全局设置 maxAsyncRequests: 1 结果如下:

为什么三个模块被打包到一起了? 因为在main中动态引入testModule 由于需要满足maxAsyncRequest 此时 如果把add sub单独拆分,那么asyncRequest = 动态导入testModule + add + sub = 3 不满足限制。

maxAsyncRequest设置为 2 可以看到打包结果为

add和sub被单独拆分,和testModule分离,此时的asyncRequest刚好为 2

设置maxAsyncRequests = 3 可以看到三个模块都被独立分割了

我们修改 testModule 动态引入add 并且设置maxAsyncRequests为1

javascript 复制代码
// testModule.js
import("./add");
import "./sub";

splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      maxAsyncRequests: 1,
      enforceSizeThreshold: 0,
      cacheGroups: {
        utils: {
          test: /\/utils\//,
          minChunks: 1,
          minSize: 0,
          priority: 10,
          name: ()=>'UTILS'+Math.random()
        },
      },

此时可以看到,结果为

为什么add被单独拆分了? 这是不是不满足maxAsyncRequest: 1 的限制了? 其实不是

我们前面说了,splitChunks无法改变webpack的默认行为,由于add模块属于异步引入,对其拆分成一个单独的模块属于webpack的默认行为,所以splitChunks只能在add被拆分的基础上进行限制。

但是请你仔细考虑,maxAsyncRequests本身是不是就不包括异步引入的模块呢?

对于一个import动态引入的模块 其形式为

javascript 复制代码
import SyncModuleA. // 预解析
import SyncModuleB // 预解析
import (AsyncModuleC) // 运行时

对于同步引入的SyncModuleA & B 在整个模块被import的时候就会同步的下载这两个模块,但是对于动态引入的ModuleC 只有在运行到此的时候才会真正去加载,由此可见,异步引入模块是不包含在maxAsyncRequest中的 其本意是 在动态引入一个模块时,同步加载进来的模块数量,而其中的异步模块,并不属于 "同步加载进来的"

我们继续修改maxAsyncRequest = 2 你可以猜到打包结果了

其中,add.js是webpack默认行为拆分的,不包含在maxAsyncRequests的计算中,所以maxAsyncRequests = import(TestModule) + import 'add.js' = 2 满足约束

优化建议maxSize

maxSize表示,拆分的chunk最大值,当拆分出的chunk超过这个值的时候,webpack会尝试将其拆分成更小的chunks

maxSize是个 "建议性质"的限制,也就是webpack如果没有找到合适的切分点,maxSize将不起作用, 官方描述如下

maxSize的优先级 高于maxAsyncRequest / maxInitialRequest

看一个例子,

javascript 复制代码
// main.js
import'react'

//webpack.config.js
optimization: {
    runtimeChunk: true,
    // splitChunks: false,
    splitChunks: {
      chunks: "all",
      minSize: 0,
      minChunks: 1,
      maxInitialRequests: 1,
      maxSize: 200,
      enforceSizeThreshold: 0,
}}

此时,react依旧会被拆分成小块,即便不满足maxInitialRequests,如下:

相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax