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

Feature/recipes precache and normalize #2718

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 26 additions & 13 deletions packages/workbox-recipes/src/imageCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
import {warmStrategyCache} from './warmStrategyCache';
import {registerRoute} from 'workbox-routing/registerRoute.js';
import {CacheFirst} from 'workbox-strategies/CacheFirst.js';
import {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.js';
import {ExpirationPlugin} from 'workbox-expiration/ExpirationPlugin.js';
import {RouteMatchCallback, RouteMatchCallbackOptions} from 'workbox-core/types.js';
import {RouteMatchCallback, RouteMatchCallbackOptions, WorkboxPlugin} from 'workbox-core/types.js';

import './_version.js';

Expand All @@ -18,6 +19,8 @@ export interface ImageCacheOptions {
matchCallback?: RouteMatchCallback;
maxAgeSeconds?: number;
maxEntries?: number;
plugins?: Array<WorkboxPlugin>;
warmCache?: Array<string>;
}

/**
Expand All @@ -27,8 +30,11 @@ export interface ImageCacheOptions {
*
* @param {Object} [options]
* @param {string} [options.cacheName] Name for cache. Defaults to images
* @param {RouteMatchCallback} [options.matchCallback] Workbox callback function to call to match to. Defaults to request.destination === 'image';
* @param {number} [options.maxAgeSeconds] Maximum age, in seconds, that font entries will be cached for. Defaults to 30 days
* @param {number} [options.maxEntries] Maximum number of images that will be cached. Defaults to 60
* @param {WorkboxPlugin[]} [options.plugins] Additional plugins to use for this recipe
* @param {string[]} [options.warmCache] Paths to call to use to warm this cache
*/
function imageCache(options: ImageCacheOptions = {}) {
const defaultMatchCallback = ({request}: RouteMatchCallbackOptions) => request.destination === 'image';
Expand All @@ -37,22 +43,29 @@ function imageCache(options: ImageCacheOptions = {}) {
const matchCallback = options.matchCallback || defaultMatchCallback;
const maxAgeSeconds = options.maxAgeSeconds || 30 * 24 * 60 * 60;
const maxEntries = options.maxEntries || 60;
const plugins = (options.plugins || []);
plugins.push(new CacheableResponsePlugin({
statuses: [0, 200],
}));
plugins.push(new ExpirationPlugin({
maxEntries,
maxAgeSeconds
}));

const strategy = new CacheFirst({
cacheName,
plugins,
});

registerRoute(
matchCallback,
new CacheFirst({
cacheName,
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
new ExpirationPlugin({
maxEntries,
maxAgeSeconds
}),
],
})
strategy,
);

// Warms the cache
if (options.warmCache) {
warmStrategyCache({paths: options.warmCache, strategy});
}
}

export { imageCache }
4 changes: 3 additions & 1 deletion packages/workbox-recipes/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {imageCache} from './imageCache';
import {staticResourceCache} from './staticResourceCache';
import {pageCache} from './pageCache';
import {offlineFallback} from './offlineFallback';
import {warmStrategyCache} from './warmStrategyCache';

import './_version.js';

Expand All @@ -24,5 +25,6 @@ export {
imageCache,
staticResourceCache,
pageCache,
offlineFallback
offlineFallback,
warmStrategyCache
};
25 changes: 22 additions & 3 deletions packages/workbox-recipes/src/offlineFallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export interface OfflineFallbackOptions {
fontFallback?: string;
}

// Give TypeScript the correct global.
declare let self: ServiceWorkerGlobalScope;

/**
* An implementation of the [comprehensive fallbacks recipe]{@link https://developers.google.com/web/tools/workbox/guides/advanced-recipes#comprehensive_fallbacks}. Be sure to include the fallbacks in your precache injection
*
Expand All @@ -32,21 +35,37 @@ function offlineFallback(options: OfflineFallbackOptions = {}) {
const imageFallback = options.imageFallback || false;
const fontFallback = options.fontFallback || false;

self.addEventListener('install', event => {
const files = [pageFallback];
if (imageFallback) {
files.push(imageFallback);
}
if (fontFallback) {
files.push(fontFallback);
}

event.waitUntil(self.caches.open('workbox-offline-fallbacks').then(cache => cache.addAll(files)));
});

const handler: RouteHandler = async (
options: RouteHandlerCallbackOptions
) => {
const dest = options.request.destination;
const cache = await self.caches.open('workbox-offline-fallbacks');

if (dest === "document") {
return (await matchPrecache(pageFallback)) || Response.error();
const match = await matchPrecache(pageFallback) || await cache.match(pageFallback);
return match || Response.error();
}

if (dest === "image" && imageFallback !== false) {
return (await matchPrecache(imageFallback)) || Response.error();
const match = await matchPrecache(imageFallback) || await cache.match(imageFallback);
return match || Response.error();
}

if (dest === "font" && fontFallback !== false) {
return (await matchPrecache(fontFallback)) || Response.error();
const match = await matchPrecache(fontFallback) || await cache.match(fontFallback);
return match || Response.error();
}

return Response.error();
Expand Down
34 changes: 24 additions & 10 deletions packages/workbox-recipes/src/pageCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
import {warmStrategyCache} from './warmStrategyCache';
import {registerRoute} from 'workbox-routing/registerRoute.js';
import {NetworkFirst} from 'workbox-strategies/NetworkFirst.js';
import {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.js';
import {RouteMatchCallback, RouteMatchCallbackOptions} from 'workbox-core/types.js';
import {RouteMatchCallback, RouteMatchCallbackOptions, WorkboxPlugin} from 'workbox-core/types.js';

import './_version.js';

export interface ImageCacheOptions {
cacheName?: string;
matchCallback?: RouteMatchCallback;
networkTimeoutSeconds?: number;
plugins?: Array<WorkboxPlugin>;
warmCache?: Array<string>;
}

/**
Expand All @@ -27,26 +30,37 @@ export interface ImageCacheOptions {
* @param {string} [options.cacheName] Name for cache. Defaults to pages
* @param {RouteMatchCallback} [options.matchCallback] Workbox callback function to call to match to. Defaults to request.mode === 'navigate';
* @param {number} [options.networkTimoutSeconds] Maximum amount of time, in seconds, to wait on the network before falling back to cache. Defaults to 3
* @param {WorkboxPlugin[]} [options.plugins] Additional plugins to use for this recipe
* @param {string[]} [options.warmCache] Paths to call to use to warm this cache
*/
function pageCache(options: ImageCacheOptions = {}) {
const defaultMatchCallback = ({request}: RouteMatchCallbackOptions) => request.mode === 'navigate';

const cacheName = options.cacheName || 'pages';
const matchCallback = options.matchCallback || defaultMatchCallback;
const networkTimeoutSeconds = options.networkTimeoutSeconds || 3;
const plugins = (options.plugins || []);
plugins.push(new CacheableResponsePlugin({
statuses: [0, 200],
}));

const strategy = new NetworkFirst({
networkTimeoutSeconds,
cacheName,
plugins,
});


// Registers the route
registerRoute(
matchCallback,
new NetworkFirst({
networkTimeoutSeconds,
cacheName,
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
],
})
strategy
);

// Warms the cache
if (options.warmCache) {
warmStrategyCache({paths: options.warmCache, strategy});
}
}

