在 Service Worker 更新时重新加载 Create-react-app

问题描述 投票:0回答:5

我想修改

create-react-app
Service Worker file 并实现弹出消息,如果新的 Service Worker 已准备好激活,将要求用户更新应用程序。我几乎完成了解决方案,但有一个陷阱。我想在用户确认 Service Worker 更新弹出窗口时重新加载应用程序,因此我在
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)
      })
    })
  }
}

但问题是,当尚不存在任何 Service Worker 时,它也会在第一次访问时重新加载应用程序。我该如何解决?

reactjs service-worker create-react-app
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 警报来通知用户。请用定制的吐司替换它。

这个

postMessage
函数起作用的原因是因为 CRA 在底层使用
workbox-webpack-plugin
,其中包括一个
SKIP_WAITING
侦听器。

有关 Service Worker 的更多信息

好的指南:https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68

讨论 Service Worker 缓存的 CRA 问题:https://github.com/facebook/create-react-app/issues/5316

如果您没有使用CRA,可以直接使用workbox:https://developers.google.com/web/tools/workbox


3
投票

完成@jfbloom22的回答

正如您可能想在检测到比普通

alert
更复杂的更新后询问用户,您需要确保
registration
对象可从 React 的组件树内部获取,并将其保存以在用户接受更新(例如,通过单击按钮)。

作为一个选项,在组件中,您可以在

document
等全局对象上创建自定义事件侦听器,并在
onUpdate
回调传递给
serviceWorker.register()
时触发此事件,将
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);

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上的一篇文章,我将其与此处的答案结合起来,以获得完整的解决方案。)

© www.soinside.com 2019 - 2024. All rights reserved.