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

How to reduce react app build time and understanding behaviour of webpack when bundling

最近我正在尝试优化网络应用程序 (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 堆内存问题的原因,确认我删除了我的应用程序中的所有延迟加载路由并构建了。现在构建成功了。后来根据社区的建议,我增加了节点堆 space 大小并获得了以下见解

首先我将其增加到 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分钟

根据这些数据点,我得到了几个问题

  1. 我们是否需要在每次添加一些代码时不断增加堆 space?如果是,社区中的平均堆内存大小是多少
  2. 对于这些类型的重型应用程序,构建系统的通常配置是什么,为什么因为现在我不确定是否增加内核或 RAM 或堆的数量 space 或总的来说与我们的应用代码。
  3. webpack 是否提供任何类型的解决方案来避免堆内存问题,例如限制并行进程或任何插件?
  4. 如果它与我们的 App 代码有关,是否有任何标准流程可以调试占用内存的位置并据此进行优化

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

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

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

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

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

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

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

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

没有 WMF,webpack 允许 none 以上。

简短示例

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

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

  1. 非动态导入将始终在加载时解析(因此使其成为非动态依赖项),并且
  2. "dynamic require" 不能被 webpack 打包,因为浏览器没有 commonjs 的概念(除非你使用一些 hack,在这种情况下,你会丢失相关的 "loading promise").

文档

尽管根据我的经验,WMF 成熟、易于使用,并且可能已准备好投入生产,it's official documentation is currently only a not all too polished collection of conceptual notes. That is why I would recommend