fix(deploy): 前端资源 - 保留旧版本分片并恢复加载
This commit is contained in:
43
web/src/lib/chunk-recovery.ts
Normal file
43
web/src/lib/chunk-recovery.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
const RECOVERY_QUERY = "__chunk_recovery";
|
||||
const RECOVERY_KEY = "canvas:chunk-recovery";
|
||||
let recovering = false;
|
||||
|
||||
export function installChunkRecovery() {
|
||||
window.addEventListener("vite:preloadError", handlePreloadError);
|
||||
}
|
||||
|
||||
export function markChunkRecoverySucceeded() {
|
||||
// An entry module loading says nothing about lazy routes. Keep the guard for
|
||||
// the tab's lifetime; the URL also protects browsers with disabled storage.
|
||||
}
|
||||
|
||||
/** Explicit user retry is allowed even after the automatic recovery was used. */
|
||||
export function reloadAfterChunkFailure() {
|
||||
navigateToFreshEntry();
|
||||
}
|
||||
|
||||
export function tryRecoverChunkFailure() {
|
||||
if (recovering) return true;
|
||||
const url = new URL(window.location.href);
|
||||
if (url.searchParams.has(RECOVERY_QUERY)) return false;
|
||||
try {
|
||||
if (sessionStorage.getItem(RECOVERY_KEY) === "1") return false;
|
||||
sessionStorage.setItem(RECOVERY_KEY, "1");
|
||||
} catch {
|
||||
// The query marker survives the reload when session storage is denied.
|
||||
}
|
||||
recovering = true;
|
||||
navigateToFreshEntry();
|
||||
return true;
|
||||
}
|
||||
|
||||
function navigateToFreshEntry() {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set(RECOVERY_QUERY, String(Date.now()));
|
||||
window.location.replace(url.toString());
|
||||
}
|
||||
|
||||
function handlePreloadError(event: Event) {
|
||||
// On the second failure let Vite throw to the route error boundary.
|
||||
if (tryRecoverChunkFailure()) event.preventDefault();
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import "@fontsource-variable/inter";
|
||||
import "@fontsource-variable/jetbrains-mono";
|
||||
import { installChunkRecovery } from "@/lib/chunk-recovery";
|
||||
import { bootstrapAppearance } from "@/services/appearance-bootstrap";
|
||||
import { isIsolatedDirectorRepro } from "@/lib/dev-repro";
|
||||
|
||||
installChunkRecovery();
|
||||
|
||||
// The public film entry checks its availability independently of workspace bootstrap.
|
||||
if (/^\/welcome\/?$/.test(window.location.pathname)) void import("./welcome-application");
|
||||
else {
|
||||
// The backend-free DEV lab must not make requests before AppProviders isolates it.
|
||||
const appearanceReady = isIsolatedDirectorRepro(import.meta.env.DEV, window.location.pathname) ? Promise.resolve() : bootstrapAppearance();
|
||||
void appearanceReady.finally(() => import("./application"));
|
||||
void appearanceReady.then(() => import("./application"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Home, RefreshCw } from "lucide-react";
|
||||
import { useNavigate, useRouteError } from "react-router";
|
||||
|
||||
import { WorkspaceSignalIcon } from "@/components/ui/aceternity/workspace-signal-icon";
|
||||
import { reloadAfterChunkFailure } from "@/lib/chunk-recovery";
|
||||
|
||||
export default function RouteErrorPage() {
|
||||
const error = useRouteError();
|
||||
@@ -17,7 +18,7 @@ export default function RouteErrorPage() {
|
||||
<h1 className="mt-3 text-2xl font-semibold">当前页面没有正常加载</h1>
|
||||
<p className="mt-3 break-words text-sm leading-6 text-muted-foreground">{message}</p>
|
||||
<div className="mt-6 flex justify-center gap-3">
|
||||
<Button icon={<RefreshCw className="size-4" />} onClick={() => window.location.reload()}>重新加载</Button>
|
||||
<Button icon={<RefreshCw className="size-4" />} onClick={reloadAfterChunkFailure}>重新加载</Button>
|
||||
<Button type="primary" icon={<Home className="size-4" />} onClick={() => navigate("/")}>返回主页</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user