Make PWA update notice detect build changes

This commit is contained in:
tianma committed 2026-07-01 00:09:16 +08:00
1 parent ee5d0b9ca9
commit 858034cf8a
5 files changed
+64 -7

No files matched your search

+25 -6
View File
@@ -3,6 +3,7 @@ import { CheckCircle2, RefreshCw, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import {
consumePwaBuildUpdate,
consumePwaUpdateReloadPending,
PWA_UPDATE_NOTICE_EVENT,
type PwaUpdateNoticeEvent,
@@ -10,6 +11,7 @@ import {
} from "@/lib/pwa-update-notice";
type Notice = {
buildLabel?: string;
id: number;
kind: PwaUpdateNoticeKind;
};
@@ -63,6 +65,14 @@ const noticeCopy: Record<PwaUpdateNoticeKind, { title: string; description: stri
},
};
const getNoticeDescription = (notice: Notice, fallbackDescription: string) => {
if (notice.kind === "updated" && notice.buildLabel) {
return `当前 PWA 已是最新版本(${notice.buildLabel})。`;
}
return fallbackDescription;
};
export const PwaUpdateNotice = () => {
const [displayMode, setDisplayMode] = useState(() => getDisplayMode());
const [notice, setNotice] = useState<Notice | null>(null);
@@ -73,18 +83,27 @@ export const PwaUpdateNotice = () => {
}, []);
useEffect(() => {
if (consumePwaUpdateReloadPending()) {
setNotice({ id: Date.now(), kind: "updated" });
if (!displayMode.visible) {
return;
}
const buildLabel = __EDGEEVER_BUILD_LABEL__;
if (
consumePwaUpdateReloadPending() ||
consumePwaBuildUpdate(__EDGEEVER_BUILD_ID__, { notifyWhenMissingBaseline: true })
) {
setNotice({ buildLabel, id: Date.now(), kind: "updated" });
}
const handleNotice = (event: Event) => {
const { kind } = (event as PwaUpdateNoticeEvent).detail;
setNotice({ id: Date.now(), kind });
const { buildLabel: eventBuildLabel, kind } = (event as PwaUpdateNoticeEvent).detail;
setNotice({ buildLabel: eventBuildLabel ?? buildLabel, id: Date.now(), kind });
};
window.addEventListener(PWA_UPDATE_NOTICE_EVENT, handleNotice);
return () => window.removeEventListener(PWA_UPDATE_NOTICE_EVENT, handleNotice);
}, []);
}, [displayMode.visible]);
useEffect(() => {
if (!notice) {
@@ -126,7 +145,7 @@ export const PwaUpdateNotice = () => {
</div>
<div className="min-w-0 flex-1">
<div className="text-sm font-semibold leading-5">{copy.title}</div>
<div className="mt-0.5 text-xs leading-5 text-slate-500">{copy.description}</div>
<div className="mt-0.5 text-xs leading-5 text-slate-500">{getNoticeDescription(notice, copy.description)}</div>
{isReloadRequired ? (
<Button className="mt-2" size="sm" variant="solid" onClick={() => window.location.reload()}>
立即刷新
+20
View File
@@ -1,10 +1,12 @@
export const PWA_UPDATE_NOTICE_EVENT = "edgeever:pwa-update-notice";
const PWA_BUILD_ID_KEY = "edgeever:pwa-build-id";
const PWA_UPDATE_RELOADED_AT_KEY = "edgeever:pwa-update-reloaded-at";
export type PwaUpdateNoticeKind = "checking" | "updated" | "reload-required";
export type PwaUpdateNoticeDetail = {
buildLabel?: string;
kind: PwaUpdateNoticeKind;
};
@@ -31,3 +33,21 @@ export const consumePwaUpdateReloadPending = () => {
return false;
}
};
export const consumePwaBuildUpdate = (
currentBuildId: string,
{ notifyWhenMissingBaseline = false }: { notifyWhenMissingBaseline?: boolean } = {}
) => {
try {
const previousBuildId = window.localStorage.getItem(PWA_BUILD_ID_KEY);
window.localStorage.setItem(PWA_BUILD_ID_KEY, currentBuildId);
if (!previousBuildId) {
return notifyWhenMissingBaseline;
}
return previousBuildId !== currentBuildId;
} catch {
return false;
}
};
+1 -1
View File
@@ -14,7 +14,7 @@ let updateServiceWorker: ReturnType<typeof registerSW>;
updateServiceWorker = registerSW({
immediate: true,
onNeedRefresh() {
emitPwaUpdateNotice({ kind: "checking" });
emitPwaUpdateNotice({ buildLabel: __EDGEEVER_BUILD_LABEL__, kind: "checking" });
void updateServiceWorker(true);
},
onNeedReload() {
+3
View File
@@ -1,2 +1,5 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />
declare const __EDGEEVER_BUILD_ID__: string;
declare const __EDGEEVER_BUILD_LABEL__: string;
+15
View File
@@ -1,10 +1,25 @@
import react from "@vitejs/plugin-react";
import { execSync } from "node:child_process";
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
const readGitCommit = () => {
try {
return execSync("git rev-parse --short=12 HEAD", { encoding: "utf8" }).trim();
} catch {
return null;
}
};
const buildId = process.env.CF_PAGES_COMMIT_SHA?.slice(0, 12) ?? process.env.GITHUB_SHA?.slice(0, 12) ?? readGitCommit() ?? "local";
export default defineConfig({
root: "apps/web",
define: {
__EDGEEVER_BUILD_ID__: JSON.stringify(buildId),
__EDGEEVER_BUILD_LABEL__: JSON.stringify(buildId === "local" ? "local" : buildId.slice(0, 7)),
},
plugins: [
react(),
VitePWA({