调整 iam
This commit is contained in:
+137
-7
@@ -1,38 +1,168 @@
|
||||
/**
|
||||
* 懒加载 chunk / CSS preload 失败后的整页恢复。
|
||||
* 常见于:部署后旧 hash 失效、静态服务空闲断连、代理 CONNECTION_RESET。
|
||||
*
|
||||
* 失败后必须整页刷新:旧入口里的 hashed 资源地址不会自行更新,
|
||||
* 仅捕获路由错误而不刷新时,未打开过的页面会一直无法进入。
|
||||
*/
|
||||
|
||||
const RELOAD_FLAG = 'app:chunk-reload-ts';
|
||||
const RELOAD_COOLDOWN_MS = 10000;
|
||||
const RELOAD_QUERY = '_chunkreload';
|
||||
const RELOAD_COOLDOWN_MS = 15000;
|
||||
|
||||
const CHUNK_ERROR_RE =
|
||||
/Failed to fetch dynamically imported module|Importing a module script failed|Unable to preload CSS|error loading dynamically imported module|Loading CSS chunk|Loading chunk .+ failed|ChunkLoadError/i;
|
||||
/Failed to fetch dynamically imported module|Importing a module script failed|Unable to preload CSS|error loading dynamically imported module|Loading CSS chunk|Loading chunk .+ failed|ChunkLoadError|Unable to preload|error loading module|Load failed/i;
|
||||
|
||||
/** 最近一次路由跳转目标,供 onError 时整页落到正确地址 */
|
||||
let pendingFullPath = '';
|
||||
let installed = false;
|
||||
let reloading = false;
|
||||
|
||||
export function setPendingRoutePath(fullPath = '') {
|
||||
pendingFullPath = fullPath || '';
|
||||
}
|
||||
|
||||
function collectErrorText(error, depth = 0) {
|
||||
if (!error || depth > 3) return '';
|
||||
if (typeof error === 'string') return error;
|
||||
const parts = [
|
||||
error.message,
|
||||
error.msg,
|
||||
error.name,
|
||||
error.stack,
|
||||
error.error && collectErrorText(error.error, depth + 1),
|
||||
error.reason && collectErrorText(error.reason, depth + 1),
|
||||
error.cause && collectErrorText(error.cause, depth + 1),
|
||||
error.payload && collectErrorText(error.payload, depth + 1),
|
||||
];
|
||||
const target = error.target;
|
||||
if (target && (target.src || target.href)) {
|
||||
parts.push(target.src || target.href);
|
||||
}
|
||||
try {
|
||||
parts.push(String(error));
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
return parts.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
export function isChunkLoadError(error) {
|
||||
if (!error) return false;
|
||||
const message = error.message || String(error);
|
||||
return CHUNK_ERROR_RE.test(message);
|
||||
const text = collectErrorText(error);
|
||||
if (!text) return false;
|
||||
if (CHUNK_ERROR_RE.test(text)) return true;
|
||||
return /Failed to fetch/i.test(text) && /\.m?js(\?|$|:)/i.test(text);
|
||||
}
|
||||
|
||||
export function hasReloadQuery(query = {}) {
|
||||
return !!(query && query[RELOAD_QUERY] !== undefined);
|
||||
}
|
||||
|
||||
export function consumeReloadQuery(query = {}) {
|
||||
if (!hasReloadQuery(query)) return query;
|
||||
const next = { ...query };
|
||||
delete next[RELOAD_QUERY];
|
||||
return next;
|
||||
}
|
||||
|
||||
function buildReloadUrl(targetPath) {
|
||||
const url = new URL(targetPath, window.location.origin);
|
||||
url.searchParams.set(RELOAD_QUERY, String(Date.now()));
|
||||
return url.pathname + url.search + url.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} 是否已触发刷新(冷却期内返回 false,避免死循环)
|
||||
*/
|
||||
export function reloadForChunkError(targetPath) {
|
||||
if (reloading) return false;
|
||||
const now = Date.now();
|
||||
const last = Number(sessionStorage.getItem(RELOAD_FLAG) || 0);
|
||||
if (now - last < RELOAD_COOLDOWN_MS) {
|
||||
return false;
|
||||
}
|
||||
reloading = true;
|
||||
sessionStorage.setItem(RELOAD_FLAG, String(now));
|
||||
const path = targetPath || pendingFullPath || window.location.pathname + window.location.search + window.location.hash;
|
||||
window.location.assign(path);
|
||||
const path =
|
||||
targetPath ||
|
||||
pendingFullPath ||
|
||||
`${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
const dest = buildReloadUrl(path);
|
||||
// cache: 'reload' 会回写 HTTP 缓存,降低 index.html 仍指向旧 hash 资源的概率
|
||||
fetch(`${window.location.pathname}?${RELOAD_QUERY}=${now}`, {
|
||||
cache: 'reload',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
Pragma: 'no-cache',
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
window.location.replace(dest);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
export function wrapViewLoader(loader) {
|
||||
if (typeof loader !== 'function') return loader;
|
||||
return () =>
|
||||
Promise.resolve()
|
||||
.then(() => loader())
|
||||
.catch(error => {
|
||||
if (isChunkLoadError(error)) {
|
||||
reloadForChunkError();
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
export function installChunkReload() {
|
||||
if (installed || typeof window === 'undefined') return;
|
||||
installed = true;
|
||||
|
||||
window.addEventListener('vite:preloadError', event => {
|
||||
if (reloadForChunkError()) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
if (!isChunkLoadError(event.reason)) return;
|
||||
if (reloadForChunkError()) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener(
|
||||
'error',
|
||||
event => {
|
||||
const el = event.target;
|
||||
const isAssetNode =
|
||||
el &&
|
||||
el !== window &&
|
||||
(el.tagName === 'SCRIPT' ||
|
||||
(el.tagName === 'LINK' && /modulepreload|stylesheet/i.test(el.rel || '')));
|
||||
if (isAssetNode || isChunkLoadError(event.error || event.message)) {
|
||||
if (reloadForChunkError()) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') checkDeployedAssets();
|
||||
});
|
||||
}
|
||||
|
||||
function checkDeployedAssets() {
|
||||
const el = document.querySelector('script[type="module"][src*="/assets/"]');
|
||||
if (!el || !el.src) return;
|
||||
fetch(el.src, { method: 'HEAD', cache: 'no-store', credentials: 'same-origin' })
|
||||
.then(res => {
|
||||
if (res.status === 404) reloadForChunkError();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user