* fix "dev:build" watch mode

* remove cross-env
* append build hash to service worker cache names for easy purge
This commit is contained in:
m5r
2022-06-11 15:13:28 +02:00
parent 836b1d8d1b
commit 1e9b7a8aa2
12 changed files with 137 additions and 76 deletions

View File

@ -1,6 +1,6 @@
import { deleteCaches } from "./cache-utils";
declare let self: ServiceWorkerGlobalScope;
declare const self: ServiceWorkerGlobalScope;
export default async function handleActivate(event: ExtendableEvent) {
console.debug("Service worker activated");

View File

@ -1,8 +1,8 @@
import { json } from "@remix-run/server-runtime";
export const ASSET_CACHE = "asset-cache";
export const DATA_CACHE = "data-cache";
export const DOCUMENT_CACHE = "document-cache";
declare const ASSET_CACHE: string;
declare const DATA_CACHE: string;
declare const DOCUMENT_CACHE: string;
export function isAssetRequest(request: Request) {
return ["font", "image", "script", "style"].includes(request.destination);
@ -17,7 +17,7 @@ export function isDocumentGetRequest(request: Request) {
return request.method.toLowerCase() === "get" && request.mode === "navigate";
}
export function cacheAsset(event: FetchEvent): Promise<Response> {
export function fetchAsset(event: FetchEvent): Promise<Response> {
// stale-while-revalidate
const url = new URL(event.request.url);
return caches
@ -53,7 +53,7 @@ export function cacheAsset(event: FetchEvent): Promise<Response> {
// stores the timestamp for when each URL's cached response has been revalidated
const lastTimeRevalidated: Record<string, number> = {};
export function cacheLoaderData(event: FetchEvent): Promise<Response> {
export function fetchLoaderData(event: FetchEvent): Promise<Response> {
const url = new URL(event.request.url);
const path = url.pathname + url.search;
@ -145,7 +145,7 @@ async function areResponsesEqual(a: Response, b: Response): Promise<boolean> {
return true;
}
export function cacheDocument(event: FetchEvent): Promise<Response> {
export function fetchDocument(event: FetchEvent): Promise<Response> {
// network-first
const url = new URL(event.request.url);
console.debug("Serving document from network", url.pathname);
@ -170,6 +170,7 @@ export function cacheDocument(event: FetchEvent): Promise<Response> {
export async function deleteCaches() {
const allCaches = await caches.keys();
await Promise.all(allCaches.map((cacheName) => caches.delete(cacheName)));
const cachesToDelete = allCaches.filter((cacheName) => cacheName !== ASSET_CACHE);
await Promise.all(cachesToDelete.map((cacheName) => caches.delete(cacheName)));
console.debug("Caches deleted");
}

View File

@ -1,25 +1,25 @@
import {
cacheAsset,
cacheDocument,
cacheLoaderData,
fetchAsset,
fetchDocument,
fetchLoaderData,
isAssetRequest,
isDocumentGetRequest,
isLoaderRequest,
} from "./cache-utils";
declare let self: ServiceWorkerGlobalScope;
declare const self: ServiceWorkerGlobalScope;
export default async function handleFetch(event: FetchEvent) {
if (isAssetRequest(event.request)) {
return cacheAsset(event);
return fetchAsset(event);
}
if (isLoaderRequest(event.request)) {
return cacheLoaderData(event);
return fetchLoaderData(event);
}
if (isDocumentGetRequest(event.request)) {
return cacheDocument(event);
return fetchDocument(event);
}
return fetch(event.request);

View File

@ -1,4 +1,4 @@
declare let self: ServiceWorkerGlobalScope;
declare const self: ServiceWorkerGlobalScope;
export default async function handleInstall(event: ExtendableEvent) {
console.debug("Service worker installed");

View File

@ -1,8 +1,7 @@
import type { AssetsManifest } from "@remix-run/react/entry";
import { ASSET_CACHE } from "./cache-utils";
declare let self: ServiceWorkerGlobalScope;
declare const ASSET_CACHE: string;
declare const self: ServiceWorkerGlobalScope;
export default async function handleMessage(event: ExtendableMessageEvent) {
if (event.data.type === "SYNC_REMIX_MANIFEST") {
@ -13,32 +12,31 @@ export default async function handleMessage(event: ExtendableMessageEvent) {
async function handleSyncRemixManifest(event: ExtendableMessageEvent) {
console.debug("Caching routes modules");
await cacheStaticAssets(event.data.manifest);
}
async function cacheStaticAssets(manifest: AssetsManifest) {
const cachePromises: Map<string, Promise<void>> = new Map();
const assetCache = await caches.open(ASSET_CACHE);
const manifest: AssetsManifest = event.data.manifest;
const routes = [...Object.values(manifest.routes), manifest.entry];
const assetsToCache: string[] = [];
for (const route of routes) {
if (!cachePromises.has(route.module)) {
cachePromises.set(route.module, cacheAsset(route.module));
}
assetsToCache.push(route.module);
if (route.imports) {
for (const assetUrl of route.imports) {
if (!cachePromises.has(assetUrl)) {
cachePromises.set(assetUrl, cacheAsset(assetUrl));
}
}
assetsToCache.push(...route.imports);
}
}
await purgeStaticAssets(assetsToCache);
await cacheStaticAssets(assetsToCache);
}
async function cacheStaticAssets(assetsToCache: string[]) {
const cachePromises: Map<string, Promise<void>> = new Map();
const assetCache = await caches.open(ASSET_CACHE);
assetsToCache.forEach((assetUrl) => cachePromises.set(assetUrl, cacheAsset(assetUrl)));
await Promise.all(cachePromises.values());
async function cacheAsset(assetUrl: string) {
if (await assetCache.match(assetUrl)) {
// no need to update the asset, it has a unique hash in its name
return;
}
@ -48,3 +46,14 @@ async function cacheStaticAssets(manifest: AssetsManifest) {
});
}
}
async function purgeStaticAssets(assetsToCache: string[]) {
const assetCache = await caches.open(ASSET_CACHE);
const cachedAssets = await assetCache.keys();
const cachesToDelete = cachedAssets.filter((asset) => !assetsToCache.includes(new URL(asset.url).pathname));
console.log(
"cachesToDelete",
cachesToDelete.map((c) => new URL(c.url).pathname),
);
await Promise.all(cachesToDelete.map((asset) => assetCache.delete(asset)));
}

View File

@ -1,6 +1,6 @@
import { removeBadge } from "~/utils/pwa.client";
declare let self: ServiceWorkerGlobalScope;
declare const self: ServiceWorkerGlobalScope;
// noinspection TypeScriptUnresolvedVariable
export default async function handleNotificationClick(event: NotificationEvent) {

View File

@ -1,7 +1,7 @@
import type { NotificationPayload } from "~/utils/web-push.server";
import { addBadge } from "~/utils/pwa.client";
declare let self: ServiceWorkerGlobalScope;
declare const self: ServiceWorkerGlobalScope;
const defaultOptions: NotificationOptions = {
icon: "/icons/android-chrome-192x192.png",