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

[RFC] v6 Precaching Updates and Changes #2638

Closed
philipwalton opened this issue Sep 26, 2020 · 0 comments · Fixed by #2639
Closed

[RFC] v6 Precaching Updates and Changes #2638

philipwalton opened this issue Sep 26, 2020 · 0 comments · Fixed by #2639

Comments

@philipwalton
Copy link
Member

Objective

Decouple routing and strategy logic from the PrecacheController class in order to:

  • Reduce bugs/confusion stemming from route order/precedence of precached routes.
  • Give developers more control over how assets are stored and served from the precache
  • Reduce bundle sizes for developers not using many of the existing built-in precaching options
  • Make it possible (in the future) to swap out PrecacheStrategy for a custom strategy

Background

The workbox-precaching package was recently updated to use modules from workbox-strategies and workbox-routing under the hood, but it did not change the API—which means the degree of decoupling was limited, and it could not fully address several issues:

With v6 being a major release and breaking changes being possible, this proposals continues the work started in #2459 and fully removes any custom route-matching or strategy-handling logic done in workbox-precaching. All of that work is now being done by subclasses of workbox-routing or workbox-strategy classes.

New modules

This proposal introduces several new modules (both public and private) to the workbox-precaching packages. These modules are largely self-contained and independent, so developers will only pay the cost for the modules they import:

PrecacheStrategy

