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

feat(workbox-precaching) Support for concurrent prefetching #3205

Open
wants to merge 1 commit into
base: v7
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
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,
Copy link
Author

Choose a reason for hiding this comment

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

Not a big fan of the two option arguments. Combining them into a single options argument in a non-breaking manner didn't sit well with me either.

): 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;
};