SplitChunksPlugin

最初,chunk(以及其中导入的模块)在 webpack 内部图中通过父子关系连接。CommonsChunkPlugin 用于避免它们之间重复的依赖,但无法进行进一步优化。

从 webpack v4 开始,CommonsChunkPlugin 被移除,取而代之的是 optimization.splitChunks

默认值

开箱即用,SplitChunksPlugin 应该能很好地满足大多数用户的需求。

默认情况下,它只影响按需加载的 chunk,因为更改初始 chunk 会影响 HTML 文件运行项目所需包含的脚本标签。

Webpack 将根据以下条件自动拆分 chunk

  • 新 chunk 可以被共享,或者模块来自 node_modules 文件夹
  • 新 chunk 将大于 20kb(压缩和 Gzip 之前)
  • 按需加载 chunk 时最大并行请求数应小于或等于 30
  • 初始页面加载时最大并行请求数应小于或等于 30

在尝试满足最后两个条件时,首选更大的 chunk。

配置

Webpack 为希望对此功能有更多控制权的开发人员提供了一组选项。

optimization.splitChunks

此配置对象表示 SplitChunksPlugin 的默认行为。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

splitChunks.automaticNameDelimiter

string = '~'

默认情况下,webpack 会使用 chunk 的来源和名称生成名称(例如 vendors~main.js)。此选项允许您指定用于生成名称的分隔符。

splitChunks.chunks

string = 'async' function (chunk) RegExp

这表示将选择哪些 chunk 进行优化。当提供字符串时,有效值为 allasyncinitial。提供 all 可能特别强大,因为它意味着 chunk 甚至可以在异步和非异步 chunk 之间共享。

请注意,它也适用于回退缓存组(splitChunks.fallbackCacheGroup.chunks)。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      // include all types of chunks
      chunks: 'all',
    },
  },
};

或者,您可以提供一个函数以获得更多控制权。返回值将指示是否包含每个 chunk。

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks(chunk) {
        // exclude `my-excluded-chunk`
        return chunk.name !== 'my-excluded-chunk';
      },
    },
  },
};

如果您使用 webpack 5.86.0 或更高版本,您还可以传递正则表达式

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: /foo/,
    },
  },
};

splitChunks.maxAsyncRequests

number = 30

按需加载时最大并行请求数。

splitChunks.maxInitialRequests

number = 30

入口点的最大并行请求数。

splitChunks.defaultSizeTypes

[string] = ['javascript', 'unknown']

设置当数字用于大小时使用的尺寸类型。

splitChunks.minChunks

number = 1

模块在拆分之前必须在 chunk 之间共享的最小次数。

splitChunks.hidePathInfo

boolean

在为通过 maxSize 拆分的部分创建名称时,防止暴露路径信息。

splitChunks.minSize

number = 20000 { [index: string]: number }

生成 chunk 的最小大小(以字节为单位)。

splitChunks.minSizeReduction

number { [index: string]: number }

生成 chunk 所需的主 chunk(bundle)的最小大小减少量(以字节为单位)。这意味着如果拆分成一个 chunk 没有按给定字节量减少主 chunk(bundle)的大小,即使它满足 splitChunks.minSize 值,也不会进行拆分。

splitChunks.enforceSizeThreshold

splitChunks.cacheGroups.{cacheGroup}.enforceSizeThreshold

number = 50000

强制执行拆分且忽略其他限制(minRemainingSize、maxAsyncRequests、maxInitialRequests)的大小阈值。

splitChunks.minRemainingSize

splitChunks.cacheGroups.{cacheGroup}.minRemainingSize

number = 0

splitChunks.minRemainingSize 选项在 webpack 5 中引入,旨在通过确保拆分后剩余 chunk 的最小大小超过某个限制来避免零大小模块。在 'development' 模式下默认为 0。在其他情况下,splitChunks.minRemainingSize 默认为 splitChunks.minSize 的值,因此除了需要深度控制的极少数情况外,无需手动指定。

splitChunks.layer

splitChunks.cacheGroups.{cacheGroup}.layer

RegExp string function

按模块层将模块分配给缓存组。

splitChunks.maxSize

number = 0

使用 maxSize(无论是全局的 optimization.splitChunks.maxSize、每个缓存组的 optimization.splitChunks.cacheGroups[x].maxSize 还是回退缓存组的 optimization.splitChunks.fallbackCacheGroup.maxSize)都会告诉 webpack 尝试将大于 maxSize 字节的 chunk 拆分成更小的部分。拆分后的部分大小至少为 minSize(靠近 maxSize)。该算法是确定性的,模块的更改只会产生局部影响。因此,在使用长期缓存时它很有用,并且不需要记录。maxSize 只是一个提示,当模块大于 maxSize 或拆分会违反 minSize 时,可能会违反此限制。

