mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
perf(pwa): reduce precache asset fanout
Limit PWA precache to app shell assets and add bounded runtime caching for scoped deployments.
This commit is contained in:
+4
-1
@@ -33,7 +33,10 @@
|
||||
"stream-browserify",
|
||||
// The PWA service worker entry is injected by vite-plugin-pwa, which Knip does not trace here.
|
||||
"workbox-core",
|
||||
"workbox-precaching"
|
||||
"workbox-expiration",
|
||||
"workbox-precaching",
|
||||
"workbox-routing",
|
||||
"workbox-strategies"
|
||||
],
|
||||
"ignoreIssues": {
|
||||
// This import is intentionally satisfied transitively through bitsocial-react-hooks.
|
||||
|
||||
@@ -44,7 +44,10 @@
|
||||
"tcp-port-used": "1.0.2",
|
||||
"typescript": "6.0.2",
|
||||
"workbox-core": "7.4.0",
|
||||
"workbox-expiration": "7.4.0",
|
||||
"workbox-precaching": "7.4.0",
|
||||
"workbox-routing": "7.4.0",
|
||||
"workbox-strategies": "7.4.0",
|
||||
"zustand": "4.4.3"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -2,26 +2,51 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
|
||||
import { clientsClaim } from 'workbox-core';
|
||||
import { ExpirationPlugin } from 'workbox-expiration';
|
||||
import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching';
|
||||
import { registerRoute } from 'workbox-routing';
|
||||
import { NetworkFirst } from 'workbox-strategies';
|
||||
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
const precacheEntries = self.__WB_MANIFEST.filter((entry) => (typeof entry === 'string' ? entry !== 'index.html' : entry.url !== 'index.html'));
|
||||
const runtimeAssetDestinations = new Set<RequestDestination>(['font', 'image', 'manifest', 'script', 'style']);
|
||||
const scopeRoot = self.registration.scope.endsWith('/') ? self.registration.scope : `${self.registration.scope}/`;
|
||||
const scopePath = (path: string) => new URL(path, scopeRoot).pathname;
|
||||
const apiPath = scopePath('api');
|
||||
const apiPathPrefix = scopePath('api/');
|
||||
const internalPathPrefix = scopePath('_(');
|
||||
const runtimeAssetPathPrefixes = ['assets/', 'translations/'].map(scopePath);
|
||||
const isApiPath = (pathname: string) => pathname === apiPath || pathname.startsWith(apiPathPrefix);
|
||||
|
||||
// Precache revisioned assets, but let navigations fetch fresh HTML first.
|
||||
cleanupOutdatedCaches();
|
||||
precacheAndRoute(precacheEntries);
|
||||
|
||||
registerRoute(
|
||||
({ request, url }) => request.mode === 'navigate' && !url.pathname.startsWith('/api') && !/^\/_\(.*\)/.test(url.pathname),
|
||||
({ request, url }) => request.mode === 'navigate' && !isApiPath(url.pathname) && !url.pathname.startsWith(internalPathPrefix),
|
||||
new NetworkFirst({
|
||||
cacheName: 'html-cache',
|
||||
networkTimeoutSeconds: 3,
|
||||
}),
|
||||
);
|
||||
|
||||
registerRoute(
|
||||
({ request, url }) =>
|
||||
url.origin === self.location.origin &&
|
||||
(runtimeAssetDestinations.has(request.destination) || runtimeAssetPathPrefixes.some((prefix) => url.pathname.startsWith(prefix))),
|
||||
new StaleWhileRevalidate({
|
||||
cacheName: 'runtime-static-assets',
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 200,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 30,
|
||||
purgeOnQuotaError: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
// Standard SW lifecycle methods
|
||||
self.skipWaiting();
|
||||
clientsClaim();
|
||||
|
||||
+75
-6
@@ -6,6 +6,74 @@ import { VitePWA } from 'vite-plugin-pwa';
|
||||
|
||||
const { version: packageVersion } = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
||||
const appVersion = `${process.env.VITE_APP_VERSION || packageVersion}`.trim() || packageVersion;
|
||||
const publicBase = process.env.PUBLIC_URL || '/';
|
||||
const buildOutDir = 'build';
|
||||
const basePathPrefix = (() => {
|
||||
const pathname = new URL(publicBase, 'https://example.invalid/').pathname;
|
||||
return pathname === '/' ? '' : pathname.replace(/^\/+|\/+$/g, '');
|
||||
})();
|
||||
const neverPrecacheUrls = new Set(['index.html', 'version.json']);
|
||||
const vitePwaManagedAssetUrls = new Set([
|
||||
'manifest.webmanifest',
|
||||
'favicon.ico',
|
||||
'favicon2.ico',
|
||||
'robots.txt',
|
||||
'apple-touch-icon.png',
|
||||
'manifest-icon-192x192.png',
|
||||
'manifest-icon-512x512.png',
|
||||
]);
|
||||
const baselineAppShellUrls = new Set([
|
||||
'registerSW.js',
|
||||
'manifest.json',
|
||||
'manifest.webmanifest',
|
||||
'favicon.ico',
|
||||
'favicon2.ico',
|
||||
'robots.txt',
|
||||
'apple-touch-icon.png',
|
||||
'manifest-icon-192x192.png',
|
||||
'manifest-icon-512x512.png',
|
||||
]);
|
||||
|
||||
function normalizePrecacheUrl(url) {
|
||||
const normalizedUrl = url.split('?')[0].replace(/^[./]+/, '');
|
||||
|
||||
if (basePathPrefix && normalizedUrl.startsWith(`${basePathPrefix}/`)) {
|
||||
return normalizedUrl.slice(basePathPrefix.length + 1);
|
||||
}
|
||||
|
||||
return normalizedUrl;
|
||||
}
|
||||
|
||||
function collectIndexAssetUrls() {
|
||||
const indexHtml = readFileSync(new URL(`./${buildOutDir}/index.html`, import.meta.url), 'utf8');
|
||||
const urls = new Set(baselineAppShellUrls);
|
||||
const assetAttributePattern = /\b(?:href|src)=["'](?:\.\/|\/)?([^"']+\.(?:css|ico|js|json|png|webmanifest))(?:\?[^"']*)?["']/g;
|
||||
|
||||
for (const match of indexHtml.matchAll(assetAttributePattern)) {
|
||||
urls.add(normalizePrecacheUrl(match[1]));
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
function keepAppShellPrecacheOnly(manifestEntries) {
|
||||
const appShellUrls = collectIndexAssetUrls();
|
||||
const manifest = manifestEntries.filter((entry) => {
|
||||
const normalizedUrl = normalizePrecacheUrl(entry.url);
|
||||
|
||||
if (neverPrecacheUrls.has(normalizedUrl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vitePwaManagedAssetUrls.has(normalizedUrl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return appShellUrls.has(normalizedUrl);
|
||||
});
|
||||
|
||||
return { manifest };
|
||||
}
|
||||
|
||||
function appVersionMetadataPlugin() {
|
||||
const payload = `${JSON.stringify({ version: appVersion })}\n`;
|
||||
@@ -85,7 +153,8 @@ export default defineConfig({
|
||||
strategies: 'injectManifest',
|
||||
injectManifest: {
|
||||
maximumFileSizeToCacheInBytes: 20000000,
|
||||
globIgnores: ['**/version.json'],
|
||||
globPatterns: ['**/*.{css,html,ico,js,json,png,webmanifest}'],
|
||||
manifestTransforms: [keepAppShellPrecacheOnly],
|
||||
},
|
||||
srcDir: 'src',
|
||||
filename: 'sw.ts',
|
||||
@@ -103,17 +172,17 @@ export default defineConfig({
|
||||
display: 'standalone',
|
||||
icons: [
|
||||
{
|
||||
src: '/android-chrome-192x192.png',
|
||||
src: 'manifest-icon-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: '/android-chrome-512x512.png',
|
||||
src: 'manifest-icon-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: '/android-chrome-512x512.png',
|
||||
src: 'manifest-icon-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
@@ -216,7 +285,7 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
// Use 'build' to match what electron/main.js expects (../build/index.html)
|
||||
outDir: 'build',
|
||||
outDir: buildOutDir,
|
||||
emptyOutDir: true,
|
||||
sourcemap: process.env.GENERATE_SOURCEMAP === 'true',
|
||||
target: process.env.ELECTRON ? 'electron-renderer' : 'esnext',
|
||||
@@ -248,7 +317,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
base: process.env.PUBLIC_URL || '/',
|
||||
base: publicBase,
|
||||
optimizeDeps: {
|
||||
include: ['ethers', 'assert', 'buffer', 'process', 'util', 'stream-browserify', 'isomorphic-fetch', 'workbox-core', 'workbox-precaching'],
|
||||
},
|
||||
|
||||
@@ -90,7 +90,10 @@ __metadata:
|
||||
wait-on: "npm:9.0.3"
|
||||
workbox-build: "npm:7.4.0"
|
||||
workbox-core: "npm:7.4.0"
|
||||
workbox-expiration: "npm:7.4.0"
|
||||
workbox-precaching: "npm:7.4.0"
|
||||
workbox-routing: "npm:7.4.0"
|
||||
workbox-strategies: "npm:7.4.0"
|
||||
workbox-window: "npm:7.4.0"
|
||||
zustand: "npm:4.4.3"
|
||||
dependenciesMeta:
|
||||
|
||||
Reference in New Issue
Block a user