Service Worker
vite-plugin-tapi composes cleanly with vite-plugin-pwa in injectManifest mode. The plugin redirects the client build to dist/client/, which is exactly where VitePWA emits sw.js, and the server bundle is built separately so nothing leaks across.
Installation
Section titled “Installation”pnpm add -D vite-plugin-pwaVite Config
Section titled “Vite Config”import { defineConfig } from "vite";import tapi from "@farbenmeer/vite-plugin-tapi";import { VitePWA } from "vite-plugin-pwa";
export default defineConfig({ plugins: [ tapi(), VitePWA({ strategies: "injectManifest", srcDir: "src", filename: "service-worker.ts", injectRegister: "auto", devOptions: { enabled: true, type: "module" }, }), ],});Setting devOptions.enabled: true makes the service worker run during vite dev as well. Without it the SW only runs in vite preview and production.
Service Worker
Section titled “Service Worker”import { handleTapiRequest, listenForInvalidations, cleanup,} from "@farbenmeer/tapi/worker";
declare const self: ServiceWorkerGlobalScope;
self.addEventListener("activate", (event) => { event.waitUntil(cleanup({ maximumStaleAge: 60 * 60 * 24 * 7 }));});
self.addEventListener("fetch", (event) => { const url = new URL(event.request.url); if ( url.pathname.startsWith("/api") && !url.pathname.startsWith("/api/__tapi") ) { event.respondWith(handleTapiRequest(event.request)); }});
listenForInvalidations({ url: "/api/__tapi/invalidations" });TypeScript
Section titled “TypeScript”Add "WebWorker" to the lib array in tsconfig.json so TypeScript recognizes ServiceWorkerGlobalScope and related globals:
{ "compilerOptions": { "lib": ["ESNext", "DOM", "WebWorker"] }}- Adjust the
/apipath checks in the service worker to match thebasePathyou pass totapi(). cleanup’smaximumStaleAgeis a grace period in seconds past a cache entry’sexpiresAtbefore it is deleted on the next SW activation.