当 chunk 已经有名称时,每个部分将获得一个派生自该名称的新名称。根据 optimization.splitChunks.hidePathInfo 的值,它将添加一个派生自第一个模块名称的键或其哈希值。

maxSize 选项旨在与 HTTP/2 和长期缓存一起使用。它增加了请求计数以实现更好的缓存。它还可以用于减小文件大小以加快重建速度。

splitChunks.maxAsyncSize

number

maxSize 类似,maxAsyncSize 可以全局应用(splitChunks.maxAsyncSize),也可以应用于缓存组(splitChunks.cacheGroups.{cacheGroup}.maxAsyncSize)或回退缓存组(splitChunks.fallbackCacheGroup.maxAsyncSize)。

maxAsyncSizemaxSize 的区别在于 maxAsyncSize 只会影响按需加载的 chunk。

splitChunks.maxInitialSize

number

maxSize 类似,maxInitialSize 可以全局应用(splitChunks.maxInitialSize),也可以应用于缓存组(splitChunks.cacheGroups.{cacheGroup}.maxInitialSize)或回退缓存组(splitChunks.fallbackCacheGroup.maxInitialSize)。

maxInitialSizemaxSize 的区别在于 maxInitialSize 只会影响初始加载的 chunk。

splitChunks.name

boolean = false function (module, chunks, cacheGroupKey) => string string

每个缓存组也可用:splitChunks.cacheGroups.{cacheGroup}.name

拆分 chunk 的名称。提供 false 将保留 chunk 的名称不变,从而避免不必要的名称更改。这是生产构建的推荐值。

提供字符串或函数允许您使用自定义名称。指定一个字符串或一个始终返回相同字符串的函数会将所有公共模块和供应商合并到一个单独的 chunk 中。这可能导致更大的初始下载量并减慢页面加载速度。

如果您选择指定一个函数,您可能会发现 chunk.name 属性(其中 chunkchunks 数组的一个元素)在为您的 chunk 选择名称时特别有用。

如果 splitChunks.name入口点 名称匹配,则入口点 chunk 和缓存组将被合并为一个单独的 chunk。

main.js

import _ from 'lodash';

