This module efficiently precaches assets for Progressive Web Apps and service workers.
—
Plugin system for extending functionality and cleanup utilities for cache maintenance. These tools help customize precaching behavior and maintain cache hygiene.
Function for adding plugins to the default precaching strategy.
/**
* Adds plugins to the precaching strategy.
* Plugins added via this method will be used by all precaching operations using the default controller.
* @param plugins - Array of Workbox plugins to add to the precaching strategy
*/
function addPlugins(plugins: WorkboxPlugin[]): void;Usage Examples:
import { addPlugins, precacheAndRoute } from "workbox-precaching";
// Add custom plugins before setting up precaching
addPlugins([
{
cacheKeyWillBeUsed: async ({ request, mode }) => {
// Add custom cache key logic
const url = new URL(request.url);
url.searchParams.set('sw-version', '1.0');
return url.href;
},
cacheWillUpdate: async ({ request, response }) => {
// Only cache successful responses
return response.status === 200 ? response : null;
},
cacheDidUpdate: async ({ cacheName, request, oldResponse, newResponse }) => {
// Log cache updates
console.log(`Cache updated: ${request.url}`);
// Notify clients about updates
const clients = await self.clients.matchAll();
clients.forEach(client => {
client.postMessage({
type: 'CACHE_UPDATED',
url: request.url
});
});
}
}
]);
// Now set up precaching with plugins applied
precacheAndRoute([
{ url: "/index.html", revision: "v1.0" },
{ url: "/app.js", revision: "v2.1" }
]);Function for cleaning up outdated caches from previous Workbox versions.
/**
* Adds an activate event listener which cleans up incompatible precaches
* created by older versions of Workbox.
* This is important when updating Workbox versions to prevent cache bloat.
*/
function cleanupOutdatedCaches(): void;Usage Examples:
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching";
// Clean up old caches when service worker activates
cleanupOutdatedCaches();
// Set up current precaching
precacheAndRoute([
{ url: "/index.html", revision: "v2.0" },
{ url: "/styles.css", revision: "v2.1" }
]);
// The cleanup will automatically run during service worker activation
// and remove caches from older Workbox versionsPlugin class that provides offline fallback responses from the precache.
/**
* Plugin that provides offline fallback responses from precache.
* Implements WorkboxPlugin interface.
*/
class PrecacheFallbackPlugin implements WorkboxPlugin {
/**
* Create a new PrecacheFallbackPlugin instance
* @param options - Configuration options
*/
constructor(options: {
/** Precached URL to use as fallback */
fallbackURL: string;
/** PrecacheController instance (defaults to singleton) */
precacheController?: PrecacheController;
});
/**
* Plugin callback that returns precached fallback response when handler fails
* @returns Promise resolving to fallback response or undefined
*/
handlerDidError(): Promise<Response | undefined>;
}Usage Examples:
import {
PrecacheFallbackPlugin,
precacheAndRoute
} from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { NetworkFirst } from "workbox-strategies";
// Precache fallback pages
precacheAndRoute([
{ url: "/offline.html", revision: "v1.0" },
{ url: "/error.html", revision: "v1.0" }
]);
// Create fallback plugins
const offlineFallback = new PrecacheFallbackPlugin({
fallbackURL: "/offline.html"
});
const errorFallback = new PrecacheFallbackPlugin({
fallbackURL: "/error.html"
});
// Use with strategies
const networkFirstStrategy = new NetworkFirst({
cacheName: "pages",
plugins: [offlineFallback]
});
// Register routes with fallback
registerRoute(
({ request }) => request.mode === 'navigate',
networkFirstStrategy
);
// Use different fallbacks for different content types
registerRoute(
({ request }) => request.destination === 'document',
new NetworkFirst({
cacheName: "documents",
plugins: [errorFallback]
})
);import { addPlugins } from "workbox-precaching";
const loggingPlugin = {
cacheKeyWillBeUsed: async ({ request, mode, params }) => {
const cacheKey = params?.cacheKey || request.url;
console.log(`Cache key for ${request.url}: ${cacheKey}`);
return cacheKey;
},
cacheWillUpdate: async ({ request, response, event }) => {
console.log(`Caching ${request.url}:`, {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries())
});
return response;
},
cacheDidUpdate: async ({ cacheName, request, oldResponse, newResponse, event }) => {
console.log(`Cache updated in ${cacheName}:`, {
url: request.url,
oldStatus: oldResponse?.status,
newStatus: newResponse?.status
});
},
cachedResponseWillBeUsed: async ({ cacheName, request, cachedResponse, event }) => {
console.log(`Serving from cache ${cacheName}: ${request.url}`);
return cachedResponse;
}
};
addPlugins([loggingPlugin]);import { addPlugins } from "workbox-precaching";
const securityPlugin = {
cacheWillUpdate: async ({ request, response }) => {
// Only cache HTTPS responses
if (!request.url.startsWith('https://')) {
console.warn(`Refusing to cache non-HTTPS resource: ${request.url}`);
return null;
}
// Check response headers for security
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/javascript')) {
const csp = response.headers.get('content-security-policy');
if (!csp) {
console.warn(`JavaScript resource without CSP: ${request.url}`);
}
}
return response;
},
cachedResponseWillBeUsed: async ({ cachedResponse, request }) => {
// Add security headers to cached responses
if (cachedResponse) {
const secureResponse = new Response(cachedResponse.body, {
status: cachedResponse.status,
statusText: cachedResponse.statusText,
headers: {
...cachedResponse.headers,
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-Served-From': 'sw-cache'
}
});
return secureResponse;
}
return cachedResponse;
}
};
addPlugins([securityPlugin]);import { addPlugins } from "workbox-precaching";
const performancePlugin = {
requestWillFetch: async ({ request, event }) => {
// Mark start time
(request as any).__startTime = performance.now();
return request;
},
fetchDidSucceed: async ({ request, response }) => {
const startTime = (request as any).__startTime;
if (startTime) {
const duration = performance.now() - startTime;
console.log(`Network fetch for ${request.url}: ${duration.toFixed(2)}ms`);
// Report to analytics
if ('gtag' in self) {
(self as any).gtag('event', 'sw_fetch_duration', {
url: request.url,
duration: Math.round(duration)
});
}
}
return response;
},
cacheDidUpdate: async ({ request, cacheName }) => {
// Track cache updates
console.log(`Cache update: ${request.url} in ${cacheName}`);
if ('gtag' in self) {
(self as any).gtag('event', 'sw_cache_update', {
url: request.url,
cache_name: cacheName
});
}
}
};
addPlugins([performancePlugin]);import {
precacheAndRoute,
cleanupOutdatedCaches,
addPlugins,
PrecacheFallbackPlugin
} from "workbox-precaching";
import { registerRoute } from "workbox-routing";
import { NetworkFirst, StaleWhileRevalidate } from "workbox-strategies";
// 1. Clean up old caches
cleanupOutdatedCaches();
// 2. Add global plugins
addPlugins([
// Production logging
{
cacheDidUpdate: async ({ request, cacheName }) => {
console.log(`Updated ${request.url} in ${cacheName}`);
// Notify app about updates
const clients = await self.clients.matchAll();
clients.forEach(client => {
client.postMessage({
type: 'SW_CACHE_UPDATED',
url: request.url
});
});
}
},
// Error handling
{
fetchDidFail: async ({ originalRequest, error }) => {
console.error(`Fetch failed for ${originalRequest.url}:`, error);
}
}
]);
// 3. Set up precaching with fallbacks
precacheAndRoute([
{ url: "/", revision: "v1.0.0" },
{ url: "/offline.html", revision: "v1.0.0" },
{ url: "/error.html", revision: "v1.0.0" },
...self.__WB_MANIFEST // Generated manifest
]);
// 4. Create fallback plugins
const navigationFallback = new PrecacheFallbackPlugin({
fallbackURL: "/offline.html"
});
const documentFallback = new PrecacheFallbackPlugin({
fallbackURL: "/error.html"
});
// 5. Set up runtime caching with fallbacks
registerRoute(
({ request }) => request.mode === 'navigate',
new NetworkFirst({
cacheName: "pages",
plugins: [navigationFallback]
})
);
registerRoute(
({ request }) => request.destination === 'document',
new StaleWhileRevalidate({
cacheName: "documents",
plugins: [documentFallback]
})
);import {
precacheAndRoute,
addPlugins,
cleanupOutdatedCaches
} from "workbox-precaching";
// Enable detailed logging for development
const debugPlugin = {
cacheKeyWillBeUsed: async ({ request, mode }) => {
console.log(`[DEBUG] Cache key for ${request.url} (${mode})`);
return request.url;
},
cacheWillUpdate: async ({ request, response }) => {
console.log(`[DEBUG] Caching ${request.url}:`, {
status: response.status,
headers: Array.from(response.headers.entries())
});
return response;
},
cachedResponseWillBeUsed: async ({ request, cachedResponse }) => {
console.log(`[DEBUG] Serving cached ${request.url}`);
return cachedResponse;
}
};
// Add debug plugin
addPlugins([debugPlugin]);
// Clean up during development
cleanupOutdatedCaches();
// Precache development assets
precacheAndRoute([
{ url: "/", revision: Date.now().toString() }, // Always update in dev
{ url: "/dev-tools.html", revision: "dev" }
]);Install with Tessl CLI
npx tessl i tessl/npm-workbox-precaching