Skip to content

Instantly share code, notes, and snippets.

@PatrickJS
Last active December 3, 2023 16:47
Show Gist options
  • Select an option

  • Save PatrickJS/b2cb051c68441e6b31016a45ce102ef5 to your computer and use it in GitHub Desktop.

Select an option

Save PatrickJS/b2cb051c68441e6b31016a45ce102ef5 to your computer and use it in GitHub Desktop.
qwik pwa service-worker
import { defineConfig} from '@vite-pwa/assets-generator/config'
import type { Preset } from '@vite-pwa/assets-generator/config';
export const minimalPreset: Preset = {
transparent: {
sizes: [64, 144, 192, 512],
favicons: [[64, 'favicon.ico']]
},
maskable: {
sizes: [512]
},
apple: {
sizes: [180]
}
}
export default defineConfig({
preset: minimalPreset,
images: ['public/favicon.svg']
})
/*
* WHAT IS THIS FILE?
*
* The service-worker.ts file is used to have state of the art prefetching.
* https://qwik.builder.io/qwikcity/prefetching/overview/
*
* Qwik uses a service worker to speed up your site and reduce latency, ie, not used in the traditional way of offline.
* You can also use this file to add more functionality that runs in the service worker.
*/
import { setupServiceWorker } from "@builder.io/qwik-city/service-worker";
import type { PrecacheEntry } from "workbox-precaching";
import {
cleanupOutdatedCaches,
createHandlerBoundToURL,
precacheAndRoute,
} from "workbox-precaching";
import { NavigationRoute, registerRoute } from "workbox-routing";
const assets = [...publicDirAssets, ...emittedAssets];
cleanupOutdatedCaches();
precacheAndRoute(
urlsToEntries([...routes.map((r) => r.pathname), ...assets], manifestHash)
);
// should be registered after precacheAndRoute
for (const route of routes) {
registerRoute(
new NavigationRoute(createHandlerBoundToURL(route.pathname), {
allowlist: [route.pattern],
})
);
}
setupServiceWorker();
addEventListener("install", () => self.skipWaiting());
addEventListener("activate", () => self.clients.claim());
const base = "/build/"; // temp, it should be dynamic based on the build
const qprefetchEvent = new MessageEvent<ServiceWorkerMessage>("message", {
data: {
type: "qprefetch",
base,
links: routes.map((route) => route.pathname),
bundles: appBundles.map((appBundle) => appBundle[0]),
},
});
self.dispatchEvent(qprefetchEvent);
function urlsToEntries(urls: string[], hash: string): PrecacheEntry[] {
const matcher = /^build\/q-([a-f0-9]{8})\./;
return urls.map((url) => {
// we should think about enabling this https://github.com/GoogleChrome/workbox/issues/2024
// revision: hash
const match = url.match(matcher);
return {
url,
revision: `${match ? match[1] : hash}`,
};
});
}
declare const self: ServiceWorkerGlobalScope;
export type AppSymbols = Map<string, string>;
export type AppBundle =
| [bundleName: string, importedBundleIds: number[]]
| [
bundleName: string,
importedBundleIds: number[],
symbolHashesInBundle: string[],
];
export type LinkBundle = [routePattern: RegExp, bundleIds: number[]];
export interface QPrefetchData {
links?: string[];
bundles?: string[];
symbols?: string[];
}
export interface QPrefetchMessage extends QPrefetchData {
type: "qprefetch";
base: string;
}
export type ServiceWorkerMessage = QPrefetchMessage;
export interface ServiceWorkerMessageEvent {
data: ServiceWorkerMessage;
}
declare const appBundles: AppBundle[];
declare const libraryBundleIds: number[];
declare const linkBundles: LinkBundle[];
declare const publicDirAssets: string[];
declare const emittedAssets: string[];
declare const routes: { pathname: string; pattern: RegExp }[];
declare const manifestHash: string;
import { routes } from '@qwik-city-plan';
import { PluginOption, defineConfig, Plugin, } from "vite";
import { QwikBuildTarget, QwikVitePlugin, qwikVite } from "@builder.io/qwik/optimizer";
import { QwikCityPlugin, qwikCity } from "@builder.io/qwik-city/vite";
import tsconfigPaths from "vite-tsconfig-paths";
import path from "node:path";
import fs from "node:fs/promises";
import fg from 'fast-glob'
export default defineConfig(() => {
return {
plugins: [
qwikCity(), qwikVite(), tsconfigPaths(),
qwikPwa()
// VitePWA({
// strategies: 'injectManifest',
// srcDir: 'src',
// filename: 'routes/service-worker.ts'
// }),
],
preview: {
headers: {
"Cache-Control": "public, max-age=600",
},
},
};
});
let tempGenerateFunc: NonNullable<Plugin['generateBundle']> = (() => {}) satisfies Plugin['generateBundle']
type OutputBundle = Parameters<typeof tempGenerateFunc>[1]
export function qwikPwa(): PluginOption {
let qwikPlugin: QwikVitePlugin | null = null;
let qwikCityPlugin: QwikCityPlugin | null = null;
let rootDir: string | null = null
let outDir: string | null = null;
let publicDir: string | null = null;
let target: QwikBuildTarget | null = null;
// make the type an argument of the generateBundle function
let bundle: OutputBundle
let clientOutDir: string
let basePathRelDir: string
let clientOutBaseDir: string
let swClientDistPath: string
return [
{
name: 'qwik-pwa-mutual',
enforce: 'post',
configResolved(config) {
rootDir = path.resolve(config.root);
qwikPlugin = config.plugins.find((p) => p.name === 'vite-plugin-qwik') as QwikVitePlugin;
qwikCityPlugin = config.plugins.find(
(p) => p.name === 'vite-plugin-qwik-city'
) as QwikCityPlugin;
target = qwikPlugin!.api.getOptions().target
publicDir = config.publicDir;
outDir = config.build?.outDir;
clientOutDir = qwikPlugin!.api.getClientOutDir()!;
basePathRelDir = qwikCityPlugin!.api.getBasePathname().replace(/^\/|\/$/, '');
clientOutBaseDir = path.join(clientOutDir, basePathRelDir);
swClientDistPath = path.join(clientOutBaseDir, 'service-worker.js');
},
},
{
name: 'qwik-pwa',
enforce: 'post',
generateBundle(_, _bundle) {
bundle = _bundle
},
closeBundle: {
sequential: true,
order: 'post',
async handler() {
if (target !== 'client') {
return
}
const publicDirAssets = await fg.glob('**/*' , {cwd: publicDir!})
// the q-*.js files are going to be handled by qwik itself
const emittedAssets = Object.keys(bundle).filter((key) => !/.*q-.*\.js$/.test(key))
const routes = qwikCityPlugin!.api.getRoutes()
console.log(qwikCityPlugin!.api.getRoutes())
const swCode = await fs.readFile(swClientDistPath, 'utf-8');
const swCodeUpdate = `
const publicDirAssets = ${JSON.stringify(publicDirAssets)};
const emittedAssets = ${JSON.stringify(emittedAssets)};
const routes = [${routes.map(route => `{ pathname: ${JSON.stringify(route.pathname)}, pattern: new RegExp(${JSON.stringify(route.pattern.source)}) }`).join(',\n')}];
${swCode}
`
await fs.writeFile(swClientDistPath, swCodeUpdate);
}
}
}, {
name: 'qwik-pwa-ssr',
enforce: 'post',
closeBundle: {
sequential: true,
order: 'post',
async handler() {
if (target !== 'ssr') {
return
}
const swCode = await fs.readFile(swClientDistPath, 'utf-8');
const manifest = qwikPlugin!.api.getManifest()
const swCodeUpdate = `
const manifestHash = ${JSON.stringify(manifest?.manifestHash)};
${swCode}
`
await fs.writeFile(swClientDistPath, swCodeUpdate);
}
}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment