Create-react-app在服务工作者更新时重新加载

4

我想修改create-react-app的服务工作器文件并实现弹出消息,询问用户是否更新应用程序,如果有新的服务工作器可以激活。我已经几乎完成了解决方案,但还有一个问题。当用户确认服务工作器更新弹出窗口时,我想重新加载应用程序,因此我已经在register函数的末尾添加了一些代码,如下所示:

export default function register(config) {
  if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
    // The URL constructor is available in all browsers that support SW.
    const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
    if (publicUrl.origin !== window.location.origin) {
      // Our service worker won't work if PUBLIC_URL is on a different origin
      // from what our page is served on. This might happen if a CDN is used to
      // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
      return
    }

    window.addEventListener("load", () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`

      if (isLocalhost) {
        // This is running on localhost. Lets check if a service worker still exists or not.
        checkValidServiceWorker(swUrl, config)

        // Add some additional logging to localhost, pointing developers to the
        // service worker/PWA documentation.
        navigator.serviceWorker.ready.then(() => {
          console.log(
            "This web app is being served cache-first by a service " +
              "worker."
          )
        })
      } else {
        // Is not local host. Just register service worker
        registerValidSW(swUrl, config)
      }

      let preventDevToolsReloadLoop
      navigator.serviceWorker.addEventListener("controllerchange", function() {
        // ensure refresh is called only once
        if (preventDevToolsReloadLoop) {
          return
        }

        preventDevToolsReloadLoop = true
        console.log("reload")
        window.location.reload(true)
      })
    })
  }
}

但是问题在于,即使在第一次访问时还没有任何服务工作者,它仍会重新加载应用程序。我该怎么解决这个问题呢?

在本地状态中将preventDevToolsReloadLoop设置为true,然后在export default function register(config) {之后的顶部取消设置。 - Tyro Hunter
5个回答

18

升级到 react-scripts ^3.2.0。确保您有新版本的serviceWorker.ts或.js。旧版本称为registerServiceWorker.ts,并且注册函数不接受配置对象。请注意,只有在您没有进行懒加载时,此解决方案才能正常工作。

然后在index.tsx中:

  serviceWorker.register({
    onUpdate: registration => {
      alert('New version available!  Ready to update?');
      if (registration && registration.waiting) {
        registration.waiting.postMessage({ type: 'SKIP_WAITING' });
      }
      window.location.reload();
    }
  });
最新版本的ServiceWorker.ts的register()函数接受一个配置对象,其中包含一个回调函数,我们可以在其中处理升级。如果我们发布一条消息SKIP_WAITING,这将告诉服务工作者停止等待并在下次刷新后继续加载新内容。在此示例中,我使用JavaScript alert通知用户。请将其替换为自定义toast。 postMessage函数之所以起作用是因为CRA在内部使用workbox-webpack-plugin,该插件包含SKIP_WAITING侦听器。
更多关于服务工作者的信息: 好的指南:https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68 CRA问题讨论服务工作者缓存:https://github.com/facebook/create-react-app/issues/5316 如果您不使用CRA,则可以直接使用Workbox:https://developers.google.com/web/tools/workbox

我已经解决了,但是忘记发布解决方案了,谢谢! :-) - ketysek
非常干净的解决方案!谢谢! - Alex Cavazos

3
完成 @jfbloom22的答案
由于您可能希望在检测到更新后询问用户时,使用比纯粹的alert更复杂的内容,因此您需要确保在React组件树内部可用并保存registration对象以在用户接受更新后使用(例如,通过单击按钮)。
作为一个选项,在组件中,您可以在全局对象(如document)上创建自定义事件监听器,并在serviceWorker.register()中传递给onUpdate回调时触发此事件,同时传递resgistration对象作为额外数据。
这正是我最近发布的Service Worker Updater所做的事情(一些自我推销)。要使用它,您只需要:
  • 将其添加到依赖项中:
yarn add @3m1/service-worker-updater
  • 在您的index.js中使用它:
import { onServiceWorkerUpdate } from '@3m1/service-worker-updater';

// ...

// There are some naming changes in newer Create React App versions
serviceWorkerRegistration.register({
  onUpdate: onServiceWorkerUpdate
});
  • 在一些React组件中使用它:
import React from 'react';
import { withServiceWorkerUpdater } from '@3m1/service-worker-updater';

const Updater = (props) => {
  const {newServiceWorkerDetected, onLoadNewServiceWorkerAccept} = props;
  return newServiceWorkerDetected ? (
    <>
      New version detected.
      <button onClick={ onLoadNewServiceWorkerAccept }>Update!</button>
    </>
  ) : null; // If no update is available, render nothing
}

export default withServiceWorkerUpdater(Updater);

1
我非常想说感谢这个伟大的模块,我已经实现了它,而且它运行得非常完美! - Alexandre Danault
非常出色的工作。不过有一个问题,如果在更新之前刷新选项卡,则不会收到另一个通知。 - Chris Harrison

0
尝试在 installingWorker.state === 'installed' 中添加重新加载功能。
     if (installingWorker.state === 'installed') {
        if (navigator.serviceWorker.controller) {
          // do something ..
        }
      }

0

对我来说,接受的答案是重新加载后得到了一个空白白屏幕。

添加 await registration.unregister(); 解决了这个问题。

serviceWorker.register({
  onUpdate: async registration => {
    // We want to run this code only if we detect a new service worker is
    // waiting to be activated.
    // Details about it: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
    if (registration && registration.waiting) {
      await registration.unregister();
      // Makes Workbox call skipWaiting()
      registration.waiting.postMessage({ type: 'SKIP_WAITING' });
      // Once the service worker is unregistered, we can reload the page to let
      // the browser download a fresh copy of our app (invalidating the cache)
      window.location.reload();
    }
  },
});

来自https://github.com/facebook/create-react-app/issues/5316#issuecomment-591075209


0

对我来说,来自@jfbloom22的答案也起作用,只是我没有看到主动更新的显示。当我打开一个新的浏览器并进入应用程序时,它是有效的,但如果浏览器已经打开并且有更新发布,就什么也不会发生。(对于那些使用Redux或类似的东西的人,我解决了比alert更好的显示方法,即向一个布尔变量dispatch一个更新,以便我的React组件树可以响应。与这里提到的其他解决方案相同的概念。)

对于主动更新,我像这样更新了我的serviceWorkerRegistration.ts模块:

  navigator.serviceWorker
    .register(swUrl)
    .then((registration) => {

      setInterval(() => {      //ADDED
        registration.update(); //ADDED
      }, 1000 * 60 * 10);      //ADDED

      registration.onupdatefound = () => {
        // ...


每隔10分钟检查一次对我来说已经足够了,但你可以根据自己的需要更新setInterval的等待时间长短。
(特别感谢Medium上的一篇文章,我将其与这里的答案结合起来,得到了一个完整的解决方案。)

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