webpack的chunk生成逻辑

1,272 阅读4分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

刚接触webpack时,使用webpack打包后只会生成一个被称为bundle的文件,在慢慢熟悉webpack后,如果同时对于前端优化有一定的了解,就会尝试将臃肿的bundle拆分成多个小文件并按需加载。

本文希望通过对webpack的部分配置进行说明,让读者对相关的操作有一定了解和掌握。以下操作均是在webpack@4环境下的说明。

webpack中通过配置的optimization.splitChunks来实现这样的效果,该配置也是SplitChunksPlugins插件的配置。

webpack为optimization.splitChunks提供了默认值,让我们来看看:

// 
// ** 默认的splitChunks内容 **
//
{
  chunks: 'async',
  minSize: 30000,
  maxSize: 0,
  minChunks: 1,
  maxAsyncRequests: 5,
  maxInitialRequests: 3,
  automaticNameDelimiter: '~',
  automaticNameMaxLength: 30,
  name: true,
  cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      priority: -10
    },
    default: {
      minChunks: 2,
      priority: -20,
      reuseExistingChunk: true
    }
  }
};

让我们来仔细看看其中的配置

chunks

通过配置该项,开发者可以选择需要进行优化的chunk,被选中的chunk,其中的modules将被分析,并按照一定的策略生成新的chunk。

允许allasyncinitial(chunkname) => boolean四种值,具体效果如下:

  • initial: 所有的立即加载的chunk**(例如bundle文件)**将被检查
  • async: 所有延迟加载的chunk将被检查
  • all: 等价于initial + async效果,所有的chunk都将被检查
  • (chunkname) => boolean: 函数形式可以提供更细粒度的控制

默认情况为async,所以我们的bundle不会被优化,这里可以尝试修改为initial之后再进行一次编译。

{
  chunks: 'initial',
}

除了生成bundle之外,可能还会有名为vendors~xxx.js的文件。(如果没有生成的话,可以尝试在代码中引入node_modules中的包,再重新编译后查看结果。)

两次编译结果的变化,即chunksasync变为initial后,bundle文件因作为一个立即加载的chunk而被优化了。

minSize、maxSize

见名知意,minSizemaxSize限定了新生成的chunk的文件的最小/最大尺寸。只有当准备生成的chunk的最大和最小文件尺寸,只有在这个尺寸内的chunk文件才会被生成,否则代码会保持原样。

minSize=30000表示chunk最小应该有30000bytes。 maxSize=0表示不限制chunk的最大尺寸。如果设置为一个其他的合理值,例如150000,生成的chunk超过了maxSize的情况下,该chunk将被进一步拆分成更小的chunk。

cacheGroups

cacheGroups是splitChunks配置的一个关键配置,其决定了module应该如何合并成一个新的chunk。

{
  vendors: {
    test: /[\\/]node_modules[\\/]/,
    priority: -10
  },
}

cacheGroups配置下的每一个对象,都可以认为是一个cacheGroup,例如上面的代码中是key为vendors的对象。

cacheGroup.test

test用于对所有的module进行筛选,筛选过的module将被放入这个cacheGroup所对应的chunk文件中。上例中通过[\\/]node_modules[\\/]对于module的路径进行测试,最终所有的node_modules路径下的模块都将放在vendors这个chunk下,这也是上面生成vendors~xxx.js的原因。

cacheGroup.priority

定义cacheGroup的优先级。因为对于每个module来说,有可能同时匹配了多个cacheGrouptest规则,此时就需要根据优先级来决定需要放到哪个cacheGroup中。

name

name决定了生成的chunk文件的名字。默认为true情况下,生成的名字格式为为cacheGroup名字~chunk1名字~chunk2名字.js,其中chunk1名字chunk2名字是因为这个cacheGroup中包含了这两个chunk相关的代码,如果有更多的chunk的话以此类推。

automaticNameDelimiter

可能有细心的读者会注意到上一小节中用于连接chunk名称的~,其实是automaticNameDelimiter这个配置项设置的。开发者可以通过该配置项来设置自己想用的连接符。

automaticNameMaxLength

默认情况下,会限制name小节中的chunk1名字~chunk2名字部分长度,超出了限制情况下将进行截断。

例如:设为3情况下,vendors-chunk1名字~chunk2名字.js将变成vendors~chu~21737d60.js,仅仅保留了长度=3的chu部分。在出现截断情况下,会在后面补充一段额外的字符,可能是一种避免文件名重复的机制。

minChunks

当和一个cacheGroup相关的chunk数量超过minChunks时,新的chunk文件才可以生成。

例如,在默认配置中:

{
  default: {
    minChunks: 2,
    priority: -20,
    reuseExistingChunk: true
  }
}

可能有细心的读者又会问了:现在说的不是splitChunks.minChunks吗,和cacheGroup.minChunks有什么关系呢?

其实在splitChunks下的所有配置,在cacheGroup中都会继承或者覆盖。所以对于key为vendors的cacheGroup来说,其minChunks为1,基本就是不限制,所以node_modules文件夹下的内容都会被放入到vendors这个chunk。

所以我们知道,key为default的cacheGroup,其中会放入至少两个chunk所引用的module。如果需要尝试的,可以通过在两个entry所生成的bundle文件中,引用相同的module,之后再打包,应该就会发现该module被放到了名为default~chunk1名字~chunk2名字这样的文件中。

(至此希望大家已经对于chunk的生成逻辑有了大致的了解。)