How to implement skipWaiting with Create React App

2020-05-23 09:28发布

问题:

I would like users to be able to update on the spot when a new service worker is available and waiting? I'm already able to show a pop up when new update is available but I would like to add a button to force update on the spot. I understand this can be achieved with calling skipWaiting but not sure how to implement it with a Create React App. Have anyone able to achieve this? Would appreciate the help. Thank you!

回答1:

The CRA build\service-worker.js file now (v3 plus) includes code to handle skipWaiting:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

The register function serviceWorker.js file that is called in index.js now accepts a config parameter:

serviceWorker.register({
  onUpdate: registration => {
    const waitingServiceWorker = registration.waiting

    if (waitingServiceWorker) {
      waitingServiceWorker.addEventListener("statechange", event => {
        if (event.target.state === "activated") {
          window.location.reload()
        }
      });
      waitingServiceWorker.postMessage({ type: "SKIP_WAITING" });
    }
  }
});

This will skip waiting and then refresh the page once the update has been applied.



回答2:

At serviceWorker.js file can find this code

if (config && config.onUpdate) {
    config.onUpdate(registration);
 }

So implement the config.onUpdate funtion

Create a file swConfig.js

export default {
 onUpdate: registration => {
   registration.unregister().then(() => {
   window.location.reload()
 })
},
onSuccess: registration => {
  console.info('service worker on success state')
  console.log(registration)
 },
}

At index.js send the implement function to serviceWorker

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import swConfig from './swConfig'

ReactDOM.render(<App />, 
document.getElementById('root'));
serviceWorker.register(swConfig);

Check out this repo https://github.com/wgod58/create_react_app_pwaupdate



回答3:

I used a package called https://github.com/bbhlondon/cra-append-sw to append the following code to call trigger skipWaiting:

self.addEventListener('message', event => {
  if (event.data === 'skipWaiting') {
    self.skipWaiting();
  }
});


回答4:

the answer from @user10532439 did not work for me. I ended up using https://github.com/bbhlondon/cra-append-sw and adding

// @ts-ignore
workbox.skipWaiting();

// @ts-ignore
workbox.clientsClaim();

along with

"build": "react-scripts build && cra-append-sw -s ./src/custom-sw.js",


回答5:

I know that it has been answered but I found a solution that does not require any additional packages except workbox of course.

at the package.json :

"build": "react-scripts build && node ./src/serviceWorkerBuild"

serviceWorkerBuild.js

const workboxBuild = require('workbox-build');

// NOTE: This should be run *AFTER* all your assets are built
const buildSW = () => {
  // This will return a Promise
  return workboxBuild.injectManifest({
    swSrc: 'src/serviceWorkerRules.js',
    swDest: 'build/sw.js',
    globDirectory: 'build',
    globPatterns: [
      '**\/*.{js,css,html,png}',
    ]
  }).then(({count, size, warnings}) => {
    // Optionally, log any warnings and details.
    warnings.forEach(console.warn);
    console.log(`${count} files will be precached, totaling ${size} bytes.`);
  });
};

buildSW();

and serviceWorkerRules.js where you can add your rules:

importScripts(
    'https://storage.googleapis.com/workbox-cdn/releases/3.5.0/workbox-sw.js'
);
/* global workbox */
if (workbox) {
    console.log('Workbox is loaded');

  self.addEventListener('install', event => {
    self.skipWaiting();
  });

    /* injection point for manifest files.  */
    workbox.precaching.precacheAndRoute([]);

/* custom cache rules*/
workbox.routing.registerNavigationRoute('/index.html', {
    blacklist: [/^\/_/, /\/[^\/]+\.[^\/]+$/],
    });

workbox.routing.registerRoute(
    /\.(?:png|gif|jpg|jpeg|svg)$/,
    workbox.strategies.cacheFirst({
        cacheName: 'images',
        plugins: [
            new workbox.expiration.Plugin({
                maxEntries: 60,
                maxAgeSeconds: 5 * 24 * 60 * 60, // 5 Days
            }),
        ],
    }),
    );

Also add window.location.reload(); at serviceWorker.js at line #78.



回答6:

You should call the skipWaiting method in the service worker's install event. The service worker is decoupled from the UI/React code. So nothing do do with react really.

Right now I have a pattern where I refactored the install event code to its own method, so my service workers tend to look like this:

self.addEventListener( "install", function ( event ) {

    event.waitUntil( installServiceWorker() );

} );

function installServiceWorker() {

    self.skipWaiting();
// do stuff here like pre-caching site core assets
//most likely returning a promise

}

Also: donb't call skipWaiting if you need to sync cached assets with the active UI. If your service worker is being updated and could possibly break the UI then don't call skipWaiting. Instead wait for the user to refresh the page. You could use the messaging infrastructure to inform the user an update is available have them refresh as an example.