【译】SplitChunksPlugin [webpack]

(免责声明:本译文未参考任何其他文章,如有与其他译版雷同部分,纯属巧合)
(声明:中文版权归作者zbc所有,转载请注明出处)
翻译原文地址(webpack官网)


原先,被打包的代码块(和被引入的组件块)都是以一种类似“父—子”模式的关系在 webpack 内部依赖图中关联着。CommonsChunkPlugin 就是用来避免依赖块被重复引用,但是没有其他优化功能。

自从 webpack4 以来,CommonsChunkPlugin 已经被移除,取而代之的是 optimization.splitChunks

默认

SplitChunksPlugin 适用于绝大多数用户。
默认情况下,它只影响按需(on-demand)加载的代码块,因为改变初始代码块会影响运行项目时 HTML 文件包含的 <script /> 标签。

webpack 会基于如下原则自动分割代码:

  • 公用代码块或来自 node_modules 文件夹的组件模块。
  • 打包的代码块大小超过 30k(最小化压缩之前)。
  • 按需加载代码块时,同时发送的请求最大数量不应该超过 5。
  • 页面初始化时,同时发送的请求最大数量不应该超过 3。

当试图完成后两项时,总会生成较大体积的代码块。

配置

webpack 提供一系列配置项给想要更多功能的开发者。

默认配置已被选择为 web 表现层最佳方案,但是这与你项目的最佳策略可能有所不同。当你改变配置项时,你应当测试这些修改确实是为你带来了收益。

optimization.splitChunks

下面这个配置对象体现为 SplitChunksPlugin 的默认配置状态。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

splitChunks.automaticNameDelimiter

< string >

webpack 默认会使用原代码块名称来命名新生成的代码块(例如:vendor~main.js)。该配置项可以让你来自定义代码块名称分隔符。

splitChunks.chunks

< function(chunk) | string >

该项指定了哪些块将被优化。如果配置为字符串的话,可选值为“all”, “async”, “initial”。选择 “all” 的功能最为强大,因为这将意味着公共代码块可以同时被异步和非异步块调用。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      // include all types of chunks
      chunks: 'all'
    }
  }
};

或者你也可以选择传入一个函数来控制。其返回值将决定是否应该包含每一个模块。

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

你可以结合 HtmlWebpackPlugin 使用此配置项,它将为你添加所有新生成的代码块。

splitChunks.maxAsyncRequests

< number >

按需加载模块时所允许的最大请求数。

splitChunks.maxInitialRequests

< number >

入口点所允许的最大请求数。

splitChunks.minChunks

< number >

代码拆分前的最小公共块数量。

splitChunks.minSize

< number >

以比特计算,生成代码块的最小体积。

splitChunks.maxSize

< number >

使用 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 和长效缓存使用的。它增加了请求数以求更好的缓存,它也可以用于快速重构即减小文件体积。

maxSize 的优先级高于 maxInitialRequest/maxAsyncRequests,事实上优先级关系为如下:
maxInitialRequest/maxAsyncRequests < maxSize < minSize.

splitChunks.name

< boolean: true | function (module, chunks, cacheGroupKey) | string >

分割块的名字。如果传入 true 将会自动生成一个基于块组和缓存组键的名称。

传入一个字符串或函数将允许你自定义块名称。指定一个字符串或是一个始终返回同一字符串的函数都会使得 webpack 将所有公共模块融合进一个唯一大代码块。这可能导致一个巨大的初始化下载过程,使得页面加载缓慢。
如果splitChunks.name与入口点名称匹配,则该入口点将被删除。
推荐将splitChunks.name设置为 false ,以避免在打包产品的时候进行不必要的改名动作。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      name (module, chunks, cacheGroupKey) {
        // generate a chunk name...
        return; //...
      }
    }
  }
};

当为不同的拆分块分配相同的名称时,所有打包模块都被放置在一个共享块中,因为这样会导致更多的代码下载,所以不建议这样做。

splitChunks.cacheGroups