console.log(_.join(['Hello', 'webpack'], ' '));

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          // cacheGroupKey here is `commons` as the key of the cacheGroup
          name(module, chunks, cacheGroupKey) {
            const moduleFileName = module
              .identifier()
              .split('/')
              .reduceRight((item) => item);
            const allChunksNames = chunks.map((item) => item.name).join('~');
            return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`;
          },
          chunks: 'all',
        },
      },
    },
  },
};

使用以下 splitChunks 配置运行 webpack 还会输出一个 common 组的 chunk,其名称为:commons-main-lodash.js.e7519d2bb8777058fa27.js(哈希作为实际输出的示例给出)。

splitChunks.usedExports

splitChunks.cacheGroups{cacheGroup}.usedExports

boolean = true

找出模块使用的导出,以混淆导出名称,省略未使用的导出,并生成更高效的代码。当为 true 时:分析每个运行时的已使用导出;当为 "global" 时:全局分析所有运行时组合的导出。

splitChunks.cacheGroups

缓存组可以继承和/或覆盖 splitChunks.* 中的任何选项;但 testpriorityreuseExistingChunk 只能在缓存组级别配置。要禁用任何默认缓存组,请将其设置为 false

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false,
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.priority

number = -20

一个模块可以属于多个缓存组。优化将优先选择 priority 更高的缓存组。默认组具有负优先级,以允许自定义组获得更高优先级(自定义组的默认值为 0)。

splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk

boolean = true

如果当前 chunk 包含已从主 bundle 拆分出的模块,则会重用它,而不是生成新的 chunk。这可能会影响 chunk 的最终文件名。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          reuseExistingChunk: true,
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.type

function RegExp string

允许按模块类型将模块分配到缓存组。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        json: {
          type: 'json',
        },
      },
    },
  },
};

splitChunks.cacheGroups.test

splitChunks.cacheGroups.{cacheGroup}.test

function (module, { chunkGraph, moduleGraph }) => boolean RegExp string

控制此缓存组选择哪些模块。省略它会选择所有模块。它可以匹配绝对模块资源路径或 chunk 名称。当匹配到 chunk 名称时,会选择该 chunk 中的所有模块。

{cacheGroup}.test提供一个函数

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        svgGroup: {
          test(module) {
            // `module.resource` contains the absolute path of the file on disk.
            // Note the usage of `path.sep` instead of / or \, for cross-platform compatibility.
            const path = require('path');
            return (
              module.resource &&
              module.resource.endsWith('.svg') &&
              module.resource.includes(`${path.sep}cacheable_svgs${path.sep}`)
            );
          },
        },
        byModuleTypeGroup: {
          test(module) {
            return module.type === 'javascript/auto';
          },
        },
      },
    },
  },
};

为了查看 modulechunks 对象中可用的信息,您可以在回调中放置 debugger; 语句。然后以调试模式运行 webpack 构建,在 Chromium DevTools 中检查参数。

{cacheGroup}.test 提供一个 RegExp

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          // Note the usage of `[\\/]` as a path separator for cross-platform compatibility.
          test: /[\\/]node_modules[\\/]|vendor[\\/]analytics_provider|vendor[\\/]other_lib/,
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.filename

string function (pathData, assetInfo) => string

允许覆盖文件名,且仅在它是初始 chunk 时才可覆盖。output.filename 中所有可用的占位符在此处也可用。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          filename: '[name].bundle.js',
        },
      },
    },
  },
};

作为函数

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          filename: (pathData) => {
            // Use pathData object for generating filename string based on your requirements
            return `${pathData.chunk.name}-bundle.js`;
          },
        },
      },
    },
  },
};

可以通过在文件名添加路径前缀来创建文件夹结构:'js/vendor/bundle.js'

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          filename: 'js/[name]/bundle.js',
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.enforce

boolean = false

告诉 webpack 忽略 splitChunks.minSizesplitChunks.minChunkssplitChunks.maxAsyncRequestssplitChunks.maxInitialRequests 选项,并始终为这个缓存组创建 chunk。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          enforce: true,
        },
      },
    },
  },
};

splitChunks.cacheGroups.{cacheGroup}.idHint

string

设置 chunk ID 的提示。它将被添加到 chunk 的文件名中。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        defaultVendors: {
          idHint: 'vendors',
        },
      },
    },
  },
};

示例

默认值:示例 1

// index.js

import('./a'); // dynamic import
// a.js
import 'react';

//...

结果:将创建一个包含 react 的单独 chunk。在导入调用时,此 chunk 将与包含 ./a 的原始 chunk 并行加载。

原因

  • 条件 1:chunk 包含来自 node_modules 的模块
  • 条件 2:react 大于 30kb
  • 条件 3:导入调用时的并行请求数为 2
  • 条件 4:不影响初始页面加载时的请求

这背后的原因是什么?react 可能不会像您的应用程序代码那样频繁更改。通过将其移动到单独的 chunk 中,此 chunk 可以与您的应用程序代码分开缓存(假设您正在使用 chunkhash、记录、Cache-Control 或其他长期缓存方法)。

默认值:示例 2

// entry.js

// dynamic imports
import('./a');
import('./b');
// a.js
import './helpers'; // helpers is 40kb in size

//...
// b.js
import './helpers';
import './more-helpers'; // more-helpers is also 40kb in size

//...

结果:将创建一个包含 ./helpers 及其所有依赖项的单独 chunk。在导入调用时,此 chunk 将与原始 chunk 并行加载。

原因

  • 条件 1:chunk 在两次导入调用之间共享
  • 条件 2:helpers 大于 30kb
  • 条件 3:导入调用时的并行请求数为 2
  • 条件 4:不影响初始页面加载时的请求

helpers 的内容放入每个 chunk 将导致其代码被下载两次。通过使用单独的 chunk,这只会发生一次。我们付出了额外请求的代价,这可以被视为一种权衡。这就是为什么有一个 30kb 的最小大小限制。

拆分 Chunk:示例 1

创建一个 commons chunk,它包含入口点之间共享的所有代码。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'initial',
          minChunks: 2,
        },
      },
    },
  },
};

拆分 Chunk:示例 2

创建一个 vendors chunk,它包含整个应用程序中来自 node_modules 的所有代码。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
};

拆分 Chunk:示例 3

创建一个 custom vendor chunk,它包含由 RegExp 匹配的特定 node_modules 包。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'vendor',
          chunks: 'all',
        },
      },
    },
  },
};

17 贡献者

sokrajeremenichelliPriestchchrisdothtmlEugeneHlushkobyzykjacobangelmadhavarshneysakhisheikhsuperburritoryandrew14snitin315chenxsanrohrlafjamesgeorge007anshumanvsnitin315