跳到内容
返回博客

2019 年 2 月 19 日星期二

Next.js 8 Webpack 内存改进

发布者

最近,Next.js 8 发布了。此版本包含了构建时内存使用量的巨大减少。这篇博客文章将探讨我们如何帮助优化 webpack 以造福社区。

Next.js 是零配置的,并建立在 webpackBabel 等工具之上。它的目标是帮助您专注于重要的事情:您的应用程序代码。

现代 Web 应用程序由一个或多个页面组成。例如,主页、博客、仪表板或产品列表。

使用 Next.js,这些页面成为项目根目录中特殊 pages 目录中的文件。

例如:文件 pages/about.js 映射到 URL /about

该框架的关键设计约束之一是它必须同时适用于单个页面和数千个页面。

在实现 Serverless Next.js 时,很快就发现,在包含数百个页面的项目上运行 next build 会导致高内存使用率。有时会超过 Node.js 大约 1.4 GB 的内存堆限制。

我们开始使用 Chrome 开发者工具分析构建过程的内存使用情况。

在生成的配置文件中,我们发现 webpack 会一次性分配 548 MB 内存。

分配的内存量与页面数量直接相关,这意味着页面越多,内存使用量越大。

The Chrome Developer Tools memory profiler showed 548 MB being allocated at once
Chrome 开发者工具内存分析器显示一次性分配了 548 MB

通过查看内存配置文件的堆栈跟踪,我们能够追踪到导致内存分配激增的函数。

分配本身来自 source.source() 方法的调用,该方法生成结果文件并将其存储到内存中。

然而,通过进一步查看调用 source() 方法的函数,您可以看到 compilation.assets 正在使用 asyncLib.forEach 进行迭代。这意味着 提供的函数 将同时为 compilation.assets 数组中的每个文件调用。

因此,这意味着如果例如有 100 个页面,并且每个页面都必须写入磁盘,则上面的代码将尝试同时写入所有 100 个页面,包括同时生成所有 100 个文件。

解决此问题的方案是使用信号量来限制并发写入的数量。通常我们为此使用async-sema,但在这种情况下,webpack 已经在neo-async上提供了合适的方法。

asyncLib.forEach(compilation.assets, (source, file, callback) => {
  // etc
});

之前并发运行所有 asset 的函数代码

asyncLib.forEachLimit(compilation.assets, 15, (source, file, callback) => {
  // etc
});

新的代码,最多同时运行 15 个函数

在实现此并发限制并再次分析构建内存使用情况后,我们看到内存分配被分成更小的 34 MB 块。

The profiler now showed chunks of 34 MB being allocated over time
分析器现在显示随着时间的推移分配了 34 MB 的块。

此更改显示了非常有希望的结果,但实际上构建仍然内存不足,因此我们继续分析和调查此问题。

通过进一步检查内存配置文件,我们注意到在调用 source.source() 方法 后,内存并未被清理(垃圾回收)。

在 webpack 中,assets 通常是 Source 类 的实例。这些类都实现了一个 source() 方法,该方法将生成文件源。

配置文件显示许多资产是 CachedSource 的实例。CachedSource 的工作方式是,当调用 source() 时,结果会缓存在内存中,直到资产被释放。

检查 Next.js 使用的 webpack 插件显示,在 webpack 写入文件后,我们没有任何插件调用 source(),这意味着缓存写入的值没有任何好处。

Tobias Koppers 合作 后,他实现了一个名为 output.futureEmitAssets 的新选项,允许选择新的资产写入行为。

采用这种新行为后,随着时间的推移,分配的块减少到 182 KB

After all optimizations the profiler shows chunks of 184 KB being allocated over time
经过所有优化后,分析器显示随着时间的推移分配了 184 KB 的块。

Next.js 8 已经内置了所有这些优化。使用 Next.js 时无需更改任何内容。

此优化是在 webpack 上引入的,这意味着不仅 Next.js 用户,所有 webpack 用户都将受益于这些优化。

我们将积极地继续改进 Next.js 和 webpack 的内存使用和性能。