CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-workbox-precaching

This module efficiently precaches assets for Progressive Web Apps and service workers.

Pending
Overview
Eval results
Files

utilities.mddocs/

Utility Functions

Helper functions for cache key management, request matching, and handler creation. These utilities provide access to precaching functionality without requiring a full controller instance.

Capabilities

Cache Key Management

Function for looking up cache keys for precached URLs.

/**
 * Takes a URL and returns the corresponding cache key used in the precache.
 * Handles revision parameters automatically.
 * @param url - The URL to look up the cache key for
 * @returns The cache key or undefined if not found
 */
function getCacheKeyForURL(url: string): string | undefined;

Usage Examples:

import { getCacheKeyForURL, precacheAndRoute } from "workbox-precaching";

// First precache some assets
precacheAndRoute([
  { url: "/index.html", revision: "abc123" },
  "/logo.png", // No revision, uses URL as cache key
]);

// Later, look up cache keys
const indexCacheKey = getCacheKeyForURL("/index.html");
console.log(indexCacheKey); // "/index.html?__WB_REVISION__=abc123"

const logoCacheKey = getCacheKeyForURL("/logo.png");
console.log(logoCacheKey); // "/logo.png"

const notFoundKey = getCacheKeyForURL("/nonexistent.html");
console.log(notFoundKey); // undefined

Request Matching

Function for looking up requests in the precache without needing a controller instance.

/**
 * Helper function that looks up a request in the precache.
 * This is a wrapper around PrecacheController#matchPrecache using the default controller.
 * @param request - The key (URL string or Request object) to look up
 * @returns Promise resolving to cached response or undefined
 */
function matchPrecache(request: string | Request): Promise<Response | undefined>;

Usage Examples:

import { matchPrecache, precacheAndRoute } from "workbox-precaching";

// Precache assets first
precacheAndRoute([
  { url: "/offline.html", revision: "v1.0" },
  { url: "/app-shell.html", revision: "v2.1" }
]);

// In service worker fetch event
self.addEventListener('fetch', async (event) => {
  // Try to match against precache
  const cachedResponse = await matchPrecache(event.request);
  
  if (cachedResponse) {
    console.log('Serving from precache:', event.request.url);
    event.respondWith(cachedResponse);
    return;
  }
  
  // Fallback to network
  event.respondWith(fetch(event.request));
});

// Can also use with URL strings
const offlineResponse = await matchPrecache("/offline.html");
if (offlineResponse) {
  // Use the cached offline page
  return offlineResponse;
}

// Handle Request objects
const request = new Request("/app-shell.html");
const shellResponse = await matchPrecache(request);

Handler Creation

Function for creating route handlers bound to specific precached URLs.

/**
 * Helper function that creates a route handler bound to a specific precached URL.
 * Returns a handler function that serves the specified precached resource.
 * @param url - The precached URL to bind the handler to
 * @returns Route handler function that serves the precached resource
 * @throws Will throw if the URL is not in the precache
 */
function createHandlerBoundToURL(url: string): RouteHandlerCallback;

Usage Examples:

import { 
  createHandlerBoundToURL, 
  precacheAndRoute 
} from "workbox-precaching";
import { registerRoute } from "workbox-routing";

// Precache assets first
precacheAndRoute([
  { url: "/", revision: "v1.0" },
  { url: "/offline.html", revision: "v1.1" },
  { url: "/app-shell.html", revision: "v2.0" }
]);

// Create handlers for specific URLs
const offlineHandler = createHandlerBoundToURL("/offline.html");
const shellHandler = createHandlerBoundToURL("/app-shell.html");

// Use handlers with routing
registerRoute(
  // Serve offline page for failed navigation requests
  ({ request, event }) => {
    return request.mode === 'navigate' && !navigator.onLine;
  },
  offlineHandler
);

registerRoute(
  // Serve app shell for SPA routes
  ({ request, url }) => {
    return request.mode === 'navigate' && 
           url.pathname.startsWith('/app/');
  },
  shellHandler
);

// Direct usage in fetch event
self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate' && event.request.url.includes('/dashboard')) {
    // Always serve app shell for dashboard routes
    const handler = createHandlerBoundToURL("/app-shell.html");
    event.respondWith(handler({ request: event.request, event }));
  }
});

Advanced Usage Patterns

Fallback Chain

import { 
  matchPrecache, 
  createHandlerBoundToURL,
  precacheAndRoute 
} from "workbox-precaching";

// Precache fallback resources
precacheAndRoute([
  { url: "/offline.html", revision: "v1.0" },
  { url: "/fallback.css", revision: "v1.0" },
  { url: "/404.html", revision: "v1.0" }
]);

