无法获取动态导入的模块:

mah*_*han 18 javascript reactjs vite

我有一些在App.tsx. App.tsx用于Index.tsx渲染并附加到body.

   const IndexPage = lazy(() => import("../features//IndexPage"));
    const TagsPage = lazy(
      () => import("../features/tags/TagsPage")
    );
    const ArticlePage = lazy(() => import("../features/article/ArticlePage"));

    const SearchResultPage = lazy(
      () => import("../features/search-result/SearchResultPage")
    );

    const ErrorPage = lazy(() => import("../features/error/ErrorPage"));

    ----

    <BrowserRouter basename={basename}>
      <Suspense fallback={<Fallback />}>
        <Routes>
          <Route path={INDEX} element={<IndexPage />} />
          <Route path={ARTICLE} element={<ArticlePage />} />
          <Route path={TAGS} element={<TagsPage />} />
          <Route path={SEARCH} element={<SearchResultPage />} />
          <Route path={ERROR} element={<ErrorPage />} />
          <Route path="/*" element={<ErrorPage />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
Run Code Online (Sandbox Code Playgroud)

在生产中经常会出现以下错误。

无法获取动态导入的模块:

所有路线都发生过这种情况。

 https://help.example.io/static/js/SearchResultPage-c1900fe3.js

 https://help.example.io/static/js/TagsPage-fb64584c.js

 https://help.example.io/static/js/ArticlePage-ea64584c.js

 https://help.example.io/static/js/IndexPage-fbd64584c.js
Run Code Online (Sandbox Code Playgroud)

我已经改变了构建路径。因此它是/static/js

  build: {
    assetsInlineLimit: 0,
    minify: true,
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          var info = assetInfo.name.split(".");
          var extType = info[info.length - 1];
          if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
            extType = "img";
          } else if (/woff|woff2/.test(extType)) {
            extType = "css";
          }
          return `static/${extType}/[name]-[hash][extname]`;
        },
        chunkFileNames: "static/js/[name]-[hash].js",
        entryFileNames: "static/js/[name]-[hash].js",
      },
    },
    outDir: "./../backend/src/main/resources/static/articles/",
    emptyOutDir: true,
  },
Run Code Online (Sandbox Code Playgroud)

有人知道如何解决这个问题吗?

更新:

我在开发中从来没有遇到过这个错误。我使用Sentry来跟踪错误。两个月内至少发生了274次。

这就是 Sentry 上的全部内容。

{
  arguments: [
    {
      message: Failed to fetch dynamically imported module: https://help.example.io/static/js/SearchResultPage-c1900fe3.js,
  name: TypeError,
    stack: TypeError: Failed to fetch dynamically imported module: https://help.example.io/static/js/SearchResultPage-c1900fe3.js
  }
],
  logger: console 
}
Run Code Online (Sandbox Code Playgroud)

更新

过去两个月我们的访问量达到了 50 万。这样的事发生了 274 次。既然tracesSampleRate0.3,那绝对不止于此。

  Sentry.init({
    dsn: "",
    integrations: [new BrowserTracing()],
    tracesSampleRate: 0.3,
  });

Run Code Online (Sandbox Code Playgroud)

这种情况在各种浏览器上都发生过,但主要是在 Chrome 上。

我无法在开发或产品中重现它。

更新

我终于重现了这个bug。如果您在某个页面上并且发布了新版本,就会发生这种情况。包含动态导入模块的文件不再存在,例如:

https://help.example.io/static/js/IndexPage-fbd64584c.js
Run Code Online (Sandbox Code Playgroud)

上面的链接返回404

M. *_*ant 6

以下是对此问题的一些替代看法。

完全回避这个问题。

这是显而易见的,我怀疑从您的角度来看这可能不是一个理想的答案,但切换到静态导入将解决您的问题。

在许多情况下,即使使用静态导入,包的大小仍然足够小,不会影响您的用户体验,并且绝对比遇到错误要小。

这也可以说是解决问题的唯一有保证的方法,因为即使您的问题是由您设法追踪和修复的某个地方的故障引起的,仍然存在发生此问题的合法情况。

优雅地管理错误

另一方面,仍然认为错误可能并且仍然会发生,React 具有 ErrorBoundaries 的概念,您可以利用它来处理发生时的用户体验:https ://reactjs.org/docs/ error-boundaries.htmlhttps://reactjs.org/docs/code-splitting.html#error-boundaries

把事情掌握在自己手中

