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

plugins-cleanup.mddocs/

Plugins and Cleanup

Plugin system for extending functionality and cleanup utilities for cache maintenance. These tools help customize precaching behavior and maintain cache hygiene.

Capabilities

Plugin Management

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" }
]);

Cache Cleanup

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 versions

Fallback Plugin

Plugin 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]
  })
);

Advanced Plugin Patterns

Comprehensive Logging Plugin

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]);

Security Plugin

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]);

Performance Monitoring Plugin

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]);

Complete Setup Examples

Production Setup with All Features

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

Development Setup with Debug Features

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

docs

advanced-controller.md

index.md

plugins-cleanup.md

route-strategy.md

simple-setup.md

utilities.md

tile.json