Webpack 代码拆分:ChunkLoadError - 加载块 X 失败,但块存在

Posted

技术标签:

【中文标题】Webpack 代码拆分:ChunkLoadError - 加载块 X 失败,但块存在【英文标题】:Webpack code splitting: ChunkLoadError - Loading chunk X failed, but the chunk exists 【发布时间】:2021-11-01 22:37:30 【问题描述】:

几天前我已将 Sentry 与我的网站集成,我注意到有时用户会在其控制台中收到此错误:

ChunkLoadError: Loading chunk <CHUNK_NAME> failed.
(error: <WEBSITE_PATH>/<CHUNK_NAME>-<CHUNK_HASH>.js)

所以我在网上调查了这个问题,发现了一些类似的案例,但与会话期间的发布更新或缓存问题导致的块丢失有关。

这些情况和我的主要区别在于失败的块实际上可以从浏览器访问,因此加载错误不取决于块哈希的发布后刷新,而是 (我猜) em>,来自一些与网络相关的问题。 此统计数据强化了这一假设:所涉及的设备中约有 90% 是移动设备

最后,我提出了一个问题:我是否应该以某种方式解决问题(例如,如果失败则重试块加载) 还是最好直接忽略它并让用户手动刷新?


2021.09.28 编辑:

一个月后,问题仍然存在,但我没有收到用户的任何报告,而且我一直在用 Hotjar 记录用户会话,但到目前为止没有发现任何相关内容。

我最近与 Sentry 支持人员聊天,帮助我排除了与网络相关的假设

我们的 React SDK 默认没有离线缓存,当捕获到错误时,它将在那个时候发送。如果应用无法连接到 Sentry 发送事件,该事件将被丢弃,SDK 将不再尝试发送。

来自哨兵的鲁道夫

我可以确认这个问题很不寻常,我与您分享另一个有趣的统计数据:自第一次出现以来受影响的用户是 332.227 名唯一访问者中的 882 名 (~0.26%),但我注意到 90% 的事件来自 iOS(不是我一个月前注意到的通用移动设备),所以如果我计算与 ios 用户相同的比例(794(882 的 90% )在 128.444 中)我们接近 0.62%。仍然很小,但在 iOS 上肯定更相关。

【问题讨论】:

我们也面临同样的问题。这些不是不再存在的旧块,这些块肯定仍然可用并且可以加载。显然对于某些用户来说,该块在某些时候不可用。网络问题可能是一个原因,但这似乎应该是相当罕见的(人们多久加载一次页面,然后失去互联网连接?)很想知道为什么会发生这种情况以及是否有任何好的解决方案。 @you786 - 这个块有什么特别像大的吗?你有错误的完整堆栈跟踪吗? @Newbie,不,实际上大多数块都很小。我刚刚检查的一个小于 1kb。我确实有一个堆栈跟踪。第一行是webpack中的一些代码:webpack:///webpack/bootstrap,然后是[native code],然后我们app定义了一个lazyImport函数,定义为:return import( /* webpackChunkName: "[request]" */ "../" + moduleName ) 你能复制这个问题吗?目前,我只能做一个假设。惰性块准备好后,它应该调用在请求它的文件中定义的函数。如果调用该函数,则块状态将从 loading 更改为 done。但是,如果未调用该 fn,则会引发该错误。例如,如果您在main.js 文件中有动态import(),则输出的文件对应。到main.js 将具有负责引发该错误的逻辑。 感谢@morganney 的关注!我知道这是因为我试图访问其中一些块(文件名在错误跟踪中)并且它们都返回了正确的内容。此外,如果问题与不断变化的哈希有关,我应该会在版本附近看到一些峰值,而不是错误频率,它会随着时间的推移保持不变。 【参考方案1】:

这很可能发生,因为浏览器正在缓存您应用的主 html 文件,例如为 webpack 包和 ma​​nifest 提供服务的index.html

首先,我会确保您的 Web 服务器发送正确的 HTTP 响应标头以不缓存应用程序的 index.html 文件(假设它被称为该文件)。如果您使用的是 nginx,您可以像这样设置适当的标头:

location ~* ^.+.html$ 
  add_header Cache-Control "no-store max-age=0";

对于 SPA,此文件的大小应该相对较小,因此只要您缓存应用程序所需的所有其他资产(如 JS 和 CSS 等),就可以不缓存它。您应该使用内容您的 JS 捆绑包上的哈希以支持对这些捆绑包的缓存破坏。有了这些,对您网站的访问应该始终包含最新版本的 index.html 以及最新资产,包括记录块名称的最新 webpack 清单。

如果你想处理块加载错误,你可以这样设置:

import  ErrorBoundary  from '@sentry/react'

const App = (children) => 
  <ErrorBoundary
    fallback=( error, resetError ) => 
      if (/ChunkLoadError/.test(error.name)) 
        // If this happens during a release you can show a new version alert
        return <NewVersionAlert />

        // If you are certain the chunk is on your web server or CDN
        // You can try reloading the page, but be careful of recursion
        // In case the chunk really is not available
        if (!localStorage.getItem('chunkErrorPageReloaded')) 
          localStorage.setItem('chunkErrorPageReloaded', true)
          window.location.reload()
        
      

      return <ExceptionRedirect resetError=resetError />
  >
    children
  </ErrorBoundary>

如果您决定重新加载页面,我会事先向用户显示一条消息。

【讨论】:

我不认为这是正确的。如果浏览器正在缓存 index.html 和清单,那么它将指向的块将是过时的或现有的(取决于该块是否需要更新)。正如 OP 所说,他看到的块是没有过时的......但即使它们已经过时,对于发生块加载错误,块不应该在给定的 URL 上可用 - 但它们是。这就是重点 - 为什么我们会看到返回正确块的 URL 的 ChunkLoadErrors? 缓存清单的块是否仍然存在取决于您管理资产的方式。您是否为每个部署覆盖您的资产,或者您是否将旧的资产保留一段时间?也许您正在使用 CDN 并且资产在特定地区不可用?所说的只是 OP 能够访问出错的块,但不完全是他如何访问它们或从哪里访问它们。 他的问题中提到的OP:The main difference between these cases and mine is that the failed chunks are actually reachable from the browser。问题的标题包括“但该块存在”。不过,CDN 理论可能是有道理的。 很遗憾这不是我的情况,没有CDN。但是,是的,reachable from the browser 有点误导!

以上是关于Webpack 代码拆分:ChunkLoadError - 加载块 X 失败,但块存在的主要内容,如果未能解决你的问题,请参考以下文章

Webpack 在目标节点时忽略代码拆分

用于代码拆分的 Webpack 配置不适用于生产构建

Webpack 代码拆分影响 Web 性能

如何使用 Webpack 拆分应用程序和供应商代码

使用 Webpack 2 和 React Router 进行 CSS 代码拆分

webpack 代码拆分,按需加载