Webpack 代码分割:ChunkLoadError - 加载块X失败,但是该块存在。

31

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

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

因此,我在网上调查了这个问题,并发现了一些类似的情况,但这些情况都是由于会话期间的发布更新或缓存问题导致的丢失块。

这些情况与我的主要区别在于,从浏览器中实际可达到失败的块,因此加载错误不取决于块哈希后发布后的刷新,但是(我猜),因为某些网络相关的问题。

这一假设得到了支持:约90%的设备都是移动设备

最后,我想问一个问题:我应该用某种方式来处理这个问题,例如重试失败的块加载,还是最好让用户手动刷新?


2021.09.28编辑:

一个月后,这个问题仍然存在,但我没有收到任何用户报告,同时我也在不断地记录Hotjar用户会话,但目前没有发现有意义的信息。

我最近与Sentry支持人员进行了交谈,他们帮助我排除了与网络有关的假设

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

来自Sentry的Rodolfo

我可以确认这个问题非常不寻常,我与您分享另一个有趣的统计数据:从第一次发生以来,受影响的用户是332,227个独立访客中的882人(约占0.26%),但我注意到90%的出现情况是来自iOS(而不是像一个月前我注意到的通用移动设备),因此如果我按照iOS用户计算相同的比例(128,444人中的794人(882的90%)),我们接近0.62%。仍然很小,但在iOS上明显更重要。


2
我们也面临着同样的问题。这些不是旧的块,它们仍然可用并且可以被加载。显然对于一些用户,在某些时刻块不可用。网络问题可能是一个原因,但这似乎应该相当罕见(人们多久才会加载一页,然后失去互联网连接?)很想听听为什么会发生这种情况,如果有好的解决方案也希望能够知道。 - you786
@you786 - 这个代码块有什么特别的地方吗?你有完整的错误堆栈跟踪吗? - Newbie
@新手,不,实际上大多数代码块都相当小。我刚刚检查的一个代码块不到1kb。我有一个堆栈跟踪。第一行是webpack中的一些代码:webpack:///webpack/bootstrap,然后是[native code],接着是我们应用程序定义的lazyImport函数,该函数定义为:return import( /* webpackChunkName: "[request]" */ "../" + moduleName ) - you786
1
谢谢@Morganney 的关注!我知道是因为我尝试访问其中一些块(文件名在错误跟踪中)并且它们都返回了正确的内容。此外,如果问题与哈希值的更改有关,我应该在发布附近看到一些峰值,但错误频率随时间保持恒定。 - 5imone
3
我一直在尝试找出获取ChunkLoadError的原因,这些失败是针对确实存在的块。而且奇怪的是,这些错误被报告到我们的缺陷跟踪器中,这意味着这不是网络问题... 我相信在调试另一个问题时已经发现了潜在的原因。如果我快速地在页面之间切换,块的网络请求会被取消,从而返回ChunkLoadError... 如果能够检测到由于这个原因导致的块失败并忽略该错误,那就太好了。 - cjwiseman11
显示剩余4条评论
3个回答

9

某个代码块是可访问的,并不意味着用户的浏览器可以解析它,例如,如果用户的浏览器版本过旧,但该代码块包含了新的语法。

Webpack通过jsonp方式加载代码块,并将一个 <script> 标签插入到 <head> 中。如果JavaScript代码块文件已经下载但无法解析,则会抛出一个 ChunkLoadError 异常。

您可以按照以下步骤重现此问题:编写一个可选链(optional chain),但不要对其进行编译,并确保这个可选链输出到一个代码块中。

const obj = {};
obj.sub ??= {};

使用 Chrome 79 或 Safari 13.0 打开您的应用程序。完整的错误消息如下所示:

SyntaxError: Unexpected token '?'           // 13.js:2
MAX RELOADS REACHED                         // chunk-load-handler.js:24
ChunkLoadError: Loading chunk 13 failed.    // trackConsoleError.js:25
(missing: http://example.com/13.js)

这是一个非常出色的答案!我简直无法相信 Loading chunk ... failed. (missing: https://... 实际上并不意味着丢失 - 这意味着Webpack认为它丢失了,但这可能是因为浏览器在成功的网络请求被解释时产生了实际的JavaScript错误。在我们的情况下,我们已经从我们的browserslistrc中放弃了旧版本的Chrome,并且在旧浏览器中加载包会导致 Uncaught SyntaxError: Unexpected token '.'. - Tim Krins

5
这很可能是因为浏览器正在缓存你的应用程序的主HTML文件,例如提供webpack捆绑包和清单的index.html
首先请确保您的Web服务器发送正确的HTTP响应头,以不缓存应用程序的index.html文件(假设它被称为那样)。如果您正在使用NGINX,则可以像这样设置适当的标头:
location ~* ^.+.html$ {
  add_header Cache-Control "no-store max-age=0";
}

对于 SPA 来说,这个文件的大小应该相对较小,因此如果您缓存了该应用程序需要的所有其他资源,如 JS 和 CSS 等,则可以不缓存它。您应该在 JS 包上使用内容哈希,以支持这些包的缓存清除(cache busting)。如果这样做了,访问您的站点应该始终包括最新版本的 index.html,以及包含最新代码块名称的最新 webpack 程序清单。

如果您想处理代码块加载错误(Chunk Load Errors),可以设置类似以下的内容:

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

如果您决定重新加载页面,我建议在之前向用户呈现一条消息。

8
我认为这不正确。如果浏览器缓存了index.html和manifest文件,那么它指向的代码块要么已经过时,要么是现有的(取决于是否需要更新该代码块)。正如OP所说,他看到的是一些并没有过时的代码块,但即使这些代码块过时了,出现代码块加载错误也应该是因为给定URL上的代码块不可用 - 但事实上它们是可用的。这就是问题所在 - 为什么我们会在返回正确代码块的URL上看到ChunkLoadErrors? - you786
缓存清单中的块是否仍存在取决于您如何管理资产。您是在每次部署时覆盖资产还是保留旧资产一段时间?也许您正在使用CDN,而某些地区无法访问这些资产?所有这些都说明了OP能够访问出错的块,但并没有确切说明他是如何访问它们或从哪里访问它们的。 - morganney
OP在他的问题中提到:“这些情况和我的主要区别在于,失败的块实际上可以从浏览器访问”。而问题的标题包括“但是块存在”。CDN理论可能是有道理的。 - you786
不幸的是,这不是我的情况,没有CDN。但是,“可以从浏览器访问”有点误导! - 5imone

1
我也遇到了类似的块加载问题。我有一个由aspnet 6.0在IIS中托管的Angular应用程序。`index.html`文件的`cache-control`设置为`max-age=0`,块文件的名称中包含哈希值,例如`main.4591141fc2c0d492.js`,并且`cache-control`设置为`max-age=2592000`。当我为块文件添加了`public`(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#public)指令后,块加载的问题就出现了。问题是不稳定的。通常情况下它工作正常,但是偶尔服务器会返回空文件。空文件是一个错误的块,导致了`ChunkLoadError`。由于这些文件被缓存,问题变得更加严重。重新加载页面无法解决问题,因为损坏(空)文件被缓存了。用户必须清除浏览器缓存才能解决这个问题。
移除`public`缓存指令解决了这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接