cacheGroups 可以继承或覆盖任何来自 splitChunks.* 的设定;但是 "test", "priority","reuseExistingChunk" 只能在本层级上设定。为避免使用任何 cacheGroups 默认设置,把它们统统设置为 false。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false
      }
    }
  }
};

splitChunks.priority

< number >

一个模块可以属于多个缓存组。优化过程总是偏爱优先级更高的缓存组。默认组的优先级为负,以便为自定义组提供相对较高的优先级(自定义组的优先级默认为0)。

splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk

< boolean >

如果当前块包含一个已经从主打包文件中分离出来的模块,那么它将被重复使用而不是新生成模块。这将影响最终打包生成的块文件名称。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          reuseExistingChunk: true
        }
      }
    }
  }
};

splitChunks.cacheGroups.{cacheGroup}.test

< function (module, chunk) | RegExp | string >

控制那些被缓存组选中的模块。省略它将选择所有模块。它可以匹配模块资源绝对路径或块名称。一旦一个块被匹配成功,当中的所有模块也一同被选中。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test(module, chunks) {
            //...
            return module.type === 'javascript/auto';
          }
        }
      }
    }
  }
};

splitChunks.cacheGroups.{cacheGroup}.filename

< string >

当且仅当一个块是初始块时,允许覆盖文件名。可以获得output.filename的所有字符。

此配置项也可以在全局的 splitChunks.filename 里面设置,但是不推荐这么做,因为当 splitChunks.chunks 没有被设置为 "initial" 时,可能会导致错误,应避免如此做法。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          filename: '[name].bundle.js'
        }
      }
    }
  }
};

splitChunks.cacheGroups.{cacheGroup}.enforce

< boolean: false >

告知 webpack 应该忽略
splitChunks.minSize,splitChunks.minChunks,splitChunks.maxAsyncRequests
splitChunks.maxInitialRequests 的配置项,并且总是为此缓存组创建块。

//**webpack.config.js**
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          enforce: true
        }
      }
    }
  }
};

举例

默认:例1

// index.js

import('./a'); // 动态引入
// a.js
import 'react';

//...

结果:创建了一个包含 react 的独立块。再导入调用的时候,此块与包含“./a” 的原始块并行加载。
为什么:

  • 条件1:此块包含来自 “node_modules” 文件夹的模块。
  • 条件2:react 包大于30k。
  • 条件3:并行的请求调用数为2。
  • 条件4:不影响页面初始加载时的需要。

这之后可以推论出什么?react 可能不会像你的应用代码那样频繁变动。通过将它转移到一个单独的文件包中可以与你的应用代码分别储存(假设你正使用块哈希,记录,缓存控制或其他长效缓存手段)。

默认:例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.js" 及其所有依赖的单独块。导入调用此块时,与原始块并行加载。
为什么:

  • 条件1:此块被两处调用。
  • 条件2:helpers.js 文件大于30k。
  • 条件3:并行的请求调用数为2。
  • 条件4:不影响页面初始加载时的需要。

将 helpers 代码分发至每一个模块会导致此部分代码被下载两次。将之分离出来就可以是这一下载过程只发生一次了。我们付出一次额外请求的代价以折衷此间消耗,这也是为什么我们把分离块的最小判断值设置为30k的原因。

分离块:例1

创建一个包含所有入口点调用的公共代码 common 块。

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

此配置可以扩大你的初始打包文件 bundle.js ,我们推荐您采用动态引入的方式调用那些一开始并不会立刻被用到的代码模块。

分离块:例2

创建一个包含全部来自 node_modules 文件夹的模块代码 vendors 块。

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

这可能会产出一个包含所有第三方模块库的大包文件。我们推荐此包应只包含您框架的核心代码和实用代码部分,并且动态加载其余依赖。

分离块:例3

创建一个自定义 custom vendor 包,这个包只包含某些我们用正则式匹配的包。

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

这将使 react 和 react-dom 合并打包至同一独立块。如果你不确定某一块里面都包含了哪些模块的话,你应该去看一下 Bundle Analysis 去探究细节。

------------------- 结束线 -----------------------

本文翻译结合笔者开发经验,如有错误,欢迎留言指正、探讨。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容