PrecacheStrategy is a custom strategy that can populate the precache using the same logic currently used in v5. This includes:

  • Adding a request to the cache on install
  • Retrieving a request from the cache on fetch
  • Falling back to network on cache miss (without updating the cache)
  • Handling same-origin redirects
  • Handling opaque responses (if a cacheWillUpdate plugin callback isn't used).
interface PrecacheStrategyOptions extends StrategyOptions {
  fallbackToNetwork: boolean;
}

declare class PrecacheStrategy extends Strategy {
  constructor(options?: PrecacheStrategyOptions);
}

Note, two items explicitly NOT handled by the PrecacheStrategy class are:

  • Handling revision cache keys
  • Generating a list of updated/not-updated URLs on install

These are handled by the PrecacheCacheKeyPlugin and PrecacheInstallReportPlugin plugins (respectively), and both of these plugins are added to the strategy by the PrecacheController on instantiation.

There are two primary reasons this logic is handled by plugins rather than PrecacheStrategy itself:

  • To avoid coupling of concerns between the PrecacheController (which maintains the precache manifest and cacheKey mapping) and the PrecacheStrategy (which handles how requests are fetched/cached).
  • To make it possible (in the future, not in v6) for a developer to use their own custom strategy (or a built-in strategy like CacheFirst) with precaching.

Note: while this proposal doesn't include any API changes to support custom precaching strategies, it does make it possible through module aliasing with a build tool. While this is not currently recommended, the fact that it does work validates the flexibility of the new design.

PrecacheRoute

PrecacheRoute is a custom route that gets created passing it a PrecacheController instance as well as the configuration options currently accepted by the addRoute() method in v5.

The route will use the urls-to-cache-keys map of the PrecacheController instance to determine whether an incoming request "matches", and it will use the strategy created by the controller as the handler. The matcher will also pass the cacheKey value as a custom param to the handler so it doesn't have to re-lookup that value.

Since the PrecacheRoute logic in v6 is separate from the PrecacheController logic, developers who don't need to use the configuration options can avoid bundling that logic in their service worker file.

declare class PrecacheRoute extends Route {
  constructor(pc: PrecacheController, options?: FetchListenerOptions);
}

PrecacheCacheKeyPlugin (private)

PrecacheCacheKeyPlugin (not publicly exposed) is a custom plugin that gets automatically set on the strategy created by the PrecacheController.

The plugin contains a cacheKeyWillBeUsed callback to map request URLs to revisioned assets in the precache.

As mentioned above, the rationale for putting this logic into its own plugin (rather than as part of PrecacheStrategy) is that it can then be used with any other strategy as well.

declare class PrecacheCacheKeyPlugin implements WorkboxPlugin {
  constructor({ precacheController }: {
      precacheController: PrecacheController;
  });
  cacheKeyWillBeUsed: WorkboxPlugin['cacheKeyWillBeUsed'];
}

PrecacheInstallReportPlugin (private)

Similar to PrecacheCacheKeyPlugin, the PrecacheInstallReportPlugin (not publicly exposed) gets automatically set on the strategy created by the PrecacheController, and it's responsible for generating the list of updated (and not updated) URLs during service worker installation.

For each handler instance run during the install event, if the URL is found in the cache already, it'll be added to the notUpdatedURLs array, otherwise it'll be added to the updatedURLs array.

declare class PrecacheInstallReportPlugin implements WorkboxPlugin {
  updatedURLs: string[];
  notUpdatedURLs: string[];
  handlerWillStart: WorkboxPlugin['handlerWillStart'];
  cachedResponseWillBeUsed: WorkboxPlugin['cachedResponseWillBeUsed'];
}

Again, this logic is implemented via a plugin to make it possible for other strategies to be used with PrecacheController, and to make it possible for the PrecacheStrategy to be used as a runtime strategy (not sure if there are real use cases for this, but it would be technically possible).

Breaking changes

This proposal intentionally limits the amount of breaking changes to the top level function modules exported by workbox-precaching. Most of the breaking changes are scoped to the lesser-used PrecacheController class and to the recently-introduced (and also lesser-used) createHandler and createHandlerBoundToURL functions.

PrecacheController

Changes to the constructor() parameter{#changes-to-the-constructor-parameters}

In v5 the only option you could pass to the PrecacheController constructor was a single cacheName param. In v6 the PrecacheController will take an object in order to make it more extensible in the future.

The current set of accepted object properties is:

  • cacheName
  • plugins
  • fallbackToNetwork (defaults to true)
// In v5

const pc = new PrecacheController('precache');

pc.addPlugins([...])

// In v6

const pc = new PrecacheController({
  cacheName: 'precache',
  plugins: [...]
});

In v5 the only way to add plugins was via the top-level addPlugins() function, or by passing them to the install() method of PrecacheController.

In v6, plugins are passed when creating the PrecacheController instance.

Lastly, in v6 the fallbackToNetwork option (previously set when calling createHandler() or createHandlerBoundToURL()) is now set when creating the PrecacheController instance, which means it will now the same setting will apply to all handled requests.

If a developer needs different fallback logic for different resources, they will need to create multiple PrecacheController instances.

Changes to the createHandlerBoundToURL() method parameter{#changes-to-the-createhandlerboundtourl-method-parameters}

The createHandlerBoundToURL method no longer accepts a second, fallbackToNetwork parameter.

As mentioned above, the fallback-to-network logic is now set on the PrecacheController instance itself (and applied to the PrecacheStrategy the controller creates upon instantiation) and thus it applies to all requests handled by the controller.

Note: the same change will be made to the top-level createHandlerBoundToURL() function, see below.

Changes to the install() method parameter{#changes-to-the-install-method-parameters}

The install method previously took an optional {event, plugins} config object. In v6 it will only take an event object, which will be required.

In addition (and not technically a breaking change), the install event will now automatically call event.waitUntil(), since it's easy for developers to forget that this is needed, which can cause installations to fail.

// In v5

const pc = new PrecacheController();

addEventListener('install', (event) => event.waitUntil(pc.install({event})));

// In v6

const pc = new PrecacheController();

addEventListener('install', pc.install);

Changes to the activate() method parameter{#changes-to-the-activate-method-parameters}

Similar to the install() method, the activate() method will also now require an event (previously it took no parameters), and that event will automatically call event.waitUntil().

// In v5

const pc = new PrecacheController();

addEventListener('activate', (event) => event.waitUntil(pc.activate()));

// In v6

const pc = new PrecacheController();

addEventListener('activate', pc.activate);

Remove the addRoute() method

Since this proposal introduces a new PrecacheRoute class, the addRoute() method on PrecacheController will be removed in favor of developers explicitly adding routes for precaching.

This will help prevent cases where the order of initializing precaching can affect what routes match particular requests:

// In v5

const pc = new PrecacheController();

// Adds a fetch event listener to match precaching requests
pc.addRoute({...});

// In v6

const pc = new PrecacheController();

// Creates and registers a route explicitly on the default router.
registerRoute(new PrecacheRoute(pc, {...}))

Note: for better backwards compatibility, the top-level addRoute() function will continue to work as it does today, only the PrecacheController method is being removed.

Remove the precacheAndRoute() method

Since the addRoute() method is being removed (see above), it also makes sense to remove the precacheAndRoute() method from the PrecacheController class.

_Note: as with the addRoute() method, the top-level precacheAndRoute() function will continue to be available, it's just the PrecacheContoller method that's being removed.

Remove the createMatchCalback() metho{#remove-the-creatematchcalback-method}

The primary purpose of the createMatchCallback() method was to create a route to match precached assets, but with this proposal's addition of the PrecacheRoute class, this is no longer needed.

Remove the createHandler() method

Since the PrecacheController object now creates and exposes a strategy instance, and since all strategies have a handle() method, the createHandler() method of PrecacheController will be removed.

Developers who need a handler to respond to requests for precached assets can use the strategy directly:

// In v5

const pc = new PrecacheController();

registerRoute(
  ({request}) => request.destination === 'script',
  pc.createHandler(),
)

// In v6

const pc = new PrecacheController();

registerRoute(
  ({request}) => request.destination === 'script',
  pc.strategy,
);

The createHandler() method also took a configuration option fallbackToNetwork, which defaulted to true. Developers who do not want their precaching logic to fall back to network can set this to false when creating the PrecacheController instance:

// In v5

const pc = new PrecacheController();

registerRoute(
  ({request}) => request.destination === 'script',
  pc.createHandler({fallbackToNetwork: false}),
)

// In v6

const pc = new PrecacheController({fallbackToNetwork: false});

registerRoute(
  ({request}) => request.destination === 'script',
  pc.strategy,
);

createHandlerBoundToURL()

As with the changes to the createHandlerBoundToURL() method (mentioned above), the top-level createHandlerBoundToURL() method no longer accepts a second fallbackToNetwork parameter.

Developers who do not want the default behavior of falling back to network on precache misses will not be able to use the default precach controller, they'll need to create their own PrecacheController instance and set the fallbackToNetwork option to false.

createHandler()

As with the removal of the createHandler() method (mentioned above), the top-level createHandler() will be removed.

Developers who need to use a handler for precached assets will not be able to use the default precache controller, they'll need to create their own PrecacheController instance and use its strategy property.

Examples

Precaching an app shell

import {PrecacheController} from 'workbox-precaching';
import {NavigationRoute, registerRoute} from 'workbox-routing';

const pc = new PrecacheController();

pc.precache([
  // Manifest goes here...
]);

const handler = pc.createHandlerBoundToURL('/app-shell.html');
const navigationRoute = new NavigationRoute(handler, {
  allowlist: [...],
  denylist: [...],
});
registerRoute(navigationRoute);

Precaching with custom routing

import {PrecacheController} from 'workbox-precaching';
import {registerRoute} from 'workbox-routing';

const pc = new PrecacheController({cacheName: 'precache'});

pc.precache([
  // Manifest goes here...
]);

registerRoute(
  ({request}) => request.destination === 'script',
  pc.strategy
);

WIP Implementation

https://github.com/GoogleChrome/workbox/tree/precaching-v6

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

Successfully merging a pull request may close this issue.

1 participant