SplitChunksPlugin

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

自 webpack v4 起,CommonsChunkPlugin 已被 optimization.splitChunks 取代。

默认值

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

默认情况下,它只影响按需块,因为更改初始块会影响 HTML 文件应包含以运行项目的脚本标记。

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

  • 新块可以共享,或者模块来自 node_modules 文件夹
  • 新块将大于 20kb(在 min+gz 之前)
  • 按需加载块时的最大并行请求数将低于或等于 30
  • 初始页面加载时的最大并行请求数将低于或等于 30

在尝试满足最后两个条件时,较大的块更受青睐。

配置

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 将使用块的来源和名称生成名称(例如 vendors~main.js)。此选项允许你指定要用于生成名称的分隔符。

splitChunks.chunks

string = 'async' function (chunk) RegExp

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

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

webpack.config.js

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

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

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

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

splitChunks.hidePathInfo

boolean

在创建由 maxSize 拆分的部件的名称时,防止公开路径信息。

splitChunks.minSize

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

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

splitChunks.minSizeReduction

number { [index: string]: number }

要生成的块所需的主块(包)的最小大小减小(以字节为单位)。这意味着,如果拆分为块不会将主块(包)的大小减少给定的字节数,则不会拆分,即使它满足 splitChunks.minSize 值也是如此。

splitChunks.enforceSizeThreshold

splitChunks.cacheGroups.{cacheGroup}.enforceSizeThreshold

number = 50000

强制拆分的尺寸阈值,并忽略其他限制(minRemainingSize、maxAsyncRequests、maxInitialRequests)。

splitChunks.minRemainingSize

splitChunks.cacheGroups.{cacheGroup}.minRemainingSize

number = 0

在 webpack 5 中引入了 splitChunks.minRemainingSize 选项,通过确保拆分后剩余块的最小大小高于限制值,来避免出现大小为零的模块。在 “开发”模式 下,默认为 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 字节的块拆分为更小的部分。部分将至少为 minSize(紧挨着 maxSize)。该算法是确定性的,对模块的更改只会产生局部影响。因此,在使用长期缓存时可以使用它,并且不需要记录。maxSize 只是一个提示,当模块大于 maxSize 或拆分会违反 minSize 时,可能会违反它。

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

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

splitChunks.maxAsyncSize

number

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

maxAsyncSizemaxSize 之间的区别在于,maxAsyncSize 仅会影响按需加载的块。

splitChunks.maxInitialSize

number

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

maxInitialSizemaxSize 的区别在于 maxInitialSize 仅会影响初始加载块。

splitChunks.name

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

每个 cacheGroup 也可以使用:splitChunks.cacheGroups.{cacheGroup}.name

拆分块的名称。提供 false 将保持块的相同名称,因此它不会不必要地更改名称。这是生产构建的推荐值。

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

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

如果 splitChunks.name 匹配一个入口点名称,则会删除该入口点。

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 还会输出一个名为 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

如果当前块包含已从主包中拆分的模块,它将被重用,而不是生成一个新的块。这可能会影响块的结果文件名。

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

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

{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

字符串 函数 (pathData, assetInfo) => 字符串

仅当它是初始块时才允许覆盖文件名。在 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

布尔值 = false

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

webpack.config.js

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

splitChunks.cacheGroups.{cacheGroup}.idHint

字符串

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

webpack.config.js

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

示例

默认值:示例 1

// index.js

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

//...

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

原因

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

背后的原因是什么?react 可能不会像你的应用程序代码那样经常更改。通过将其移到单独的块中,可以将此块与你的应用程序代码分开缓存(假设你正在使用 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 及其所有依赖项。在导入调用时,此块将与原始块并行加载。

原因

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

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

拆分块:示例 1

创建一个 commons 块,其中包括在入口点之间共享的所有代码。

webpack.config.js

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

拆分块:示例 2

创建一个 vendors 块,其中包括整个应用程序中来自 node_modules 的所有代码。

webpack.config.js

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

拆分块:示例 3

创建一个 custom vendor 块,其中包含由 RegExp 匹配的特定 node_modules 包。

webpack.config.js

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

17 位贡献者

sokrajeremenichelliPriestchchrisdothtmlEugeneHlushkobyzykjacobangelmadhavarshneysakhisheikhsuperburritoryandrew14snitin315chenxsanrohrlafjamesgeorge007anshumanvsnitin315