如何在捆绑时减少反应应用程序构建时间和理解 webpack 的行为

Posted

技术标签:

【中文标题】如何在捆绑时减少反应应用程序构建时间和理解 webpack 的行为【英文标题】:How to reduce react app build time and understanding behaviour of webpack when bundling 【发布时间】:2021-05-20 06:31:03 【问题描述】:

最近我正在尝试优化 Web 应用程序 (React) 的性能。假设它有点重,因为它由代码编辑器、Firebase、SQL、AWS SDK 等组成。所以我集成了react-loadable,它将延迟加载组件,之后,我遇到了这个 javascript 堆内存不足的问题。

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory in React

经过一些研究(来自朋友),我知道如果我们保留太多延迟加载,webpack 会尝试并行捆绑它们这可能是导致 Javascript 堆内存问题的原因,确认我删除了所有在我的应用程序中延迟加载路线并构建。现在构建成功。后来根据社区的建议,我增加了节点堆空间大小并获得了以下见解

首先我将它增加到 8 GB(8192) 然后构建成功我得到了大约 72 分钟 的构建时间,从下一次开始我会绕过 20 分钟。然后我将堆内存大小减小到 4 GB(4096) 并在 15 - 20 分钟 左右获得构建成功。系统配置为2vCPU、16GB RAM(AWS EC2 Instance r5a.large

接下来,我继续在另一个系统中构建(Mac book pro, i5, 8 GB RAM, 4 Cores)现在花了 30 分钟,第二次花了 20 分钟

所以根据这些数据点,我有几个问题

    每当我们添加一些代码时,我们是否需要不断增加堆空间?如果是,社区中的平均堆内存大小是多少 对于这类繁重的应用程序,构建系统的通常配置是什么,为什么,因为现在我不确定是增加内核数或 RAM 或堆空间,还是与我们的应用程序代码一起做一些事情。李> webpack 是否提供任何类型的解决方案来避免堆内存问题,例如限制并行进程或任何插件? 如果它与我们的应用程序代码有关,是否有任何标准流程来调试它占用内存的位置并基于此进行优化

PS : 有人建议保留GENERATE_SOUCREMAP=false 它已成功,但我们需要源映射,因为它们有助于调试生产问题

【问题讨论】:

好悲痛。这似乎很离谱。在这里,我们担心如果网页加载时间超过半秒,最终用户的可用性会受到影响。但是我们可以容忍不只是几分钟而是几十分钟的构建时间?开发人员的可用性发生了什么变化?当编译时间是即时的时,这怎么能被认为是对框架之前的 Web 开发的改进? 【参考方案1】:

最后,我可以在不增加堆内存空间的情况下解决heap out of memory 问题。

正如问题中提到的,如果我删除所有惰性路由构建正在成功,或者我必须保留 4GB 堆空间才能通过大量构建时间使其成功。当使用 4GB 堆空间构建成功时,我观察到将近 8 - 10 个块文件大小接近 1MB。所以我使用Source map explorer 分析了所有这些块。在所有块中包含几乎相同的库代码(在我的情况下,那些是 Firebase、视频播放器等很重)

所以在我的假设中,当 webpack 尝试捆绑所有这些块时,它必须在每个块中构建所有这些库依赖图,这反过来又会导致堆内存空间问题。所以我使用Loadable components 来延迟加载这些库。

在延迟加载所有这些库之后,所有块文件的大小几乎减少了一半,并且在不增加任何堆空间的情况下构建成功并且构建时间也减少了。

优化后,如果我继续构建 6vCPU , i7 System 大约需要 3 - 4 分钟,我观察到基于系统中可用的核心数量构建时间正在减少。如果我继续构建 2vCPU 系统,有时需要大约 20 - 25 分钟

【讨论】:

如果你想要一个由许多部分组成的“模块化”构建,可以动态加载它的其他部分,而不是一个整体构建,你想使用webpack-module-federation。它可以进一步改进,因为它允许您轻松地在多个包或构建的其他单独部分之间共享依赖关系。它的文档目前仍然很少,但here 是一个很好的入门指南。 如果你还有兴趣,here 是如何调试 Node.js 中的堆分配。【参考方案2】:

Vanilla webpack 是为单体构建而开发的。它的主要目的是获取许多模块并将它们捆绑到一个(不是很多)中。如果你想保持模块化,你想使用webpack-module-federation (WMF):

    WMF 允许您开发可以轻松相互依赖(和延迟加载)的独立包。 这些包将自动在彼此之间共享依赖关系。

如果没有 WMF,webpack 不允许上述任何操作。

简短示例

    一个库包app2提供了一个组件Button 应用程序包app1 使用它。 时机成熟时,app1 使用动态 import 请求组件。 您可以使用React.lazy 包装负载,如下所示:
    const RemoteButton = React.lazy(() => import("app2/Button"));
    
    例如,您可以在 useEffectRoute.render 回调等中执行此操作。 app1 可以使用该组件,一旦它被加载。加载时,您可能希望显示加载屏幕(例如,使用 Suspense):
    <React.Suspense fallback=<LoadingScreen />>
      <RemoteButton />
    </React.Suspense>
    
或者,不使用lazySuspense,只需采用从import(...) 语句返回的承诺,并以您喜欢的任何方式处理异步加载。当然,WMF 完全不限于react,可以动态加载任何模块。

另一方面,WMF 动态加载必须使用动态import(即import(...)),因为:

    非动态导入将始终在加载时解析(从而使其成为非动态依赖项),并且 “动态require”不能被webpack捆绑,因为浏览器没有commonjs的概念(除非你使用一些hack,在这种情况下,你会丢失相关的“加载promise”)。

文档

尽管根据我的经验,WMF 已经成熟、易于使用并且可能已准备好生产,但it's official documentation 目前只是概念性注释的不太完善的集合。这就是为什么我会推荐this as a "Getting Started" guide。

【讨论】:

以上是关于如何在捆绑时减少反应应用程序构建时间和理解 webpack 的行为的主要内容,如果未能解决你的问题,请参考以下文章

仅在 ios 设备上反应本机“不存在捆绑 URL”

创建反应原生项目时如何定义自己的包名称/捆绑标识符

如何将 React 应用程序与汇总捆绑在一起

Spring WebFlux 教程:如何构建反应式 Web 应用程序

如何在发布前使用 azure 管道在 Web 应用程序中复制反应构建文件?

SPDY 是不是需要资源捆绑以减少响应时间