Skip to content

Add Workbox service worker example - #283

Merged
bcomnes merged 18 commits into
static-client-cache-domstack-swfrom
static-client-cache-workbox-sw
Aug 23, 2026
Merged

bcomnes merged 18 commits into
static-client-cache-domstack-swfrom
static-client-cache-workbox-sw

Conversation

@bcomnes

@bcomnes bcomnes commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the Workbox-based static-MPA service-worker example on top of #262 and #282.

  • Converts the finalized Domstack manifest into Workbox precache and route policy through manifestBuilt.
  • Demonstrates Workbox routing, expiration, offline fallback, updates, reset, inspection, and background events.
  • Includes example documentation and the Workbox integration design plan.

Validation

  • npm run build --workspace=@domstack/static-mpa-workbox-offline-example
  • npx tsc -p examples/static-mpa-workbox-offline/tsconfig.json
  • npm test

Stack created with GitHub Stacks CLIGive Feedback 💬

@socket-security

socket-security Bot commented Jul 31, 2026

Copy link
Copy Markdown

@coveralls

coveralls commented Jul 31, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 32667671422

Coverage decreased (-0.02%) to 92.821%

Details

  • Coverage decreased (-0.02%) from the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 6254
Covered Lines: 5968
Line Coverage: 95.43%
Relevant Branches: 1324
Covered Branches: 1066
Branch Coverage: 80.51%
Branches in Coverage %: Yes
Coverage Strength: 158.11 hits per line

💛 - Coveralls

bcomnes added 17 commits August 23, 2026 09:41
@bcomnes
bcomnes merged commit 6b9bb9b into master Aug 23, 2026
8 checks passed
@bcomnes
bcomnes deleted the static-client-cache-workbox-sw branch August 23, 2026 21:31
@bcomnes

bcomnes commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

Workbox Workflow Integration Plan

Status: Example implemented with injected policy

examples/static-mpa-workbox-offline demonstrates the current preferred Workbox integration model.

Domstack does not run Workbox injectManifest and does not generate a JavaScript global loaded with importScripts().

Instead, hooks.manifestBuilt computes a Workbox-oriented policy from the finalized Domstack manifest and injects it into the final /service-worker.js bundle with context.defineServiceWorkerConstant().

The authored service worker remains normal user code.

It imports Workbox packages directly and passes the generated precacheManifest field to Workbox APIs.

Goals

  • Support Workbox without forcing it into domstack core.
  • Generate correct Workbox precache data from Domstack's real emitted outputs.
  • Keep user-authored service-worker code inspectable and customizable.
  • Use Workbox for mature precaching, routing, strategies, plugins, and update helpers.
  • Avoid runtime policy fetches and generated global scripts.
  • Keep watch mode safe when no manifest policy exists.

Non-goals

  • Do not make service workers automatic for every domstack build.
  • Do not require Workbox as a core dependency for sites that do not opt into it.
  • Do not cache API/data endpoints by default.
  • Do not hide service-worker stickiness or recovery requirements.
  • Do not replace custom user-authored service workers.

Current example architecture

