跳到内容
返回博客

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
});

之前为所有资源并发运行该功能的代码

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 中,资源通常是 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 的内存使用率和性能。