export { pageCache }
31 changes: 22 additions & 9 deletions packages/workbox-recipes/src/staticResourceCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
import {warmStrategyCache} from './warmStrategyCache';
import {registerRoute} from 'workbox-routing/registerRoute.js';
import {StaleWhileRevalidate} from 'workbox-strategies/StaleWhileRevalidate.js';
import {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.js';
import {RouteMatchCallback, RouteMatchCallbackOptions} from 'workbox-core/types.js';
import {RouteMatchCallback, RouteMatchCallbackOptions, WorkboxPlugin} from 'workbox-core/types.js';

import './_version.js';

export interface StaticResourceOptions {
cacheName?: string;
matchCallback?: RouteMatchCallback;
plugins?: Array<WorkboxPlugin>;
warmCache?: Array<string>;
}

/**
Expand All @@ -24,24 +27,34 @@ export interface StaticResourceOptions {
*
* @param {Object} [options]
* @param {string} [options.cacheName] Name for cache. Defaults to static-resources
* @param {RouteMatchCallback} [options.matchCallback] Workbox callback function to call to match to. Defaults to request.destination === 'style' || request.destination === 'script' || request.destination === 'worker';
* @param {WorkboxPlugin[]} [options.plugins] Additional plugins to use for this recipe
* @param {string[]} [options.warmCache] Paths to call to use to warm this cache
*/
function staticResourceCache(options: StaticResourceOptions = {}) {
const defaultMatchCallback = ({request}: RouteMatchCallbackOptions) => request.destination === 'style' || request.destination === 'script' || request.destination === 'worker';

const cacheName = options.cacheName || 'static-resources';
const matchCallback = options.matchCallback || defaultMatchCallback;
const plugins = (options.plugins || []);
plugins.push(new CacheableResponsePlugin({
statuses: [0, 200],
}));

const strategy = new StaleWhileRevalidate({
cacheName,
plugins,
});

registerRoute(
matchCallback,
new StaleWhileRevalidate({
cacheName,
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
],
})
strategy,
);

// Warms the cache
if (options.warmCache) {
warmStrategyCache({paths: options.warmCache, strategy});
}
}

export { staticResourceCache }
30 changes: 30 additions & 0 deletions packages/workbox-recipes/src/warmStrategyCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Strategy } from 'workbox-strategies/src/Strategy';

import './_version.js';

export interface WarmStrategyCacheOptions {
paths: Array<string>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you switch from calling this paths to urls?

(As far as I can tell, you should be able to pass in any absolute or relative URL inside this array, right?)

strategy: Strategy;
}

// Give TypeScript the correct global.
declare let self: ServiceWorkerGlobalScope;

/**
*
Snugug marked this conversation as resolved.
Show resolved Hide resolved
* @param {Object} options
* @param {string[]} options.paths Paths to warm the strategy's cache with
* @param {Strategy} options.strategy Strategy to use
*/
function warmStrategyCache(options: WarmStrategyCacheOptions): void {
self.addEventListener('install', event => {
const warm = options.paths.map(path => options.strategy.handleAll({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think done (or donePromises) would be a more descriptive variable name than warm, especially since it's not entirely obvious from reading the code that the [1] index represents a done promise.

event,
request: new Request(path),
})[1]);

event.waitUntil(Promise.all(warm));
});
}

export {warmStrategyCache};