Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying specific strategies for precaching #1767

Closed
pimterry opened this issue Nov 26, 2018 · 8 comments
Closed

Allow specifying specific strategies for precaching #1767

pimterry opened this issue Nov 26, 2018 · 8 comments

Comments

@pimterry
Copy link

Library Affected:
workbox-precaching

Browser & Platform:
All

Issue or Feature Request Description:
By default precaching uses a bespoke cache-first strategy. This is the right strategy most of the time, but not all the time. See discussion here: https://twitter.com/jeffposnick/status/1067081651803619328.

It is possible to work around this right now, but only by creating routes from scratch and configuring them to use the precache cache directly, and doing so can silently cause problems and break things with some strategies.

It would be useful to be able to pass a strategy into precache route setup directly. With that in place, workbox could:

  • Allow new combinations of existing strategies and precaching options
  • Allow custom strategies and other advanced fine tuning of precaching
  • Catch & warn users directly when trying to set up known bad combinations of strategies (e.g. stale-while-revalidate of precached revisioned resources)
@jeffposnick
Copy link
Contributor

Assigning this to @philipwalton for the latest thinking. I know that he's been looking into this space, but the ultimate resolution might just be not to proceed with this feature request.

(For the record, I am of the opinion that allowing folks to swap in alternative strategies for precaching is a Bad Idea, and that the best approach for folks who want something other than cache-first behavior is to runtime caching instead.)

@martinkadlec0
Copy link

martinkadlec0 commented Jul 24, 2019

Personally, I would love to have online first strategy for precaching. For someone like me who is only just getting into service workers, it would feel much safer to only rely on the cache when offline, but otherwise run normally. Eventually I would probably switch to offline first anyway, but the onlinefirst mode can be an important step in between allowing people to feel more confident about going fully offline first.

@jeffposnick
Copy link
Contributor

I don't think you should use workbox-precaching given that use case.

You can accomplish that by setting up a runtime caching route that uses a network-first strategy, and if needed, calling cache.addAll() inside of an install handler to pre-populate URLs.

workbox-precaching really only works if the source of truth about the current version of each cache entry is reflected in the current precache manifest.

@Finesse
Copy link

Finesse commented Nov 14, 2019

