Skip to content

Commit

Permalink
feat(workbox-precache) Add concurrent prefetch
Browse files Browse the repository at this point in the history
Fix lint

Add tests for concurrent precaching

Improve documentation
  • Loading branch information
C-Hess committed May 5, 2023
1 parent 95f97a2 commit b9d275a
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 33 deletions.
95 changes: 70 additions & 25 deletions packages/workbox-precaching/src/PrecacheController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,38 @@ declare global {
}
}

interface PrecacheControllerOptions {
function chunk<T>(array: T[], chunkSize = 1) {
const chunks: T[][] = [];
const tmp = [...array];
if (chunkSize <= 0) {
return chunks;
}
while (tmp.length) chunks.push(tmp.splice(0, chunkSize));
return chunks;
}

export interface PrecacheControllerOptions {
/** The cache to use for precaching. */
cacheName?: string;

/**
* Plugins to use when precaching as well
* as responding to fetch events for precached assets.
*/
plugins?: WorkboxPlugin[];

/**
* Whether to attempt to
* get the response from the network if there's a precache miss.
* @default true
*/
fallbackToNetwork?: boolean;

/**
* The maximum number of concurrent prefetch requests to make.
* @default 1
*/
concurrentRequests?: number;
}

/**
Expand All @@ -45,6 +73,16 @@ interface PrecacheControllerOptions {
*/
class PrecacheController {
private _installAndActiveListenersAdded?: boolean;

/**
* The number of requests to patch concurrently. By default, concurrent request batching should
* be disabled.
* @link https://github.com/GoogleChrome/workbox/issues/2528
* @link https://github.com/GoogleChrome/workbox/issues/2880
* @default 1
*/
private readonly _concurrentRequests: number;

private readonly _strategy: Strategy;
private readonly _urlsToCacheKeys: Map<string, string> = new Map();
private readonly _urlsToCacheModes: Map<
Expand All @@ -61,17 +99,13 @@ class PrecacheController {
/**
* Create a new PrecacheController.
*
* @param {Object} [options]
* @param {string} [options.cacheName] The cache to use for precaching.
* @param {string} [options.plugins] Plugins to use when precaching as well
* as responding to fetch events for precached assets.
* @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to
* get the response from the network if there's a precache miss.
* @param {PrecacheControllerOptions} [options={}] Optional precache controller configurations
*/
constructor({
cacheName,
plugins = [],
fallbackToNetwork = true,
concurrentRequests = 1,
}: PrecacheControllerOptions = {}) {
this._strategy = new PrecacheStrategy({
cacheName: cacheNames.getPrecacheName(cacheName),
Expand All @@ -81,6 +115,8 @@ class PrecacheController {
],
fallbackToNetwork,
});
this._concurrentRequests =
concurrentRequests && concurrentRequests > 0 ? concurrentRequests : 1;

// Bind the install and activate methods to the instance.
this.install = this.install.bind(this);
Expand Down Expand Up @@ -203,25 +239,34 @@ class PrecacheController {
const installReportPlugin = new PrecacheInstallReportPlugin();
this.strategy.plugins.push(installReportPlugin);

// Cache entries one at a time.
// See https://github.com/GoogleChrome/workbox/issues/2528
for (const [url, cacheKey] of this._urlsToCacheKeys) {
const integrity = this._cacheKeysToIntegrities.get(cacheKey);
const cacheMode = this._urlsToCacheModes.get(url);

const request = new Request(url, {
integrity,
cache: cacheMode,
credentials: 'same-origin',
});

await Promise.all(
this.strategy.handleAll({
params: {cacheKey},
request,
event,
}),
const chunkedUrlsToCacheKeys = chunk(
Array.from(this._urlsToCacheKeys),
this._concurrentRequests,
);

for (const urlsToCacheKeysChunk of chunkedUrlsToCacheKeys) {
const batchedRequests = urlsToCacheKeysChunk.map(
async ([url, cacheKey]) => {
const integrity = this._cacheKeysToIntegrities.get(cacheKey);
const cacheMode = this._urlsToCacheModes.get(url);

const request = new Request(url, {
integrity,
cache: cacheMode,
credentials: 'same-origin',
});

return Promise.all(
this.strategy.handleAll({
params: {cacheKey},
request,
event,
}),
);
},
);

await Promise.all(batchedRequests);
}

const {updatedURLs, notUpdatedURLs} = installReportPlugin;
Expand Down
8 changes: 6 additions & 2 deletions packages/workbox-precaching/src/precache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';
import {PrecacheEntry} from './_types.js';
import './_version.js';
import {PrecacheControllerOptions} from './PrecacheController.js';

/**
* Adds items to the precache list, removing any duplicates and
Expand All @@ -29,8 +30,11 @@ import './_version.js';
*
* @memberof workbox-precaching
*/
function precache(entries: Array<PrecacheEntry | string>): void {
const precacheController = getOrCreatePrecacheController();
function precache(
entries: Array<PrecacheEntry | string>,
options?: PrecacheControllerOptions,
): void {
const precacheController = getOrCreatePrecacheController(options);
precacheController.precache(entries);
}

Expand Down
8 changes: 5 additions & 3 deletions packages/workbox-precaching/src/precacheAndRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {addRoute} from './addRoute.js';
import {precache} from './precache.js';
import {PrecacheRouteOptions, PrecacheEntry} from './_types.js';
import './_version.js';
import {PrecacheControllerOptions} from './PrecacheController.js';

/**
* This method will add entries to the precache list and add a route to
Expand All @@ -27,10 +28,11 @@ import './_version.js';
*/
function precacheAndRoute(
entries: Array<PrecacheEntry | string>,
options?: PrecacheRouteOptions,
routeOptions?: PrecacheRouteOptions,
controllerOptions?: PrecacheControllerOptions,
): void {
precache(entries);
addRoute(options);
precache(entries, controllerOptions);
addRoute(routeOptions);
}

export {precacheAndRoute};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
https://opensource.org/licenses/MIT.
*/

import {PrecacheController} from '../PrecacheController.js';
import {
PrecacheController,
PrecacheControllerOptions,
} from '../PrecacheController.js';
import '../_version.js';

let precacheController: PrecacheController | undefined;
Expand All @@ -15,9 +18,11 @@ let precacheController: PrecacheController | undefined;
* @return {PrecacheController}
* @private
*/
export const getOrCreatePrecacheController = (): PrecacheController => {
export const getOrCreatePrecacheController = (
options?: PrecacheControllerOptions,
): PrecacheController => {
if (!precacheController) {
precacheController = new PrecacheController();
precacheController = new PrecacheController(options);
}
return precacheController;
};

0 comments on commit b9d275a

Please sign in to comment.