// Create fallback handlers
const offlineHandler = createHandlerBoundToURL("/offline.html");
const notFoundHandler = createHandlerBoundToURL("/404.html");

self.addEventListener('fetch', async (event) => {
  // First, try precache
  const cachedResponse = await matchPrecache(event.request);
  if (cachedResponse) {
    event.respondWith(cachedResponse);
    return;
  }
  
  // Then try network
  event.respondWith(
    fetch(event.request)
      .catch(async (error) => {
        // Network failed, use appropriate fallback
        if (event.request.mode === 'navigate') {
          return offlineHandler({ request: event.request, event });
        }
        
        if (event.request.destination === 'document') {
          return notFoundHandler({ request: event.request, event });
        }
        
        // For other resources, try to find a similar precached resource
        return matchPrecache('/fallback.css') || new Response('Offline', {
          status: 503,
          statusText: 'Service Unavailable'
        });
      })
  );
});

Cache Key Validation

import { getCacheKeyForURL, precacheAndRoute } from "workbox-precaching";

// Precache with revisions
precacheAndRoute([
  { url: "/critical.css", revision: "v1.5.2" },
  { url: "/app.js", revision: "v2.1.0" }
]);

// Utility to check if resources are properly cached
function validatePrecacheIntegrity(expectedAssets: Record<string, string>) {
  const issues: string[] = [];
  
  for (const [url, expectedRevision] of Object.entries(expectedAssets)) {
    const cacheKey = getCacheKeyForURL(url);
    
    if (!cacheKey) {
      issues.push(`Missing from precache: ${url}`);
      continue;
    }
    
    // Extract revision from cache key
    const revisionMatch = cacheKey.match(/\?__WB_REVISION__=(.+)$/);
    const actualRevision = revisionMatch ? revisionMatch[1] : 'none';
    
    if (actualRevision !== expectedRevision) {
      issues.push(`Version mismatch for ${url}: expected ${expectedRevision}, got ${actualRevision}`);
    }
  }
  
  return issues;
}

// Check integrity
const issues = validatePrecacheIntegrity({
  '/critical.css': 'v1.5.2',
  '/app.js': 'v2.1.0'
});

if (issues.length > 0) {
  console.warn('Precache integrity issues:', issues);
}

Dynamic Handler Selection

import { 
  createHandlerBoundToURL,
  getCacheKeyForURL,
  precacheAndRoute 
} from "workbox-precaching";
import { registerRoute } from "workbox-routing";

// Precache multiple app shells
precacheAndRoute([
  { url: "/mobile-shell.html", revision: "v1.0" },
  { url: "/desktop-shell.html", revision: "v1.0" },
  { url: "/tablet-shell.html", revision: "v1.0" }
]);

// Dynamic handler based on user agent
const createResponsiveHandler = () => {
  const mobileHandler = createHandlerBoundToURL("/mobile-shell.html");
  const desktopHandler = createHandlerBoundToURL("/desktop-shell.html");
  const tabletHandler = createHandlerBoundToURL("/tablet-shell.html");
  
  return async ({ request, event }) => {
    const userAgent = request.headers.get('user-agent') || '';
    
    if (/Mobile/.test(userAgent)) {
      return mobileHandler({ request, event });
    } else if (/Tablet|iPad/.test(userAgent)) {
      return tabletHandler({ request, event });
    } else {
      return desktopHandler({ request, event });
    }
  };
};

// Register responsive route
registerRoute(
  ({ request }) => request.mode === 'navigate',
  createResponsiveHandler()
);

Conditional Precache Serving

import { matchPrecache, getCacheKeyForURL } from "workbox-precaching";

// Check if resource is precached before serving
async function servePrecachedIfAvailable(request: Request): Promise<Response> {
  const url = new URL(request.url);
  
  // Check if we have this resource precached
  const cacheKey = getCacheKeyForURL(url.pathname);
  if (!cacheKey) {
    throw new Error(`Resource not precached: ${url.pathname}`);
  }
  
  // Try to serve from precache
  const cachedResponse = await matchPrecache(request);
  if (cachedResponse) {
    // Add custom headers to indicate precached response
    const response = new Response(cachedResponse.body, {
      status: cachedResponse.status,
      statusText: cachedResponse.statusText,
      headers: {
        ...cachedResponse.headers,
        'X-Served-From': 'precache',
        'X-Cache-Key': cacheKey
      }
    });
    return response;
  }
  
  throw new Error(`Failed to retrieve precached resource: ${url.pathname}`);
}

Install with Tessl CLI

npx tessl i tessl/npm-workbox-precaching

docs

advanced-controller.md

index.md

plugins-cleanup.md

route-strategy.md

simple-setup.md

utilities.md

tile.json