用 vite-plugin-pwa 生成 manifest 与 Service Worker,不自己写缓存逻辑。 - 装到桌面:manifest 名字/主题色/三个图标(any + maskable + apple-touch-icon), 图标和页头、安卓桌面是同一枚;首页底部给安装引导,能装就弹「安装」, iOS 给「分享 → 添加到主屏幕」,关掉记在 localStorage - 发版提示:registerType=prompt,收到新版本只在底部提示,点「刷新」才换。 故意不自动刷新——Vditor、图表是懒加载分片,后台换 chunk 会让面板 404 - 缓存边界:只预缓存页面骨架(约 0.8MB),/api/ 一律 NetworkOnly, 离线宁可报错也不端旧数据;/vditor/ 走 CacheFirst,不拖慢首次安装 - 顺手修:UpdatePrompt 居中改用 flex 包一层,animate-slide-up 的 keyframes 自带 transform,会和 -translate-x-1/2 抢属性,手机窄屏会把卡片推出屏幕 验证:web/e2e/pwa.mjs 19 项全绿(manifest/SW 激活/预缓存/安装引导/iOS 文案/ 发版提示/断网仍能打开);tsc 通过、vite build 通过。 已知限制:Service Worker 只在 https 或 localhost 注册,生产入口还是 http, 所以内网 IP 上装不了——需要域名 + 证书反代或隧道,已写进 DEPLOY.md 第 6 节。
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Service Worker 的注册与升级。
|
||||
*
|
||||
* 只做三件事:生产环境注册、把"有新版本"和"可以离线用了"两个事件抛给界面、
|
||||
* 以及提供统一的一个"现在就换新版本"入口。注册本身交给 vite-plugin-pwa 生成的
|
||||
* `virtual:pwa-register`,这里不重复实现缓存逻辑。
|
||||
*/
|
||||
|
||||
type ApplyUpdate = (reloadPage?: boolean) => Promise<void>;
|
||||
|
||||
let applyUpdate: ApplyUpdate | null = null;
|
||||
|
||||
/** 界面用的事件名:有新版可换、首次离线可用。 */
|
||||
export const PWA_EVENT = {
|
||||
updateReady: "dadao:pwa-update-ready",
|
||||
offlineReady: "dadao:pwa-offline-ready",
|
||||
} as const;
|
||||
|
||||
export function startServiceWorker(): void {
|
||||
// 开发环境没有 SW(devOptions.enabled = false),动态引入免得无谓地打包
|
||||
if (!import.meta.env.PROD || !("serviceWorker" in navigator)) return;
|
||||
void import("virtual:pwa-register")
|
||||
.then(({ registerSW }) => {
|
||||
applyUpdate = registerSW({
|
||||
immediate: true,
|
||||
onNeedRefresh() {
|
||||
window.dispatchEvent(new Event(PWA_EVENT.updateReady));
|
||||
},
|
||||
onOfflineReady() {
|
||||
window.dispatchEvent(new Event(PWA_EVENT.offlineReady));
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 注册失败不影响正常使用:下次打开页面还会再试一次
|
||||
});
|
||||
}
|
||||
|
||||
/** 用户点了"刷新":让新 SW 立刻接管并重载页面。 */
|
||||
export async function applyServiceWorkerUpdate(): Promise<void> {
|
||||
if (applyUpdate) {
|
||||
await applyUpdate(true);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
/** 这个页面是不是从主屏图标启动的(standalone)。 */
|
||||
export function isStandalone(): boolean {
|
||||
return (
|
||||
window.matchMedia?.("(display-mode: standalone)").matches === true ||
|
||||
(window.navigator as { standalone?: boolean }).standalone === true
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user