最后,Reactlazy需要一个返回解析为模块的 Promise 的函数。如果没有,那就是你的类型错误。显然,它的使用方式与您完全相同,但如果您愿意,您可以自由地将其包装在更智能的东西中。您需要做的就是确保承诺最终解析为导入的模块。如需灵感,请参阅此处有关重试 Promise 的一些看法。Promise 重试设计模式


oli*_*ren 5

请注意,我将下面的缓存清除重试逻辑包装到其自己的独立库中,因此您可以将其替换import('./my-module.js')

import {dynamicImportWithRetry} from '@fatso83/retry-dynamic-import'
...
const myModule = await dynamicImportWithRetry( () => import('./my-module.js'))
Run Code Online (Sandbox Code Playgroud)

此错误是预料之中的,并不神秘,而且是设计使然(HTML 规范):

HTML 规范要求缓存失败的动态导入。

这是相应的 Chromium 单元测试,确保它实际上像这样工作。

因此,这意味着,如果请求由于多种原因(上面列出了其中许多原因)而失败,您的 SPA 将陷入进退两难的境地。这适用于任何使用动态导入的框架、库或其他方法。它不是特定于 React 的。

不幸的是,对于受影响的用户来说,自动解决问题并不总是那么容易,因为对于某些浏览器来说,它会粘住- 重新引导页面和重新启动浏览器都将使用已解决的网络故障。2023 年 5 月,Chrome 中会发生这种情况,但 Safari 和 Firefox 不会出现这种情况,重新加载也会重新获取失败的动态导入(至少在我的测试中)。

以下是一些背景阅读:

您可以通过设置 TLS 代理并根据路径重写状态代码,使用 Charles Proxy 来重现和使用此内容。这是一个这样的例子: 使用 Charles proxy 进行复制

在验证修复时,我发现使用Charles Proxy 的断点功能非常方便:在第一次命中时手动使其失败,然后在后续请求中允许它。

其他答案之一暗示使用与React.lazy(). 这是我目前采用的方法:用React.lazy()重试的包装函数替换调用。

有些方法比其他方法效果更好:

  • 简单地重新加载应用程序(页面)将不起作用(出于上述原因,React LazyLoad 要点
  • 缓存清除将会起作用

可以在本文中找到一个简单的缓存破坏动态导入包装器的实现,它也是一个用于使用它的存储库

没什么花哨的(链接库中提供了我后来想出的更通用的跨浏览器方法)。

export const lazyWithRetries: typeof React.lazy = (importer) => {
  const retryImport = async () => {
    try {
      return await importer();
    } catch (error: any) {
      // retry 5 times with 2 second delay and backoff factor of 2 (2, 4, 8, 16, 32 seconds)
      for (let i = 0; i < 5; i++) {
        await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** i));
        // this assumes that the exception will contain this specific text with the url of the module
        // if not, the url will not be able to parse and we'll get an error on that
        // eg. "Failed to fetch dynamically imported module: https://example.com/assets/Home.tsx"
        const url = new URL(
          error.message
            .replace("Failed to fetch dynamically imported module: ", "")
            .trim()
        );
        // add a timestamp to the url to force a reload the module (and not use the cached version - cache busting)
        url.searchParams.set("t", `${+new Date()}`);

        try {
          return await import(url.href);
        } catch (e) {
          console.log("retrying import");
        }
      }
      throw error;
    }
  };
  return React.lazy(retryImport);
};
Run Code Online (Sandbox Code Playgroud)

注意:上述方法与基于 Chromium 的浏览器(Edge、Chrome 等)生成的确切错误消息相匹配,因此它在 Firefox、Safari 等中不起作用。在我上面链接到的库中,我发现了另一种应该有效的方法跨浏览器查找模块名称,但它做出的一些假设在某些环境中可能会被破坏。


Z. *_*Ben 4

如果没有所有数据,很难直接回答这个问题,但我会尽力并分享一些与此相关的资源。

正如评论中所讨论的,发生这种情况可能有很多原因。由于您无法重现,并且它不是恒定的,因此我们可能可以排除它来自您的代码或部署配置的理论。

我脑子里有几个原因:

  • 尝试获取资源时网络速度很慢。您可以尝试通过限制网络来重现。
  • 解析错误或遇到异常

我相信这个错误类似于“ChunkLoadError”(在许多其他框架中也面临这个错误,而不仅仅是React),附加一个类似的问题:Webpack code splitting: ChunkLoadError - Loading chunk X failed, but the chunk isn't as well相同数量的流量失败等

解决此问题的一种尝试是尝试捕获错误并强制重新加载以重新获取资源,但请确保不要在那里循环。

我希望这有帮助。