* fix "dev:build" watch mode
* remove cross-env * append build hash to service worker cache names for easy purge
This commit is contained in:
parent
836b1d8d1b
commit
1e9b7a8aa2
@ -7,7 +7,7 @@ import handleNotificationClick from "./service-worker/notification-click";
|
||||
import handleFetch from "./service-worker/fetch";
|
||||
import handleMessage from "./service-worker/message";
|
||||
|
||||
declare let self: ServiceWorkerGlobalScope;
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(handleInstall(event).then(() => self.skipWaiting()));
|
||||
|
@ -53,7 +53,6 @@ export default function ServiceWorkerUpdateNotifier() {
|
||||
aria-label="An updated version of the app is available. Reload to get the latest version."
|
||||
/>
|
||||
</button>
|
||||
;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -1,4 +1,4 @@
|
||||
declare let self: ServiceWorkerGlobalScope;
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
export default async function handleInstall(event: ExtendableEvent) {
|
||||
console.debug("Service worker installed");
|
||||
|
@ -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)));
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
41
package-lock.json
generated
41
package-lock.json
generated
@ -23,7 +23,6 @@
|
||||
"bullmq": "1.85.1",
|
||||
"clsx": "1.1.1",
|
||||
"compression": "1.7.4",
|
||||
"cross-env": "7.0.3",
|
||||
"express": "4.18.1",
|
||||
"ioredis": "5.0.6",
|
||||
"isbot": "3.5.0",
|
||||
@ -75,6 +74,7 @@
|
||||
"esbuild": "0.14.42",
|
||||
"esbuild-node-externals": "1.4.1",
|
||||
"eslint": "8.16.0",
|
||||
"glob": "7.2.3",
|
||||
"happy-dom": "5.0.0",
|
||||
"husky": "7.0.4",
|
||||
"lint-staged": "13.0.0",
|
||||
@ -7298,27 +7298,11 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@ -16194,6 +16178,7 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -19450,6 +19435,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
@ -19461,6 +19447,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -22389,6 +22376,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
@ -28071,18 +28059,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"requires": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@ -34524,7 +34505,8 @@
|
||||
"path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.7",
|
||||
@ -37005,6 +36987,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
@ -37012,7 +36995,8 @@
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"shell-quote": {
|
||||
"version": "1.7.3",
|
||||
@ -39316,6 +39300,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
|
20
package.json
20
package.json
@ -3,19 +3,19 @@
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"dev:build": "cross-env NODE_ENV=development dotenv npm run build:server -- --watch",
|
||||
"dev:css": "cross-env NODE_ENV=development tailwindcss -i ./styles/tailwind.css -o ./app/styles/tailwind.css --watch",
|
||||
"dev:remix": "cross-env NODE_ENV=development remix watch",
|
||||
"dev:server": "cross-env NODE_ENV=development dotenv node ./server.js",
|
||||
"dev:worker": "esbuild ./app/entry.worker.ts --outfile=./public/entry.worker.js --bundle --format=esm --watch",
|
||||
"dev:init": "cross-env NODE_ENV=development dotenv run-s build:remix build:server",
|
||||
"dev:build": "NODE_ENV=development dotenv npm run build:server -- -- --watch",
|
||||
"dev:css": "NODE_ENV=development tailwindcss -i ./styles/tailwind.css -o ./app/styles/tailwind.css --watch",
|
||||
"dev:remix": "NODE_ENV=development remix watch",
|
||||
"dev:server": "NODE_ENV=development dotenv node ./server.js",
|
||||
"dev:worker": "NODE_ENV=development npm run build:worker -- --watch",
|
||||
"dev:init": "NODE_ENV=development dotenv run-s build:remix build:server",
|
||||
"dev": "npm run dev:init && run-p dev:build dev:worker dev:css dev:remix dev:server",
|
||||
"build:server": "node ./scripts/build-server.js",
|
||||
"build:css": "tailwindcss -i ./styles/tailwind.css -o ./app/styles/tailwind.css",
|
||||
"build:remix": "remix build",
|
||||
"build:worker": "esbuild ./app/entry.worker.ts --outfile=./public/entry.worker.js --minify --bundle --format=esm",
|
||||
"build": "cross-env NODE_ENV=production run-s build:css build:worker build:remix build:server",
|
||||
"start": "cross-env NODE_ENV=production node ./server.js",
|
||||
"build:worker": "node ./scripts/build-worker.js",
|
||||
"build": "NODE_ENV=production run-s build:css build:remix build:worker build:server",
|
||||
"start": "NODE_ENV=production node ./server.js",
|
||||
"test": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
|
||||
@ -66,7 +66,6 @@
|
||||
"bullmq": "1.85.1",
|
||||
"clsx": "1.1.1",
|
||||
"compression": "1.7.4",
|
||||
"cross-env": "7.0.3",
|
||||
"express": "4.18.1",
|
||||
"ioredis": "5.0.6",
|
||||
"isbot": "3.5.0",
|
||||
@ -118,6 +117,7 @@
|
||||
"esbuild": "0.14.42",
|
||||
"esbuild-node-externals": "1.4.1",
|
||||
"eslint": "8.16.0",
|
||||
"glob": "7.2.3",
|
||||
"happy-dom": "5.0.0",
|
||||
"husky": "7.0.4",
|
||||
"lint-staged": "13.0.0",
|
||||
|
67
scripts/build-worker.js
Normal file
67
scripts/build-worker.js
Normal file
@ -0,0 +1,67 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const glob = require("glob");
|
||||
const esbuild = require("esbuild");
|
||||
|
||||
const isDev = process.env.NODE_ENV !== "production";
|
||||
const basePath = process.cwd();
|
||||
const args = process.argv.slice(2);
|
||||
const watch = args.includes("--watch");
|
||||
|
||||
const cacheVersion = isDev
|
||||
? "dev"
|
||||
: (() => {
|
||||
const manifests = glob.sync(path.join(basePath, "/public/build/manifest-*.js"));
|
||||
const manifest = manifests.reduce((mostRecent, manifest) =>
|
||||
fs.statSync(manifest).mtime > fs.statSync(mostRecent).mtime ? manifest : mostRecent,
|
||||
);
|
||||
return manifest.match(/manifest-(\w+).js/)[1].toLowerCase();
|
||||
})();
|
||||
|
||||
esbuild
|
||||
.build({
|
||||
write: true,
|
||||
outfile: path.join(basePath, "public", "entry.worker.js"),
|
||||
entryPoints: [path.join(basePath, "app", "entry.worker.ts")],
|
||||
format: "esm",
|
||||
bundle: true,
|
||||
define: {
|
||||
ASSET_CACHE: `"asset-cache_${cacheVersion}"`,
|
||||
DATA_CACHE: `"data-cache_${cacheVersion}"`,
|
||||
DOCUMENT_CACHE: `"document-cache_${cacheVersion}"`,
|
||||
},
|
||||
watch: watch
|
||||
? {
|
||||
onRebuild(error, buildResult) {
|
||||
const warnings = error?.warnings || buildResult?.warnings;
|
||||
const errors = error?.errors || buildResult?.errors;
|
||||
if (warnings.length) {
|
||||
console.log(esbuild.formatMessages(warnings, { kind: "warning" }));
|
||||
}
|
||||
if (errors.length) {
|
||||
console.log(esbuild.formatMessages(errors, { kind: "error" }));
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Service worker rebuilt successfully");
|
||||
},
|
||||
}
|
||||
: false,
|
||||
})
|
||||
.then(({ errors, warnings }) => {
|
||||
if (warnings.length) {
|
||||
console.log(esbuild.formatMessages(warnings, { kind: "warning" }));
|
||||
}
|
||||
if (errors.length) {
|
||||
console.log(esbuild.formatMessages(errors, { kind: "error" }));
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Service worker build succeeded");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
Loading…
Reference in New Issue
Block a user