My use-case is to:

  • Cache the whole application on the first launch so that it's available offline
  • Make the application update itself ASAP on the next online launch (like if the application doesn't use a service worker at all)
  • Have a single SPA shell that serves all the navigation routes (the server is configured for it)

The default strategy implemented by GenerateSW Webpack loader is to precacheAndRoute all the emitted files. It's not acceptable because it requires users to close all the application tabs to apply the update.

I can not use runtime caching with NetworkFirst strategy instead of precacheAndRoute because not all of the application assets are requested on application launch. Also there are many navigation routes that must be cached too.

Actually, I need the network first strategy only for /index.html because all the other assets have version hash in their paths. I tried to exclude it from the precacheAndRoute entries list, but it broke registerNavigationRoute that uses index.html as the SPA shell.

This solution isn't suitable too because the cache is stored for each individual navigation route (instead of a single cache like required for SPA shell). Also index.html is cached only when you open the page for the 2nd time.

@jeffposnick How can I configure GenerateSW or InjectManifest to implement network-first strategy for index.html while keeping it as the SPA shell?

@Finesse
Copy link

Finesse commented Nov 14, 2019

I've found an almost perfect (but still not suitable) service worker code for my case:

// The ordinary SW stuff here like importScripts, self.addEventListener, etc.

// self.__precacheManifest includes index.html
workbox.precaching.precacheAndRoute(self.__precacheManifest, {
  directoryIndex: '', // To make the code below works as expected at the root path
});

// Implements the network-first strategy for navigation routes that falls back to preloaded cache (index.html)
workbox.routing.registerRoute(
  new workbox.routing.NavigationRoute(async ({ url }) => {
    const cacheName = workbox.core.cacheNames.precache;
    const cacheKey = workbox.precaching.getCacheKeyForURL('/index.html');
    const cache = await caches.open(cacheName);

    let networkResponse;
    let networkError;
    try {
      networkResponse = await fetch(url);
      if (networkResponse.ok) {
        await cache.put(cacheKey, networkResponse.clone());
        return networkResponse;
      }
    } catch (error) {
      networkError = error;
    }

    const cacheMatch = await cache.match(cacheKey);
    if (cacheMatch) {
      return cacheMatch;
    } else if (networkResponse) {
      return networkResponse;
    } else {
      throw networkError;
    }
  })
);

// Don't add `workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"));`

The lack is that when I reload the updated application, then go offline and reload the page again, my new HTML file is used but Workbox doesn't load the new versions of assets (added through precacheAndRoute) from the cache until all the tabs are closed. It ruins the whole solution.

image

I assume that the Workbox precaching module is built with «apply update when tabs closed» strategy and I can't mix strategies: either obey or write your own precaching mechanism.

It would be great if Workbox has an option to use the network first strategy for precaching.

@jeffposnick
Copy link
Contributor

I've been talking to @philipwalton about this for v6, as he's been planning on a rewrite of the workbox-precaching internals.

The main takeaway has been that it's not safe to allow developers to override the default Strategy subclass that will be used for handling caching. Doing so would really open the door for using workbox-precaching in a way that leaves your cache state inconsistent and didn't play nicely with the service worker install/activate events. Instead, what we're likely to do is just allow a "safe" subset of plugins to modify precaching's Strategy.

There are legitimate use cases for pre-populating a cache while using a non-cache-first Strategy to serve those requests. For those cases, we recommend calling cache.addAll() inside of an install handler, and then using workbox-routing to associate appropriate Strategys with different routing conditions. We will likely publish a "recipe" in the docs to formalize this recommendation, but in the meantime, something like the following should allow you to do this while still using the self.__WB_MANIFEST injection from our build tools if you want—or you can just generate your list of URLs via other build methods if you'd like.

import {registerRoute} from 'workbox-routing';

const cacheName = 'install-cache';
// This assumes you're using injectManifest to replace the variable.
const manifest = self.__WB_MANIFEST;
// We just need the url, not the revision, since managing cache
// revisions will happen via runtime caching updates.
// Put this variable in the top-level scope so that we can use
// it later on in routing.
const manifestURLs = manifest.map((entry) => {
  // Create a full, absolute URL to make routing easier.
  const url = new URL(entry.url, self.location);
  return url.href;
});

// This takes care of pre-populating the cache.
// Whenever the manifest changes, it will be re-run, but unlike with precaching,
// there is no automatic "clean up".
self.addEventListener('install', (event) => {
  const populateCache = async () => {
    const cache = await caches.open(cacheName);
    await cache.addAll(manifestURLs);
  };

  event.waitUntil(populateCache());
});

// Register one or more runtime routes as needed.
// This is just an example.
registerRoute(
  // If this is a URL in the manifest...
  ({url}) => manifestURLs.includes(url.href),
  // ...respond using a StaleWhileRevalidate strategy.
  new StaleWhileRevalidate({cacheName}),
);

Given that, I'm going to close this issue.

@andyautida14
Copy link

Just sharing a little hack I made to force network-first for index.html:

// sw.js
import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { skipWaiting, clientsClaim } from 'workbox-core'
import navigationRoute from './js/sw/navigation'

precacheAndRoute([
  // for some reason, it's an object and precacheAndRoute complains when it's not converted to array
  ...self.__WB_MANIFEST
])

registerRoute(navigationRoute())

skipWaiting()
clientsClaim()
// navigation.js
import { cacheNames } from 'workbox-core'
import { NavigationRoute } from 'workbox-routing'
import { NetworkFirst } from 'workbox-strategies'

export default function() {
  const networkFirst = new NetworkFirst({
    cacheName: cacheNames.precache
  })
  return new NavigationRoute(options => {
    options.request = new Request('/index.html')
    return networkFirst.handle(options)
  })
}

I don't know what are the gotchas yet for this hack. If anyone knows or encounters some, I'd love to know as well.

@redalpha01
Copy link

To those that come in the future and want to work @jeffposnick's solution but it doesn't see m to use the right cache, use the runtime cache mentioned here instead :
https://developers.google.com/web/tools/workbox/guides/advanced-recipes#warm_the_runtime_cache

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants