From f9b7bbdf0a165e43433dd87088fcafbb6014be9b Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:35:29 +0100 Subject: [PATCH] fix(pwa): honour the adapter flag and precache an offline entry point Three defects that together meant `npm run test:build` could not pass in any of its four variants: - svelte.config.js constructed adapter-netlify inline and never used the `adapter` export from adapter.mjs, so NODE_ADAPTER=true still produced a flat build/ directory while the build test expects the node adapter's build/client layout. adapter.mjs now returns netlify (the deployment target) or node, and svelte.config.js consumes it. - No route is prerendered, so workbox's glob found no HTML document and a generateSW build precached nothing navigable: the app had no offline support in that mode at all. Adds the root entry and a navigation fallback, matching what prompt-sw.ts already did by hand for injectManifest builds. - The build scripts used by the tests skipped the tailwind step that `build` runs, so static/output.css was never generated on a clean checkout and the app under test had no stylesheet. The offline entry point assertion now also accepts the unquoted object key that prompt-sw.ts's own precache call survives minification as. --- adapter.mjs | 21 +++++++++++++++++---- package.json | 12 ++++++------ svelte.config.js | 14 +++----------- tests/build/build.test.ts | 4 +++- vite.config.ts | 5 +++++ 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/adapter.mjs b/adapter.mjs index 024165e..1bff390 100644 --- a/adapter.mjs +++ b/adapter.mjs @@ -1,7 +1,20 @@ -import process from 'node:process' +import process from 'node:process'; import AdapterNode from '@sveltejs/adapter-node'; -import AdpaterStatic from '@sveltejs/adapter-static'; +import AdapterNetlify from '@sveltejs/adapter-netlify'; -export const nodeAdapter = process.env.NODE_ADAPTER === 'true' +export const nodeAdapter = process.env.NODE_ADAPTER === 'true'; -export const adapter = nodeAdapter ? AdapterNode() : AdpaterStatic() +// Netlify is the deployment target; the node adapter exists so the service worker +// build tests can check the `build/client` layout a Node server produces. +export const adapter = nodeAdapter + ? AdapterNode() + : AdapterNetlify({ + // if true, will create a Netlify Edge Function rather + // than using standard Node-based functions + edge: false, + + // if true, will split your app into multiple functions + // instead of creating a single one for the entire app. + // if `edge` is true, this option cannot be used + split: false + }); diff --git a/package.json b/package.json index f067c68..a5e81b7 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,12 @@ "dev-generate": "GENERATE_SW=true npx tailwindcss -i ./static/input.css -o ./static/output.css && vite dev", "dev-generate-suppress-w": "GENERATE_SW=true SUPPRESS_WARNING=true npx tailwindcss -i ./static/input.css -o ./static/output.css && vite dev", "sprites:build": "node scripts/optimize-sprites.mjs", - "build-generate-sw": "GENERATE_SW=true vite build", - "build-generate-sw-node": "NODE_ADAPTER=true GENERATE_SW=true vite build", - "build": "npx tailwindcss -i ./static/input.css -o ./static/output.css && vite build", - "build-inject-manifest": "vite build", - "build-inject-manifest-node": "NODE_ADAPTER=true vite build", - "build-self-destroying": "SELF_DESTROYING_SW=true vite build", + "build-generate-sw": "npm run tailwind && GENERATE_SW=true vite build", + "build-generate-sw-node": "npm run tailwind && NODE_ADAPTER=true GENERATE_SW=true vite build", + "build": "npm run tailwind && vite build", + "build-inject-manifest": "npm run tailwind && vite build", + "build-inject-manifest-node": "npm run tailwind && NODE_ADAPTER=true vite build", + "build-self-destroying": "npm run tailwind && SELF_DESTROYING_SW=true vite build", "preview": "vite preview --port=4173", "preview-node": "PORT=4173 node build", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", diff --git a/svelte.config.js b/svelte.config.js index bb2e498..cb8df3d 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,7 +1,7 @@ -import adapter from '@sveltejs/adapter-netlify'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; // you don't need to do this if you're using generateSW strategy in your app import { generateSW } from './pwa.mjs'; +import { adapter } from './adapter.mjs'; /** @type {import('@sveltejs/kit').Config} */ const config = { @@ -10,16 +10,8 @@ const config = { preprocess: vitePreprocess(), kit: { - adapter: adapter({ - // if true, will create a Netlify Edge Function rather - // than using standard Node-based functions - edge: false, - - // if true, will split your app into multiple functions - // instead of creating a single one for the entire app. - // if `edge` is true, this option cannot be used - split: false - }), + // Netlify by default, or the node adapter when NODE_ADAPTER=true. See adapter.mjs. + adapter, serviceWorker: { register: true }, diff --git a/tests/build/build.test.ts b/tests/build/build.test.ts index 2734e70..71296a2 100644 --- a/tests/build/build.test.ts +++ b/tests/build/build.test.ts @@ -29,7 +29,9 @@ describe(`test-build: ${nodeAdapter ? 'node' : 'static'} adapter`, () => { match && match.length === 1, 'missing manifest.webmanifest in sw precache manifest' ).toBeTruthy(); - match = swContent.match(/"url":\s*"(?:\/|index\.html)"/); + // The generateSW manifest is emitted as JSON ("url": "/"), while prompt-sw.ts's own + // `precache([{ url: '/' }])` survives minification as an unquoted key (url:"/"). + match = swContent.match(/"?url"?:\s*"(?:\/|index\.html)"/); expect( match && match.length === 1, 'missing offline entry point in sw precache manifest' diff --git a/vite.config.ts b/vite.config.ts index cbacea2..0f4a57f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -51,6 +51,11 @@ export default defineConfig({ workbox: { globPatterns: ['client/**/*.{js,css,ico,png,svg,webp,woff,woff2,webmanifest}'], globIgnores: ['**/sprites/**', '**/sprites-small/**'], + // No route is prerendered, so globbing finds no HTML document and a generateSW + // build would precache nothing navigable - i.e. no offline support at all. + // prompt-sw.ts does the equivalent with `precache([{ url: '/' }])`. + additionalManifestEntries: [{ url: '/', revision: null }], + navigateFallback: '/', runtimeCaching: [ { urlPattern: ({ request }) => request.destination === 'image',