GoogleChromeLabs / sw-precache

[Deprecated] A node module to generate service worker code that will precache specific resources so they work offline.
https://developers.google.com/web/tools/workbox/guides/migrations/migrate-from-sw
Apache License 2.0
5.22k stars 388 forks source link

cache index.html for offline, but refresh on new deploy #356

Open phun-ky opened 6 years ago

phun-ky commented 6 years ago

I am currently using the sw-precache-webpack-plugin with this config: (yes, another project, this is just to show the config, since some of the configuration options are passed to sw-precache + sw-toolbox). This is a SPA.

new SWPrecacheWebpackPlugin({
  cacheId: 'project',
  filename: 'project-sw.js',
  minify: true,
  maximumFileSizeToCacheInBytes: 3500000,
  filepath: path.join(assetsDir, '/project-sw.js'),
  staticFileGlobs: ['dist/assets/index.html'],
  mergeStaticsConfig: true,
  runtimeCaching: [
    {
      urlPattern: /\/api\//,
      handler: 'networkFirst'
    }
  ],
  staticFileGlobsIgnorePatterns: [/project-sw\.js$/i],
  stripPrefix: 'dist/assets/',
  navigateFallback: '/index.html',
  navigateFallbackWhitelist: [/^((?!adomain|anotherdomain).)*$/]
})

We initialize the service worker like this in an attempt to always load the new service worker when we deploy:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/project-sw.js?build=' + __webpack_hash__);
}

Operation-wise, it works as intended. But, we cache index.html for offline usage, and this also caches the reference for the app file, app.<__webpack_hash__>.js, leading to the wrong bundles being requested.

Scenario (from clean slate):

  1. Open /index.html
  2. app.fd9e432.js is loaded
  3. service worker is loaded with /project-sw.js?build=fd9e432

.. next visit after redeploy (new hash):

  1. Open /index.html (which is cached by the previous service worker)
  2. app.fd9e432.js is loaded (same as last time)
  3. The correct service worker is loaded with /project-sw.js?build=fd9e432
  4. Goes to /newpath, webpack tries to load bundle with old hash: newpath.fd9e432.js and gets a 404

The cache headers on index.html and project-sw.js is the same (no cache):

cache-control: no-store, no-cache, must-revalidate, proxy-revalidate
etag: W/"241e-6NP8wbah/2UUxPQ4NPT2uJkYld8"
expires: 0
pragma: no-cache

Is there a configuration I am missing here, or what am I doing wrong?

phun-ky commented 6 years ago

Hm 🦆 , is it perhaps possible to avoid the issue by not using hashes in the bundles? That app.<hash>.js -> app.js et al? Then the reference would be sane (we're not caching static files on the server but in the service worker)?

phun-ky commented 6 years ago

Update, the issue has a fix, but I don't think the underlying issue is resolved. Here is how we force a reload of the service worker when new content is detected: (It's a workaround at best)

if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/project-sw.js?build=' + __webpack_hash__)
    .then(function(reg) {
      // registration worked
      console.info('Service Worker Registration succeeded. Scope is ' + reg.scope);
      // updatefound is fired if service-worker.js changes.
      reg.onupdatefound = function() {
        // The updatefound event implies that reg.installing is set; see
        // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-container-updatefound-event
        var installingWorker = reg.installing;

        installingWorker.onstatechange = function() {
          switch (installingWorker.state) {
            case 'installed':
              if (navigator.serviceWorker.controller) {
                // At this point, the old content will have been purged and the fresh content will
                // have been added to the cache.
                // It's the perfect time to display a "New content is available; please refresh."
                // message in the page's interface.
                // This is also a nice place to put major/minor version logic to inform the user of new features
                console.info('New or updated content is available.');
                window.location.reload(true);
              } else {
                // At this point, everything has been precached.
                // It's the perfect time to display a "Content is cached for offline use." message.
                console.info('Content is now available offline!');
              }
              break;

            case 'redundant':
              console.error('The installing service worker became redundant.');
              break;
          }
        };
      };
    })
    .catch(function(error) {
      // registration failed
      console.error('Service Worker Registration failed with ' + error);
    });
}