{"version":3,"file":"tempest-pwa-dev-sw.cjs","names":[],"sources":["../../src/vite/tempest-pwa-dev-sw.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { basePrefix } from \"./base-url\";\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** Options for {@link tempestPwaDevSw}. */\nexport interface TempestPwaDevSwOptions {\n    /** Service-worker entry, relative to the project root. Default `src/sw.ts`. */\n    swSrc?: string;\n    /**\n     * URL the worker is served at (must match `registerServiceWorker`).\n     * Default `/sw.js`.\n     *\n     * Write it relative to the site root; requests are matched against both the\n     * bare path and the path prefixed with the resolved Vite `base`, so a\n     * project served from a subpath is handled without extra configuration.\n     */\n    swUrl?: string;\n    /**\n     * Dev URL of the precache manifest. Default `/precache-manifest.json`.\n     * Matched the same way as {@link TempestPwaDevSwOptions.swUrl}.\n     */\n    manifestUrl?: string;\n    /** Serve the worker in dev. Default `true`; set `false` to opt out. */\n    enabled?: boolean;\n}\n\n/**\n * The dev middleware: serve the compiled worker, and a placeholder manifest.\n *\n * Extracted from the plugin so the request handling reads on its own — the\n * plugin body is then only wiring. The manifest is deliberately empty in dev:\n * precaching a set of URLs Vite rewrites on every change would serve stale\n * modules, and the worker's runtime routes still work without it.\n *\n * @param deps - The two URLs, the path matcher and the incremental build.\n * @returns A connect-style middleware.\n */\nfunction createDevSwMiddleware(deps: {\n    swUrl: string;\n    manifestUrl: string;\n    matches: (url: string, target: string) => boolean;\n    buildWorker: () => Promise<string>;\n}): (\n    req: { url?: string },\n    res: {\n        statusCode?: number;\n        setHeader: (name: string, value: string) => void;\n        end: (body: string) => void;\n    },\n    next: () => void,\n) => Promise<void> {\n    return async (req, res, next) => {\n        const url = (req.url ?? \"\").split(\"?\")[0];\n\n        if (deps.matches(url, deps.swUrl)) {\n            try {\n                const source = await deps.buildWorker();\n                res.setHeader(\"Content-Type\", \"application/javascript\");\n                res.setHeader(\"Service-Worker-Allowed\", \"/\");\n                res.setHeader(\"Cache-Control\", \"no-cache\");\n                res.end(source);\n            } catch (error) {\n                res.statusCode = 500;\n                res.end(`// SW dev build failed:\\n// ${String(error)}`);\n            }\n            return;\n        }\n\n        if (deps.matches(url, deps.manifestUrl)) {\n            res.setHeader(\"Content-Type\", \"application/json\");\n            res.setHeader(\"Cache-Control\", \"no-cache\");\n            res.end(JSON.stringify({ version: \"dev\", urls: [] }));\n            return;\n        }\n\n        next();\n    };\n}\n\n/**\n * Dev-server plugin that makes the service worker available under `npm run dev`.\n *\n * The production worker is bundled at build time (`vite.sw.config.ts`), so in\n * dev there is no `/sw.js` to register. This plugin compiles `swSrc` on the fly\n * with esbuild — through one incremental context, not a cold build per request —\n * and serves it as a classic worker, plus an empty `precache-manifest.json` (there are no hashed build assets to precache in\n * dev — push and runtime caching still work). It closes the \"SW in dev\" gap\n * that otherwise only `vite-plugin-pwa`'s `devOptions` covered.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestPwaDevSw } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestPwaDevSw()] });\n */\nexport function tempestPwaDevSw(options: TempestPwaDevSwOptions = {}): TempestVitePlugin {\n    const {\n        swSrc = \"src/sw.ts\",\n        swUrl = \"/sw.js\",\n        manifestUrl = \"/precache-manifest.json\",\n        enabled = true,\n    } = options;\n\n    let root = process.cwd();\n    let base = \"/\";\n\n    /**\n     * Whether a request path addresses `target`.\n     *\n     * Both the bare path and the base-prefixed one are accepted. Which of the\n     * two arrives depends on where this middleware lands relative to Vite's own\n     * base handling, and a project served from a subpath would otherwise never\n     * match — the browser asks for `/app/sw.js` while the option says `/sw.js`.\n     */\n    function matches(url: string, target: string): boolean {\n        if (url === target) return true;\n        const prefix = basePrefix(base);\n        if (prefix === \"/\") return false;\n        return url === `${prefix}${target.replace(/^\\//, \"\")}`;\n    }\n\n    /**\n     * The slice of esbuild's incremental API this plugin uses.\n     *\n     * Typed structurally rather than imported: `esbuild` is not a dependency of\n     * this package. It is resolved at runtime from the app's own tree, where Vite\n     * already brings it, so importing its types would add a build-time dependency\n     * the runtime does not have.\n     */\n    interface BuildContext {\n        rebuild(): Promise<{ outputFiles?: readonly { text: string }[] }>;\n        dispose(): Promise<void>;\n    }\n\n    let context: BuildContext | null = null;\n    let pending: Promise<string> | null = null;\n\n    /**\n     * The incremental build context, created on the first request that needs it.\n     *\n     * Lazy because a project that never registers a worker should not pay for an\n     * esbuild child process, and because `apply: \"serve\"` still loads this module\n     * in a build.\n     *\n     * @returns The context, reused across requests.\n     */\n    async function ensureContext(): Promise<BuildContext> {\n        if (context) return context;\n        const esbuild = (await import(\"esbuild\")) as unknown as {\n            context(options: Record<string, unknown>): Promise<BuildContext>;\n        };\n        context = await esbuild.context({\n            entryPoints: [resolve(root, swSrc)],\n            bundle: true,\n            format: \"iife\",\n            platform: \"browser\",\n            target: \"es2020\",\n            write: false,\n            absWorkingDir: root,\n            logLevel: \"silent\",\n        });\n        return context;\n    }\n\n    /**\n     * Bundle the worker, reusing the previous graph.\n     *\n     * `esbuild.build()` per request was a cold bundle every time, and the request\n     * count is not one per session: `Cache-Control: no-cache` guarantees one per\n     * page load, and Chrome re-fetches the worker script on every navigation and\n     * on its own update checks. `rebuild()` reuses the graph and only redoes what\n     * changed on disk.\n     *\n     * Single-flight, because those requests arrive in bursts and esbuild does not\n     * promise anything about concurrent `rebuild()` calls on one context. Callers\n     * that land while a bundle is in flight share its result, which is the right\n     * answer anyway — they asked for the same file at the same moment.\n     *\n     * @returns The bundled worker source.\n     */\n    function buildWorker(): Promise<string> {\n        if (pending) return pending;\n\n        const run = (async (): Promise<string> => {\n            const ctx = await ensureContext();\n            const result = await ctx.rebuild();\n            const text = result.outputFiles?.[0]?.text;\n            if (text === undefined) throw new Error(\"esbuild produced no service-worker output\");\n            return text;\n        })();\n\n        pending = run;\n        const clear = (): void => {\n            pending = null;\n        };\n        run.then(clear, clear);\n        return run;\n    }\n\n    /**\n     * Tear the context down, releasing esbuild's child process.\n     *\n     * Idempotent, because it is wired to both the dev server closing and the\n     * plugin's `buildEnd`. Without it, restarting the dev server leaks one esbuild\n     * process per restart.\n     */\n    async function disposeContext(): Promise<void> {\n        const current = context;\n        context = null;\n        pending = null;\n        await current?.dispose();\n    }\n\n    const plugin: Plugin = {\n        name: \"tempest-pwa-dev-sw\",\n        apply: \"serve\",\n        async buildEnd() {\n            await disposeContext();\n        },\n        configResolved(config) {\n            root = config.root ?? process.cwd();\n            base = config.base ?? \"/\";\n        },\n        configureServer(server) {\n            if (!enabled) return;\n\n            server.httpServer?.once(\"close\", () => void disposeContext());\n\n            server.middlewares.use(\n                createDevSwMiddleware({ swUrl, manifestUrl, matches, buildWorker }),\n            );\n        },\n    };\n\n    return plugin as TempestVitePlugin;\n}\n"],"mappings":"6DAsCA,SAAS,EAAsB,EAaZ,CACf,OAAO,MAAO,EAAK,EAAK,IAAS,CAC7B,IAAM,GAAO,EAAI,KAAO,GAAA,CAAI,MAAM,GAAG,CAAC,CAAC,GAEvC,GAAI,EAAK,QAAQ,EAAK,EAAK,KAAK,EAAG,CAC/B,GAAI,CACA,IAAM,EAAS,MAAM,EAAK,YAAY,EACtC,EAAI,UAAU,eAAgB,wBAAwB,EACtD,EAAI,UAAU,yBAA0B,GAAG,EAC3C,EAAI,UAAU,gBAAiB,UAAU,EACzC,EAAI,IAAI,CAAM,CAClB,OAAS,EAAO,CACZ,EAAI,WAAa,IACjB,EAAI,IAAI,+BAA+B,OAAO,CAAK,GAAG,CAC1D,CACA,MACJ,CAEA,GAAI,EAAK,QAAQ,EAAK,EAAK,WAAW,EAAG,CACrC,EAAI,UAAU,eAAgB,kBAAkB,EAChD,EAAI,UAAU,gBAAiB,UAAU,EACzC,EAAI,IAAI,KAAK,UAAU,CAAE,QAAS,MAAO,KAAM,CAAC,CAAE,CAAC,CAAC,EACpD,MACJ,CAEA,EAAK,CACT,CACJ,CAkBA,SAAgB,EAAgB,EAAkC,CAAC,EAAsB,CACrF,GAAM,CACF,QAAQ,YACR,QAAQ,SACR,cAAc,0BACd,UAAU,IACV,EAEA,EAAO,QAAQ,IAAI,EACnB,EAAO,IAUX,SAAS,EAAQ,EAAa,EAAyB,CACnD,GAAI,IAAQ,EAAQ,MAAO,GAC3B,IAAM,EAAS,EAAA,WAAW,CAAI,EAE9B,OADI,IAAW,KACR,IAAQ,GAAG,IAAS,EAAO,QAAQ,MAAO,EAAE,GACvD,CAeA,IAAI,EAA+B,KAC/B,EAAkC,KAWtC,eAAe,GAAuC,CAelD,OAdI,IAIJ,EAAU,MAAM,MAHO,OAAO,WAAA,CAGN,QAAQ,CAC5B,YAAa,EAAA,EAAC,EAAA,QAAA,CAAQ,EAAM,CAAK,CAAC,EAClC,OAAQ,GACR,OAAQ,OACR,SAAU,UACV,OAAQ,SACR,MAAO,GACP,cAAe,EACf,SAAU,QACd,CAAC,EACM,EACX,CAkBA,SAAS,GAA+B,CACpC,GAAI,EAAS,OAAO,EAEpB,IAAM,GAAO,SAA6B,CAGtC,IAAM,GAAO,MADQ,MADH,EAAc,EAAA,CACP,QAAQ,EAAA,CACb,cAAc,EAAE,EAAE,KACtC,GAAI,IAAS,IAAA,GAAW,MAAU,MAAM,2CAA2C,EACnF,OAAO,CACX,EAAA,CAAG,EAEH,EAAU,EACV,IAAM,MAAoB,CACtB,EAAU,IACd,EAEA,OADA,EAAI,KAAK,EAAO,CAAK,EACd,CACX,CASA,eAAe,GAAgC,CAC3C,IAAM,EAAU,EAChB,EAAU,KACV,EAAU,KACV,MAAM,GAAS,QAAQ,CAC3B,CAuBA,MAAO,CApBH,KAAM,qBACN,MAAO,QACP,MAAM,UAAW,CACb,MAAM,EAAe,CACzB,EACA,eAAe,EAAQ,CACnB,EAAO,EAAO,MAAQ,QAAQ,IAAI,EAClC,EAAO,EAAO,MAAQ,GAC1B,EACA,gBAAgB,EAAQ,CACf,IAEL,EAAO,YAAY,KAAK,YAAe,KAAK,EAAe,CAAC,EAE5D,EAAO,YAAY,IACf,EAAsB,CAAE,QAAO,cAAa,UAAS,aAAY,CAAC,CACtE,EACJ,CAGG,CACX"}