分割打包

目前,生产环境下的打包结果是单个 JavaScript 文件。如果更改了代码,则客户端也必须重新下载整个包,包括一些外部依赖包。

最好的结果是只下载更改的部分。如果外部依赖包发生更改,则客户端应仅获取依赖包。对于应用本身的代码也是如此。我们可以使用optimization.splitChunks.cacheGroups 来进行分割打包。在生产模式下运行时,webpack 4可以开箱即可执行一系列拆分。但在这里,我们会手动执行某些操作。

要正确使打包结果无效,必须将哈希附加到生成的 bundle 中,如添加哈希值到文件名一章所描述的。

分割打包的思想

通过拆分打包,您可以将外包依赖项单独打包,并从客户端级别缓存中受益。执行了该过程,应用程序的整个大小依然保持不变。尽管需要执行的请求越多,会产生轻微的开销,但缓存的好处弥补了这一成本。

打一个简单的比方,你可以得到 main.js(10 kB)和 vendor.js(90 kB),而不是 main.js(100 kB)。现在,对于已经使用过该应用程序的客户端,如果仅仅对应用代码做更改,将只加载 main.js 部分。

缓存也有相应的问题,其中之一就是缓存失效。与此相关的潜在方法将在“ 添加哈希值到文件名”一章中讨论。

打包分割并不是唯一的出路。代码分割一章讨论了另一种更精细的方式。

添加要拆分的东西

鉴于还没有可以拆分的外部依赖包,我们首先将 React 添加到项目中:

npm install react react-dom --save

然后在项目中导入它:

src/index.js

import "react";
import "react-dom";

...

执行 npm run build 以获取基本构建,你应该得到如下的东西:

Hash: 80f9bb6fc04c54949644
Version: webpack 4.1.1
Time: 3276ms
Built at: 3/16/2018 4:59:25 PM
       Asset       Size  Chunks             Chunk Names

如您所见,main.js 很大。这是接下来要解决的问题。

设置 vendor 包

在 webpack 4 之前,过去常常要使用 CommonsChunkPlugin 来管理打包分裂,现在该插件已被自动化和配置所取代。要从 node_modules 目录中提取供应商(外部依赖)包,请按如下方式调整配置:

webpack.config.js

const productionConfig = merge([
  ...

  {
    optimization: {
      splitChunks: {
        chunks: "initial",
      },
    },
  },

]);

如果您再次打包(npm run build),您应该看到以下内容:

Hash: 6c499f10237fdbb07378
Version: webpack 4.1.1
Time: 3172ms
Built at: 3/16/2018 5:00:03 PM
               Asset       Size  Chunks             Chunk Names

     vendors~main.js   96.8 KiB       0  [emitted]  vendors~main

             main.js   1.35 KiB       1  [emitted]  main
            main.css   1.27 KiB       1  [emitted]  main

    vendors~main.css   2.27 KiB       0  [emitted]  vendors~main
 vendors~main.js.map    235 KiB       0  [emitted]  vendors~main
vendors~main.css.map   93 bytes       0  [emitted]  vendors~main

         main.js.map   7.11 KiB       1  [emitted]  main
        main.css.map   85 bytes       1  [emitted]  main
          index.html  329 bytes          [emitted]
Entrypoint main = vendors~main.js vendors~main.css ...
...

现在,我们看到打包结果被拆分了。下图说明了当前的情况。

应用分割打包配置后的打包结果,应用代码和供应商代码被分开了

控制分割打包

我们可以重写上面的配置,并将外部依赖包的匹配规则针对于 node_modules 文件夹,如下所示:

webpack.config.js

const productionConfig = merge([
  ...

  {
    optimization: {
      splitChunks: {
        cacheGroups: {
          commons: {
            test: /[\\/]node_modules[\\/]/,
            name: "vendor",
            chunks: "initial",
          },
        },
      },
    },
  },

]);

如果你不希望自动分割打包,按照此格式可以更好地控制拆分过程。

拆分和合并块

Webpack 通过两个插件提供对生成的块的更多控制:AggressiveSplittingPluginAggressiveMergingPlugin。前者允许您发出更多更小的包。由于 HTTP/2 新标准的工作方式,这种处理是非常方便的。

以下是一种更激进的分割打包方式:

{
  plugins: [
    new webpack.optimize.AggressiveSplittingPlugin({
        minSize: 10000,
        maxSize: 30000,
    }),
  ],
},

如果你分成多个小的块,对于客户端缓存来说是比较有利的;但是,在 HTTP/1 环境中还会有额外的请求开销。目前,由于 HtmlWebpackPlugin 中的 bug ,如果启用该插件,这个方法不会起作用。

这个激进的插件还能够以相反的方式工作,允许您将小的块组合成更大的块:

{
  plugins: [
    new AggressiveMergingPlugin({
        minSizeReduce: 2,
        moveToParents: true,
    }),
  ],
},

如果使用 Webpack 对于已经打包过的块进行记录,那就可以更好的利用缓存。“添加哈希到文件名”一章中详细讨论了这个想法。

webpack.optimize 包含 LimitChunkCountPluginMinChunkSizePlugin,可以进一步控制块的大小。

Tobias Koppers 在webpack的官方博客中详细讨论了激进合并

Webpack 中的块类型

在上面的示例中,您使用了不同类型的 Webpack 块。Webpack 处理三种类型的块:

  • 入口块 - 入口块包含 Webpack 运行时和它随后加载的模块。
  • 正常块 - 正常块包含 Webpack 运行时。相反,这些可以在应用程序运行时动态加载,Webpack 为这些块生成合适的包装器(例如 JSONP)。您将在下一章中,设置代码拆分时生成正常块。
  • 初始块 - 初始块是正常的块,计入应用程序的初始加载时间。作为用户,您不必关心这些。这是入口块和正常块之间的重要的分割点。

总结

与之前相比,现在情况好转了。注意比较 main 块与 vendor 块之间的大小。要从此拆分中受益,你可以设置缓存。请参考本书的下一部分“ 添加哈希值到文件名”

回顾一下:

  • Webpack 允许您通过 optimization.splitChunks.cacheGroups 字段从配置入口中分割打包。它在生产模式下默认执行分割打包。
  • vendor 包包含项目的第三方代码。可以通过检查模块的导入位置来检测外部依赖。
  • Webpack 通过特定的插件提供对块的分割进行更多的控制,例如 AggressiveSplittingPluginAggressiveMergingPlugin。这些拆分插件在面向 HTTP/2 的设置时非常方便。
  • 内部 Webpack 依赖于三种块类型:入口块,正常块和初始块。

在下一章中,您将了解代码拆分和按需加载代码。

用户头像
登录后发表评论