通过精细分块提升 Next.js 和 Gatsby 网页加载性能

Next.js 和 Gatsby 中更新的 webpack 分块策略可最大限度地减少重复代码,从而改善网页加载性能。

Chrome 正在与工具协作 JavaScript 开源生态系统中的各种框架和工具。最近,有一些较新的优化措施 添加了此模块,以提高 Next.jsGatsby。本文介绍了一种改进后的精细分块策略 目前已在两个框架中默认提供。

简介

与许多 Web 框架一样,Next.js 和 Gatsby 使用 webpack 作为核心。 打包器引入了 webpack v3 CommonsChunkPlugin,即可 单个(或几个)“公共”中不同入口点之间共享的输出模块数据块(或 数据块)。共享代码可以单独下载,并尽早存储在浏览器缓存中, 从而提升加载性能

许多单页应用框架采用入口点和 bundle 配置,如下所示:

通用入口点和软件包配置

将所有共享模块代码捆绑到单个块的概念虽然可行,但 限制。对于没有在每个入口点共享的模块,可以为不使用它的路由下载 这会导致不必要的代码下载。例如,当 page1 加载时 common 代码块,它会加载 moduleC 的代码,即使 page1 不使用 moduleC 也是如此。 因此,与一些其他原因一样,webpack v4 删除了该插件,取而代之的是 一个:SplitChunksPlugin

改进的分块

SplitChunksPlugin的默认设置适合大多数用户。多个拆分区块 根据许多条件创建的 以防止跨多个路由提取重复的代码。

不过,许多使用此插件的 Web 框架仍然遵循“单通用”框架,分块分析法 拆分。例如,Next.js 会生成一个 commons 软件包,其中包含 在超过 50% 的页面和所有框架依赖项(reactreact-dom 等)中使用。

const splitChunksConfigs = {
  …
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

尽管将依赖于框架的代码添加到共享块中意味着这些代码可以 针对任何入口点进行缓存,基于使用情况的启发法,包括 半个网页并不是很有效。修改此比率只会导致以下两种结果之一:

  • 如果您降低该比率,则会下载更多不必要的代码。
  • 如果您提高该比率,则更多代码会在多条路线中重复。

为了解决这个问题,Next.js 采用了 SplitChunksPlugin配置, 不必要的代码。

  • 任何足够大的第三方模块(大于 160 KB)会拆分为单独的模块 数据块
  • 系统会为框架依赖项(reactreact-domframeworks 依此类推)
  • 可根据需要创建任意数量的共享分块(最多 25 个)
  • 生成的分块的最小大小更改为 20 KB

这种精细的分块策略具有以下优势:

  • 缩短网页加载时间。发出多个共享数据块,而不是一个 可最大限度减少任何入口点不需要(或重复)的代码量。
  • 改进了导航期间的缓存。拆分大型库和框架依赖项 可降低缓存失效的可能性,因为两者都不太可能 更改,直到升级完成为止。

您可以在 webpack-config.ts 中查看 Next.js 采用的完整配置。

更多 HTTP 请求

SplitChunksPlugin 定义了精细分块的基础,并将此方法应用于 像 Next.js 这样的框架并不是一个全新的概念。然而,许多框架仍然继续 使用单一启发法和“公共”部署捆绑策略的原因有很多其中包括 HTTP 请求数量增加可能会给网站性能带来负面影响。

浏览器只能打开有限数量的指向单个源的 TCP 连接(Chrome 为 6 个),因此 尽可能减少捆绑器输出的区块数量可确保 必须低于此阈值但是,这仅适用于 HTTP/1.1。HTTP/2 中的多路复用 允许使用单个连接通过单个端口并行流式传输多个请求, 来源。也就是说,我们通常无需担心数据块数量 由打包器发出的

所有主流浏览器均支持 HTTP/2。Chrome 团队和 Next.js 团队 我想看看能否通过拆分 Next.js 的单个“commons”来增加请求数套装 拆分为多个共享数据块,都会以任何方式影响加载性能。他们首先衡量了 同时使用 maxInitialRequests 属性。

请求次数增加时网页加载性能

在单个网页上平均运行三次多次试验后, load, start-renderFirst Contentful Paint 次的次数都大致相同 请求数(从 5 到 15)。有趣的是,我们发现,在性能方面 经过激进地拆分为数百个请求之后。

数百个请求的网页加载性能

这表明,保持在可靠的阈值(20~25 个请求)以下实现了适当的平衡 加载性能和缓存效率之间取得的平衡。经过一些基准测试后,选择了 25 个 maxInitialRequest 计数。

修改并行发生的请求数上限会导致产生多个 并针对每个入口点适当地分离它们,显著降低了 为同一网页添加大量不需要的代码。

通过增加分块减少 JavaScript 载荷

此实验只是为了修改请求数量 对网页加载性能产生负面影响。结果建议将 maxInitialRequests 设置为 测试页上的25是最佳选择,因为它减少了 JavaScript 载荷大小,并且不会减慢速度 向下滚动水合网页所需的 JavaScript 总量仍保留 大致相同,这解释了为什么网页加载速度降低后, 代码量。

webpack 生成区块的默认最小大小为 30 KB。不过,将一个 maxInitialRequests 值为 25,最小 20 KB 可获得更好的缓存。

使用精细数据块缩减大小

包括 Next.js 在内的许多框架依靠客户端路由(由 JavaScript 处理)来注入 新的脚本标记。但他们如何在构建时预先确定这些动态块呢?

Next.js 使用服务器端构建清单文件来确定使用了哪些输出分块 不同的入口点为了将这些信息也提供给客户端,我们提供了一个简化的客户端 创建了 build 清单文件,以映射每个入口点的所有依赖项。

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Next.js 应用中多个共享分块的输出。

这种较新的精细分块策略最初是在 Next.js 中基于一个标志推出的,在该版本中,该策略在 早期使用者的数量。许多开发者发现其网站所用的 JavaScript 总 整个网站:

网站 总体 JS 变化 差异百分比
https://www.barnebys.com/ -238 KB -23%
https://sumup.com/ -220 KB -30%
https://www.hashicorp.com/ -11 MB -71%
缩减 JavaScript 大小 - 在所有路由上(已压缩)

默认情况下,最终版本会搭载版本 9.2

盖茨比

Gatsby 使用的方法与使用基于用量的方法相同, 启发法:

config.optimization = {
  …
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

通过优化其 webpack 配置以采用类似的精细分块策略,他们还 发现许多大型网站显著减少了 JavaScript:

网站 总体 JS 变化 差异百分比
https://www.gatsbyjs.org/ -680 KB -22%
https://www.thirdandgrove.com/ -390 KB -25%
https://ghost.org/ -1.1 MB -35%
https://reactjs.org/ -80 KB -8%
缩减 JavaScript 大小 - 在所有路由上(已压缩)

查看公共关系,了解他们是如何 已在他们的 webpack 配置中实现此逻辑,v2.20.7 中默认提供该配置。

总结

分发颗粒块的概念并非特定于 Next.js、Gatsby 甚至 webpack。所有人 如果应用遵循大量“公共”政策,则应考虑改进其应用的分块策略 bundle 方法,无论使用的是框架还是模块打包器。

  • 如果您想看到原版 React 应用同样采用相同的分块优化, 请参阅这个示例 React app。它使用 简化版精细分块策略,并且可以帮助您开始应用相同的 逻辑等
  • 对于 Rollup,系统会默认精细地创建分块。请查看 manualChunks(如果您要手动 配置行为