The Workbox example uses:

  • src/globals/domstack-manifest/domstack-manifest.settings.ts
  • src/globals/domstack-manifest/policy-build.ts
  • src/globals/service-worker/service-worker.ts
  • src/globals/global-client/*

The manifest settings hook injects a policy constant:

context.defineServiceWorkerConstant('__DOMSTACK_WORKBOX_POLICY__', {
  version: manifest.version,
  offlineFallbackUrl: '/offline/',
  precacheManifest: [
    { url: '/', revision: 'sha256hex...' },
    { url: '/about/', revision: 'sha256hex...' },
    { url: '/global-ABC123.css', revision: null, integrity: 'sha256-...' },
  ],
  runtimeUrls: ['/progressive-cache/'],
  networkOnlyUrls: ['/admin/'],
})

The service worker reads the injected policy inside the manifest-enabled branch:

declare const __DOMSTACK_WORKBOX_POLICY__: StaticMpaWorkboxServiceWorkerPolicy

if (manifestEnabled) {
  const policy = __DOMSTACK_WORKBOX_POLICY__
  precacheAndRoute(policy.precacheManifest)
}

The policy constant must not be read at module top level in watch mode.

Watch mode builds do not define it.

Workbox APIs currently used

The example uses:

  • workbox-precaching
    • precacheAndRoute()
    • cleanupOutdatedCaches()
    • matchPrecache() where needed by fallback behavior
  • workbox-routing
    • registerRoute()
    • setCatchHandler()
  • workbox-strategies
    • NetworkFirst
    • NetworkOnly
  • workbox-cacheable-response
    • CacheableResponsePlugin
  • workbox-expiration
    • ExpirationPlugin
  • workbox-recipes
    • offlineFallback()
  • workbox-window
    • registration lifecycle events
    • messageSkipWaiting()

Policy shape

Workbox's native precache input is:

type WorkboxPrecacheEntry = {
  url: string
  revision: string | null
  integrity?: string
}

The example policy includes that native shape plus app-specific route policy:

type StaticMpaWorkboxServiceWorkerPolicy = {
  version: string
  offlineFallbackUrl: string
  precacheManifest: WorkboxPrecacheEntry[]
  runtimeUrls: string[]
  networkOnlyUrls: string[]
}

Only precacheManifest is passed directly to Workbox precaching.

runtimeUrls, networkOnlyUrls, and offlineFallbackUrl are app policy and are mapped explicitly to Workbox routing/strategy APIs.

Why injected policy is preferred

Injected policy has these advantages:

  • no runtime fetch for a policy JSON file
  • no generated JS file imported by the service worker
  • no importScripts() convention
  • no Workbox self.__WB_MANIFEST source transform
  • policy changes alter /service-worker.js bytes
  • browser update lifecycle is triggered naturally
  • the authored service worker remains regular bundled module code

This means Domstack only needs the general manifestBuilt hook and final service-worker build step.

It does not need Workbox-specific source transformation in core.

Watch mode

Workbox precaching is disabled in watch mode.

Watch mode sets DOMSTACK_MANIFEST_ENABLED=false and does not run the manifest/policy injection path.

The service worker branch for watch mode:

  • installs immediately
  • deletes owned caches
  • unregisters itself
  • registers no Workbox routes
  • does not touch the injected policy constant

The client also unregisters existing workers and clears known caches in watch mode.

Watch builds disable esbuild splitting so /service-worker.js stays self-contained during cleanup.

Client lifecycle

The Workbox example uses workbox-window because it provides cleaner lifecycle events than hand-rolled registration logic.

Current behavior:

  • installing shows “Installing offline cache…”
  • activated shows ready state for first install
  • waiting prompts for update or applies a previously waiting update
  • controlling reloads after accepted updates
  • redundant logs to the console only

Watch-mode cleanup happens before normal registration and does not wait for window.load.

Production registration waits for window.load.

Runtime caching policy

The example only runtime-caches routes selected by manifest vars.

It uses Workbox plugins to keep runtime cache behavior bounded:

  • CacheableResponsePlugin limits which responses can enter the cache.
  • ExpirationPlugin limits cache age/count.

The example does not cache arbitrary API/data requests by default.

Potential package helper

A future helper could live outside core or as an optional export:

import { createWorkboxPolicy } from '@domstack/static/workbox'

export default {
  hooks: {
    manifestBuilt: [context => {
      context.defineServiceWorkerConstant(
        '__APP_WORKBOX_POLICY__',
        createWorkboxPolicy(context.manifest, options),
      )
    }],
  },
}

The helper could cover:

  • max precache size
  • include/exclude filters
  • revision/null handling for hashed URLs
  • optional integrity inclusion
  • route-policy derivation from selected manifestVars
  • warnings for skipped entries

This should remain optional.

Domstack core should continue exposing generic manifest hooks rather than hard-coding Workbox behavior.

Deprecated ideas

These ideas were considered but are not the current direction:

  • generating a public Workbox manifest module and importing it from the service worker
  • fetching a policy JSON file during service-worker install
  • using importScripts() to load generated globals
  • transforming self.__WB_MANIFEST like Workbox injectManifest
  • generating the entire Workbox service worker from core config

They remain possible for external integrations, but the example and current plan prefer injected constants.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants