Files
wait4xandGLM-5.3 2e33a7cd2e fix(pwa): SW 缓存版本 v2→v3——冲刷主题修复前的旧页缓存
用户报首页刷新偶现浅色、内页深色。本地/生产双端复测均正确
(首帧即 dark),最可能是 SW 缓存里残留 data-astro-rerun 修复前的
旧首页(无主题脚本)。版本号 bump 强制 activate 清旧缓存。

验证: pnpm astro build;本地四页帧序列实测全 dark

Spec: none (缓存冲刷,环境性症状防御)

Co-authored-by: GLM-5.3
2026-11-02 09:06:42 +08:00

94 lines
3.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* wait4x.online Service Worker — 静态站保守策略
* 设计约束(改前必读):
* - HTML 走 network-first:站点是 SSG 但内容会更新,且上游还有 ESA 边缘缓存——
* SW 不能比边缘更激进,否则"刷了缓存还有 SW 旧页"双重陈旧坑
* - /_astro/*(内容哈希文件名)、字体切片、photos 为不可变资源:cache-first
* - Artalk 评论 API 与 /admin 完全绕过 SW(动态数据不缓存、CMS 走网络)
* - 无 precache(首访零负担);激活时清旧版本缓存
*/
const VERSION = 'w4x-v3'; // v3:冲刷主题修复(data-astro-rerun)前的旧缓存页
// install 预缓存核心路由(SSG 全量 ~30 页 HTML,<1MB,换来全站离线可读;
// 列表由构建产物路由手工维护,新增顶层页时补一行)
// PRECACHE:AUTO-BEGIN 以下为兜底清单——构建期由 scripts/build-perf/gen-sw-precache.mjs
// 按产物自动重写(规则:顶层+en 轨路由,深度≤1 段;/admin 永不预缓存)
const PRECACHE = [
'/', '/archives/', '/gallery/', '/moments/', '/books/', '/opensource/',
'/about/', '/terminal/',
'/en/', '/en/archives/', '/en/gallery/', '/en/moments/', '/en/books/',
'/en/opensource/', '/en/about/', '/en/terminal/',
];
// PRECACHE:AUTO-END
const IMMUTABLE = [
/\/_astro\//, // 构建产物(文件名带哈希)
/\/fonts?\//, // 字体切片(fontsource,内容不变)
/\/photos\//, // 图库压缩版(git 仓管理,变更=换文件)
/\/icons\//, // PWA 图标
/\.(?:woff2|png|jpg|webp|svg|ico|css|js|webmanifest|xml)$/i, // 兜底静态后缀
];
const NEVER_CACHE = [/\/api\//, /\/artalk/, /\/admin/, /\/wp-/];
self.addEventListener('install', (e) => {
e.waitUntil(
(async () => {
const cache = await caches.open(VERSION);
const results = await Promise.allSettled(PRECACHE.map((u) => cache.add(new Request(u, { cache: 'reload' }))));
// 探针:install 失败原因必须可见(allSettled 静默是调试黑洞)
results.forEach((r, i) => {
if (r.status === 'rejected') console.error(`[sw-install] ${PRECACHE[i]} →`, r.reason);
});
console.log(`[sw-install] done ok=${results.filter(r => r.status === 'fulfilled').length}/${PRECACHE.length} keys=${(await cache.keys()).length}`);
self.skipWaiting();
})(),
);
});
self.addEventListener('activate', (e) => {
e.waitUntil(
(async () => {
const keys = await caches.keys();
await Promise.all(keys.filter((k) => k !== VERSION).map((k) => caches.delete(k)));
await self.clients.claim();
})(),
);
});
self.addEventListener('fetch', (e) => {
const req = e.request;
if (req.method !== 'GET') return;
const url = new URL(req.url);
if (url.origin !== self.location.origin) return; // 评论等三方资源不掺和
if (NEVER_CACHE.some((re) => re.test(url.pathname))) return;
if (req.mode === 'navigate') {
// HTML network-first:网络失败时回缓存(离线阅读兜底),成功时后台更新缓存
e.respondWith(
(async () => {
const cache = await caches.open(VERSION);
try {
const fresh = await fetch(req);
if (fresh.ok) cache.put(req, fresh.clone());
return fresh;
} catch {
const hit = await cache.match(req);
return hit || Response.error();
}
})(),
);
return;
}
if (IMMUTABLE.some((re) => re.test(url.pathname))) {
e.respondWith(
(async () => {
const cache = await caches.open(VERSION);
const hit = await cache.match(req);
if (hit) return hit;
const res = await fetch(req);
if (res.ok && res.status === 200) cache.put(req, res.clone());
return res;
})(),
);
}
});