@4399ywkf/core 5.1.5 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +136 -48
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +697 -67
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.d.ts +3 -3
- package/dist/router/index.js +12 -9
- package/dist/router/index.js.map +1 -1
- package/dist/rspack/index.d.ts +2 -2
- package/dist/rspack/index.js +101 -15
- package/dist/rspack/index.js.map +1 -1
- package/dist/runtime/index.d.ts +86 -4
- package/dist/runtime/index.js +578 -19
- package/dist/runtime/index.js.map +1 -1
- package/dist/{schema-CuEnqbqQ.d.ts → schema-QzJlmn39.d.ts} +16 -1
- package/dist/{types-BYqzyGAY.d.ts → types-ZJhd87ue.d.ts} +51 -1
- package/package.json +4 -2
package/dist/runtime/index.js
CHANGED
|
@@ -1,3 +1,367 @@
|
|
|
1
|
+
// src/runtime/chunk-recovery.ts
|
|
2
|
+
var DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS = 3e4;
|
|
3
|
+
var CHUNK_RECOVERY_STORAGE_KEY = "@4399ywkf/core:chunk-recovery-attempt";
|
|
4
|
+
var CHUNK_RECOVERY_CACHE_BUST_PARAM = "__ywkf_chunk_retry";
|
|
5
|
+
var CHUNK_RECOVERY_ACTIVE_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
6
|
+
"@4399ywkf/core/chunk-recovery-active"
|
|
7
|
+
);
|
|
8
|
+
var CHUNK_RECOVERY_REGISTRY_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
9
|
+
"@4399ywkf/core/chunk-recovery-registry"
|
|
10
|
+
);
|
|
11
|
+
var CHUNK_LOAD_ERROR_PATTERNS = [
|
|
12
|
+
/\bChunkLoadError\b/i,
|
|
13
|
+
/\bLoading (?:CSS )?chunk\s+\S+\s+failed\b/i,
|
|
14
|
+
/\bFailed to fetch dynamically imported module\b/i,
|
|
15
|
+
/\bImporting a module script failed\b/i,
|
|
16
|
+
/\berror loading dynamically imported module\b/i,
|
|
17
|
+
/\bUnable to preload CSS for\b/i,
|
|
18
|
+
/\bFailed to load module script\b/i
|
|
19
|
+
];
|
|
20
|
+
function getErrorLikeValue(error, key) {
|
|
21
|
+
if ((typeof error !== "object" || error === null) && typeof error !== "function") {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return error[key];
|
|
26
|
+
} catch {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function matchesChunkLoadError(error, depth) {
|
|
31
|
+
if (depth > 3) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (typeof error === "string") {
|
|
35
|
+
return CHUNK_LOAD_ERROR_PATTERNS.some((pattern) => pattern.test(error));
|
|
36
|
+
}
|
|
37
|
+
const name = getErrorLikeValue(error, "name");
|
|
38
|
+
const message = getErrorLikeValue(error, "message");
|
|
39
|
+
const code = getErrorLikeValue(error, "code");
|
|
40
|
+
const signature = [name, message].filter((value) => typeof value === "string").join(": ");
|
|
41
|
+
if (code === "CSS_CHUNK_LOAD_FAILED" || CHUNK_LOAD_ERROR_PATTERNS.some((pattern) => pattern.test(signature))) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const cause = getErrorLikeValue(error, "cause");
|
|
45
|
+
return cause !== void 0 && cause !== error && matchesChunkLoadError(cause, depth + 1);
|
|
46
|
+
}
|
|
47
|
+
function isChunkLoadError(error) {
|
|
48
|
+
return matchesChunkLoadError(error, 0);
|
|
49
|
+
}
|
|
50
|
+
function hashRecoveryIdentity(value) {
|
|
51
|
+
let hash = 2166136261;
|
|
52
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
53
|
+
hash ^= value.charCodeAt(index);
|
|
54
|
+
hash = Math.imul(hash, 16777619);
|
|
55
|
+
}
|
|
56
|
+
return (hash >>> 0).toString(36);
|
|
57
|
+
}
|
|
58
|
+
function getFrameworkBuildId() {
|
|
59
|
+
const buildId = globalThis.__YWKF_BUILD_ID__;
|
|
60
|
+
return typeof buildId === "string" ? buildId.trim() : "";
|
|
61
|
+
}
|
|
62
|
+
function resolveRecoveryId(error, config) {
|
|
63
|
+
const configuredBuildId = typeof config === "object" && typeof config.buildId === "string" ? config.buildId.trim() : "";
|
|
64
|
+
const buildId = configuredBuildId || getFrameworkBuildId();
|
|
65
|
+
if (buildId) {
|
|
66
|
+
return `build-${hashRecoveryIdentity(buildId)}`;
|
|
67
|
+
}
|
|
68
|
+
const request = getErrorLikeValue(error, "request");
|
|
69
|
+
const name = getErrorLikeValue(error, "name");
|
|
70
|
+
const message = getErrorLikeValue(error, "message");
|
|
71
|
+
const signature = [request, name, message].filter((value) => typeof value === "string").join(": ");
|
|
72
|
+
return `error-${hashRecoveryIdentity(signature || String(error))}`;
|
|
73
|
+
}
|
|
74
|
+
function toError(error, fallbackMessage = "Chunk loading failed") {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
return error;
|
|
77
|
+
}
|
|
78
|
+
if (typeof error === "string") {
|
|
79
|
+
return new Error(error);
|
|
80
|
+
}
|
|
81
|
+
const message = getErrorLikeValue(error, "message");
|
|
82
|
+
const name = getErrorLikeValue(error, "name");
|
|
83
|
+
const normalizedError = new Error(typeof message === "string" ? message : fallbackMessage);
|
|
84
|
+
if (typeof name === "string") {
|
|
85
|
+
normalizedError.name = name;
|
|
86
|
+
}
|
|
87
|
+
return normalizedError;
|
|
88
|
+
}
|
|
89
|
+
function normalizeCooldownMs(value) {
|
|
90
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1e3) {
|
|
91
|
+
return DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS;
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
function resolveChunkRecoveryPolicy(config, context) {
|
|
96
|
+
const options = typeof config === "object" ? config : void 0;
|
|
97
|
+
const configuredEnabled = typeof config === "boolean" ? config : options?.enabled;
|
|
98
|
+
const enabledForEnvironment = configuredEnabled ?? context.isProduction;
|
|
99
|
+
if (!enabledForEnvironment) {
|
|
100
|
+
return {
|
|
101
|
+
enabled: false,
|
|
102
|
+
disabledReason: configuredEnabled === false ? "configuration" : "environment",
|
|
103
|
+
cooldownMs: normalizeCooldownMs(options?.cooldownMs),
|
|
104
|
+
globalHandlers: options?.globalHandlers === true
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (context.isMicroApp && options?.allowInMicroApp !== true) {
|
|
108
|
+
return {
|
|
109
|
+
enabled: false,
|
|
110
|
+
disabledReason: "micro-app",
|
|
111
|
+
cooldownMs: normalizeCooldownMs(options?.cooldownMs),
|
|
112
|
+
globalHandlers: options?.globalHandlers === true
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
enabled: true,
|
|
117
|
+
cooldownMs: normalizeCooldownMs(options?.cooldownMs),
|
|
118
|
+
globalHandlers: options?.globalHandlers === true
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function parseStoredChunkRecoveryState(value) {
|
|
122
|
+
if (value === null) {
|
|
123
|
+
return { attempts: [] };
|
|
124
|
+
}
|
|
125
|
+
const parsed = JSON.parse(value);
|
|
126
|
+
if (!Array.isArray(parsed.attempts)) {
|
|
127
|
+
throw new Error("Invalid chunk recovery state");
|
|
128
|
+
}
|
|
129
|
+
const attempts = parsed.attempts.map((attempt) => {
|
|
130
|
+
if (typeof attempt !== "object" || attempt === null || typeof attempt.recoveryId !== "string" || typeof attempt.attemptedAt !== "number" || !Number.isFinite(attempt.attemptedAt)) {
|
|
131
|
+
throw new Error("Invalid chunk recovery attempt");
|
|
132
|
+
}
|
|
133
|
+
return attempt;
|
|
134
|
+
});
|
|
135
|
+
if (parsed.lastAttemptAt !== void 0 && (typeof parsed.lastAttemptAt !== "number" || !Number.isFinite(parsed.lastAttemptAt))) {
|
|
136
|
+
throw new Error("Invalid chunk recovery timestamp");
|
|
137
|
+
}
|
|
138
|
+
return { attempts, lastAttemptAt: parsed.lastAttemptAt };
|
|
139
|
+
}
|
|
140
|
+
function claimChunkRecoveryAttempt(storage, options) {
|
|
141
|
+
const {
|
|
142
|
+
cooldownMs = DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS,
|
|
143
|
+
now,
|
|
144
|
+
recoveryId,
|
|
145
|
+
storageKey = CHUNK_RECOVERY_STORAGE_KEY
|
|
146
|
+
} = options;
|
|
147
|
+
let state;
|
|
148
|
+
try {
|
|
149
|
+
state = parseStoredChunkRecoveryState(storage.getItem(storageKey));
|
|
150
|
+
} catch {
|
|
151
|
+
return { claimed: false, reason: "storage-unavailable" };
|
|
152
|
+
}
|
|
153
|
+
const existingAttempt = state.attempts.find((attempt) => attempt.recoveryId === recoveryId);
|
|
154
|
+
if (existingAttempt) {
|
|
155
|
+
return {
|
|
156
|
+
claimed: false,
|
|
157
|
+
reason: "already-attempted",
|
|
158
|
+
attemptedAt: existingAttempt.attemptedAt
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const inCooldown = state.lastAttemptAt !== void 0 && (state.lastAttemptAt > now || now - state.lastAttemptAt < cooldownMs);
|
|
162
|
+
if (inCooldown) {
|
|
163
|
+
return { claimed: false, reason: "cooldown", attemptedAt: state.lastAttemptAt };
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
storage.setItem(
|
|
167
|
+
storageKey,
|
|
168
|
+
JSON.stringify({
|
|
169
|
+
attempts: [...state.attempts, { attemptedAt: now, recoveryId }],
|
|
170
|
+
lastAttemptAt: now
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
} catch {
|
|
174
|
+
return { claimed: false, reason: "storage-unavailable" };
|
|
175
|
+
}
|
|
176
|
+
return { claimed: true, attemptedAt: now };
|
|
177
|
+
}
|
|
178
|
+
function isMicroAppRuntime() {
|
|
179
|
+
if (typeof window === "undefined") {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const runtimeWindow = window;
|
|
183
|
+
return Boolean(runtimeWindow.__POWERED_BY_QIANKUN__ || runtimeWindow.__GARFISH__);
|
|
184
|
+
}
|
|
185
|
+
function getSessionStorage() {
|
|
186
|
+
if (typeof window === "undefined") {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
return window.sessionStorage;
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function invokeSafely(callback, label) {
|
|
196
|
+
if (!callback) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
callback();
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`[ywkf] ${label} \u6267\u884C\u5931\u8D25:`, error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function schedulePageReplace(timestamp) {
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
try {
|
|
208
|
+
const reloadUrl = new URL(window.location.href);
|
|
209
|
+
reloadUrl.searchParams.set(CHUNK_RECOVERY_CACHE_BUST_PARAM, String(timestamp));
|
|
210
|
+
window.location.replace(reloadUrl.toString());
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("[ywkf] chunk \u6062\u590D\u5237\u65B0\u5931\u8D25:", error);
|
|
213
|
+
}
|
|
214
|
+
}, 0);
|
|
215
|
+
}
|
|
216
|
+
function clearChunkRecoveryCacheBustParam() {
|
|
217
|
+
if (typeof window === "undefined") {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const currentUrl = new URL(window.location.href);
|
|
222
|
+
if (!currentUrl.searchParams.has(CHUNK_RECOVERY_CACHE_BUST_PARAM)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
currentUrl.searchParams.delete(CHUNK_RECOVERY_CACHE_BUST_PARAM);
|
|
226
|
+
window.history.replaceState(window.history.state, "", currentUrl.toString());
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error("[ywkf] \u6E05\u7406 chunk \u6062\u590D\u53C2\u6570\u5931\u8D25:", error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function handleChunkLoadErrorInternal(reason, options, trustedChunkEvent) {
|
|
232
|
+
if (!trustedChunkEvent && !isChunkLoadError(reason)) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const error = toError(reason);
|
|
236
|
+
const timestamp = Date.now();
|
|
237
|
+
const recoveryId = resolveRecoveryId(reason, options.config);
|
|
238
|
+
const isMicroApp = options.isMicroApp ?? isMicroAppRuntime();
|
|
239
|
+
const policy = resolveChunkRecoveryPolicy(options.config, {
|
|
240
|
+
isProduction: process.env.NODE_ENV === "production",
|
|
241
|
+
isMicroApp
|
|
242
|
+
});
|
|
243
|
+
let outcome;
|
|
244
|
+
if (!policy.enabled) {
|
|
245
|
+
outcome = policy.disabledReason === "micro-app" ? "reload-skipped-micro-app" : "reload-skipped-disabled";
|
|
246
|
+
} else {
|
|
247
|
+
const storage = getSessionStorage();
|
|
248
|
+
const claim = storage ? claimChunkRecoveryAttempt(storage, {
|
|
249
|
+
cooldownMs: policy.cooldownMs,
|
|
250
|
+
now: timestamp,
|
|
251
|
+
recoveryId
|
|
252
|
+
}) : { claimed: false, reason: "storage-unavailable" };
|
|
253
|
+
if (claim.claimed) {
|
|
254
|
+
outcome = "reload-scheduled";
|
|
255
|
+
} else if (claim.reason === "already-attempted") {
|
|
256
|
+
outcome = "reload-skipped-already-attempted";
|
|
257
|
+
} else if (claim.reason === "cooldown") {
|
|
258
|
+
outcome = "reload-skipped-cooldown";
|
|
259
|
+
} else {
|
|
260
|
+
outcome = "reload-skipped-storage-unavailable";
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const event = {
|
|
264
|
+
error,
|
|
265
|
+
source: options.source,
|
|
266
|
+
outcome,
|
|
267
|
+
timestamp,
|
|
268
|
+
cooldownMs: policy.cooldownMs,
|
|
269
|
+
recoveryId
|
|
270
|
+
};
|
|
271
|
+
invokeSafely(() => options.onError?.(error), "onError");
|
|
272
|
+
invokeSafely(() => options.onRecoveryEvent?.(event), "onRecoveryEvent");
|
|
273
|
+
if (outcome === "reload-scheduled") {
|
|
274
|
+
schedulePageReplace(timestamp);
|
|
275
|
+
}
|
|
276
|
+
return event;
|
|
277
|
+
}
|
|
278
|
+
function handleChunkLoadError(reason, options) {
|
|
279
|
+
return handleChunkLoadErrorInternal(reason, options, false);
|
|
280
|
+
}
|
|
281
|
+
function getLatestSubscriber(registry) {
|
|
282
|
+
let latest;
|
|
283
|
+
for (const subscriber of registry.subscribers.values()) {
|
|
284
|
+
latest = subscriber;
|
|
285
|
+
}
|
|
286
|
+
return latest;
|
|
287
|
+
}
|
|
288
|
+
function createGlobalChunkRecoveryRegistry() {
|
|
289
|
+
const registry = {
|
|
290
|
+
subscribers: /* @__PURE__ */ new Map()
|
|
291
|
+
};
|
|
292
|
+
registry.handleUnhandledRejection = (event) => {
|
|
293
|
+
const subscriber = getLatestSubscriber(registry);
|
|
294
|
+
if (!subscriber) return;
|
|
295
|
+
const recoveryEvent = handleChunkLoadError(event.reason, {
|
|
296
|
+
...subscriber,
|
|
297
|
+
source: "unhandled-rejection"
|
|
298
|
+
});
|
|
299
|
+
if (recoveryEvent?.outcome === "reload-scheduled") {
|
|
300
|
+
event.preventDefault();
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
registry.handleVitePreloadError = (event) => {
|
|
304
|
+
const subscriber = getLatestSubscriber(registry);
|
|
305
|
+
if (!subscriber) return;
|
|
306
|
+
const payload = event.payload;
|
|
307
|
+
const recoveryEvent = handleChunkLoadErrorInternal(
|
|
308
|
+
payload,
|
|
309
|
+
{
|
|
310
|
+
...subscriber,
|
|
311
|
+
source: "vite-preload-error"
|
|
312
|
+
},
|
|
313
|
+
true
|
|
314
|
+
);
|
|
315
|
+
if (recoveryEvent?.outcome === "reload-scheduled") {
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
return registry;
|
|
320
|
+
}
|
|
321
|
+
function installGlobalChunkRecoveryHandlers(options) {
|
|
322
|
+
if (typeof window === "undefined") {
|
|
323
|
+
return () => {
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const isMicroApp = options.isMicroApp ?? isMicroAppRuntime();
|
|
327
|
+
const policy = resolveChunkRecoveryPolicy(options.config, {
|
|
328
|
+
isProduction: process.env.NODE_ENV === "production",
|
|
329
|
+
isMicroApp
|
|
330
|
+
});
|
|
331
|
+
if (!policy.enabled || !policy.globalHandlers) {
|
|
332
|
+
return () => {
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const runtimeWindow = window;
|
|
336
|
+
let registry = runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL];
|
|
337
|
+
if (!registry && runtimeWindow[CHUNK_RECOVERY_ACTIVE_SYMBOL] === true) {
|
|
338
|
+
return () => {
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (!registry) {
|
|
342
|
+
registry = createGlobalChunkRecoveryRegistry();
|
|
343
|
+
runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL] = registry;
|
|
344
|
+
runtimeWindow[CHUNK_RECOVERY_ACTIVE_SYMBOL] = true;
|
|
345
|
+
window.addEventListener("unhandledrejection", registry.handleUnhandledRejection);
|
|
346
|
+
window.addEventListener("vite:preloadError", registry.handleVitePreloadError);
|
|
347
|
+
}
|
|
348
|
+
const subscriberId = /* @__PURE__ */ Symbol("chunk-recovery-subscriber");
|
|
349
|
+
registry.subscribers.set(subscriberId, { ...options, isMicroApp });
|
|
350
|
+
let disposed = false;
|
|
351
|
+
return () => {
|
|
352
|
+
if (disposed) return;
|
|
353
|
+
disposed = true;
|
|
354
|
+
registry.subscribers.delete(subscriberId);
|
|
355
|
+
if (registry.subscribers.size > 0) return;
|
|
356
|
+
window.removeEventListener("unhandledrejection", registry.handleUnhandledRejection);
|
|
357
|
+
window.removeEventListener("vite:preloadError", registry.handleVitePreloadError);
|
|
358
|
+
if (runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL] === registry) {
|
|
359
|
+
delete runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL];
|
|
360
|
+
delete runtimeWindow[CHUNK_RECOVERY_ACTIVE_SYMBOL];
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
1
365
|
// src/runtime/bootstrap.tsx
|
|
2
366
|
import { createRoot } from "react-dom/client";
|
|
3
367
|
|
|
@@ -49,8 +413,23 @@ function useIsDev() {
|
|
|
49
413
|
}
|
|
50
414
|
|
|
51
415
|
// src/runtime/error-boundary.tsx
|
|
52
|
-
import {
|
|
416
|
+
import {
|
|
417
|
+
Component,
|
|
418
|
+
createContext as createContext2,
|
|
419
|
+
useContext as useContext2,
|
|
420
|
+
useEffect,
|
|
421
|
+
useMemo,
|
|
422
|
+
useRef
|
|
423
|
+
} from "react";
|
|
424
|
+
import { isRouteErrorResponse, useRouteError } from "react-router";
|
|
53
425
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
426
|
+
var ChunkRecoveryRuntimeContext = createContext2(null);
|
|
427
|
+
function ChunkRecoveryRuntimeProvider({
|
|
428
|
+
children,
|
|
429
|
+
...value
|
|
430
|
+
}) {
|
|
431
|
+
return /* @__PURE__ */ jsx2(ChunkRecoveryRuntimeContext.Provider, { value, children });
|
|
432
|
+
}
|
|
54
433
|
var ErrorBoundary = class extends Component {
|
|
55
434
|
constructor(props) {
|
|
56
435
|
super(props);
|
|
@@ -60,15 +439,31 @@ var ErrorBoundary = class extends Component {
|
|
|
60
439
|
return { hasError: true, error };
|
|
61
440
|
}
|
|
62
441
|
componentDidCatch(error, errorInfo) {
|
|
63
|
-
|
|
64
|
-
|
|
442
|
+
const reportError = () => {
|
|
443
|
+
console.error("[ErrorBoundary] Caught error:", error, errorInfo);
|
|
444
|
+
this.props.onError?.(error, errorInfo);
|
|
445
|
+
};
|
|
446
|
+
if (this.props.chunkRecovery === void 0) {
|
|
447
|
+
reportError();
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const recoveryEvent = handleChunkLoadError(error, {
|
|
451
|
+
source: "error-boundary",
|
|
452
|
+
config: this.props.chunkRecovery,
|
|
453
|
+
isMicroApp: this.props.isMicroApp,
|
|
454
|
+
onError: reportError,
|
|
455
|
+
onRecoveryEvent: this.props.onRecoveryEvent
|
|
456
|
+
});
|
|
457
|
+
if (!recoveryEvent) {
|
|
458
|
+
reportError();
|
|
459
|
+
}
|
|
65
460
|
}
|
|
66
461
|
reset = () => {
|
|
67
462
|
this.setState({ hasError: false, error: null });
|
|
68
463
|
};
|
|
69
464
|
render() {
|
|
70
465
|
const { hasError, error } = this.state;
|
|
71
|
-
const { children, fallback } = this.props;
|
|
466
|
+
const { children, chunkRecovery, fallback, isMicroApp } = this.props;
|
|
72
467
|
if (hasError && error) {
|
|
73
468
|
if (typeof fallback === "function") {
|
|
74
469
|
return fallback(error, this.reset);
|
|
@@ -76,12 +471,86 @@ var ErrorBoundary = class extends Component {
|
|
|
76
471
|
if (fallback) {
|
|
77
472
|
return fallback;
|
|
78
473
|
}
|
|
79
|
-
|
|
474
|
+
const resolvedIsMicroApp = isMicroApp ?? isMicroAppRuntime();
|
|
475
|
+
const chunkRecoveryEnabled = chunkRecovery !== void 0 && resolveChunkRecoveryPolicy(chunkRecovery, {
|
|
476
|
+
isProduction: process.env.NODE_ENV === "production",
|
|
477
|
+
isMicroApp: resolvedIsMicroApp
|
|
478
|
+
}).enabled;
|
|
479
|
+
const canReloadPage = !resolvedIsMicroApp || typeof chunkRecovery === "object" && chunkRecovery.allowInMicroApp === true;
|
|
480
|
+
return /* @__PURE__ */ jsx2(
|
|
481
|
+
DefaultErrorFallback,
|
|
482
|
+
{
|
|
483
|
+
canReloadPage,
|
|
484
|
+
error,
|
|
485
|
+
isChunkError: chunkRecoveryEnabled && isChunkLoadError(error),
|
|
486
|
+
onReset: this.reset
|
|
487
|
+
}
|
|
488
|
+
);
|
|
80
489
|
}
|
|
81
490
|
return children;
|
|
82
491
|
}
|
|
83
492
|
};
|
|
84
|
-
|
|
493
|
+
var UNHANDLED_ROUTE_ERROR = /* @__PURE__ */ Symbol("unhandled-route-error");
|
|
494
|
+
function normalizeRouteError(reason) {
|
|
495
|
+
if (reason instanceof Error) return reason;
|
|
496
|
+
if (typeof reason === "string") return new Error(reason);
|
|
497
|
+
if (isRouteErrorResponse(reason)) {
|
|
498
|
+
const message = [reason.status, reason.statusText].filter(Boolean).join(" ");
|
|
499
|
+
const error = new Error(message || "Route request failed");
|
|
500
|
+
error.name = "RouteErrorResponse";
|
|
501
|
+
return error;
|
|
502
|
+
}
|
|
503
|
+
return new Error("Route rendering failed");
|
|
504
|
+
}
|
|
505
|
+
function RouteErrorBoundary({ fallback }) {
|
|
506
|
+
const reason = useRouteError();
|
|
507
|
+
const error = useMemo(() => normalizeRouteError(reason), [reason]);
|
|
508
|
+
const recoveryRuntime = useContext2(ChunkRecoveryRuntimeContext);
|
|
509
|
+
const handledReasonRef = useRef(UNHANDLED_ROUTE_ERROR);
|
|
510
|
+
useEffect(() => {
|
|
511
|
+
if (Object.is(handledReasonRef.current, reason)) return;
|
|
512
|
+
handledReasonRef.current = reason;
|
|
513
|
+
const reportError = () => {
|
|
514
|
+
console.error("[RouteErrorBoundary] Caught error:", error);
|
|
515
|
+
recoveryRuntime?.onError?.(error);
|
|
516
|
+
};
|
|
517
|
+
if (!recoveryRuntime) {
|
|
518
|
+
reportError();
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const recoveryEvent = handleChunkLoadError(reason, {
|
|
522
|
+
source: "route-error-boundary",
|
|
523
|
+
config: recoveryRuntime.chunkRecovery,
|
|
524
|
+
isMicroApp: recoveryRuntime.isMicroApp,
|
|
525
|
+
onError: reportError,
|
|
526
|
+
onRecoveryEvent: recoveryRuntime.onRecoveryEvent
|
|
527
|
+
});
|
|
528
|
+
if (!recoveryEvent) reportError();
|
|
529
|
+
}, [error, reason, recoveryRuntime]);
|
|
530
|
+
if (typeof fallback === "function") return fallback(error);
|
|
531
|
+
if (fallback) return fallback;
|
|
532
|
+
const chunkRecovery = recoveryRuntime?.chunkRecovery;
|
|
533
|
+
const resolvedIsMicroApp = recoveryRuntime?.isMicroApp ?? isMicroAppRuntime();
|
|
534
|
+
const chunkRecoveryEnabled = chunkRecovery !== void 0 && resolveChunkRecoveryPolicy(chunkRecovery, {
|
|
535
|
+
isProduction: process.env.NODE_ENV === "production",
|
|
536
|
+
isMicroApp: resolvedIsMicroApp
|
|
537
|
+
}).enabled;
|
|
538
|
+
const canReloadPage = !resolvedIsMicroApp || typeof chunkRecovery === "object" && chunkRecovery.allowInMicroApp === true;
|
|
539
|
+
return /* @__PURE__ */ jsx2(
|
|
540
|
+
DefaultErrorFallback,
|
|
541
|
+
{
|
|
542
|
+
canReloadPage,
|
|
543
|
+
error,
|
|
544
|
+
isChunkError: chunkRecoveryEnabled && isChunkLoadError(reason)
|
|
545
|
+
}
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
function DefaultErrorFallback({
|
|
549
|
+
canReloadPage,
|
|
550
|
+
error,
|
|
551
|
+
isChunkError,
|
|
552
|
+
onReset
|
|
553
|
+
}) {
|
|
85
554
|
const isDev = process.env.NODE_ENV === "development";
|
|
86
555
|
return /* @__PURE__ */ jsx2(
|
|
87
556
|
"div",
|
|
@@ -107,7 +576,7 @@ function DefaultErrorFallback({ error, onReset }) {
|
|
|
107
576
|
},
|
|
108
577
|
children: [
|
|
109
578
|
/* @__PURE__ */ jsx2("h1", { style: { color: "#ff4d4f", marginBottom: 16 }, children: "\u9875\u9762\u51FA\u9519\u4E86" }),
|
|
110
|
-
/* @__PURE__ */ jsx2("p", { style: { color: "#666", marginBottom: 16 }, children: "\u62B1\u6B49\uFF0C\u9875\u9762\u9047\u5230\u4E86\u4E00\u4E9B\u95EE\u9898\u3002\u8BF7\u5C1D\u8BD5\u5237\u65B0\u9875\u9762\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u3002" }),
|
|
579
|
+
/* @__PURE__ */ jsx2("p", { style: { color: "#666", marginBottom: 16 }, children: canReloadPage ? "\u62B1\u6B49\uFF0C\u9875\u9762\u9047\u5230\u4E86\u4E00\u4E9B\u95EE\u9898\u3002\u8BF7\u5C1D\u8BD5\u5237\u65B0\u9875\u9762\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u3002" : "\u62B1\u6B49\uFF0C\u9875\u9762\u9047\u5230\u4E86\u4E00\u4E9B\u95EE\u9898\u3002\u8BF7\u5173\u95ED\u5F53\u524D\u9875\u9762\u540E\u91CD\u65B0\u6253\u5F00\uFF0C\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u3002" }),
|
|
111
580
|
isDev && /* @__PURE__ */ jsxs(
|
|
112
581
|
"details",
|
|
113
582
|
{
|
|
@@ -141,8 +610,8 @@ function DefaultErrorFallback({ error, onReset }) {
|
|
|
141
610
|
]
|
|
142
611
|
}
|
|
143
612
|
),
|
|
144
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
145
|
-
/* @__PURE__ */ jsx2(
|
|
613
|
+
(canReloadPage || !isChunkError && onReset) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
614
|
+
!isChunkError && onReset && /* @__PURE__ */ jsx2(
|
|
146
615
|
"button",
|
|
147
616
|
{
|
|
148
617
|
onClick: onReset,
|
|
@@ -157,7 +626,7 @@ function DefaultErrorFallback({ error, onReset }) {
|
|
|
157
626
|
children: "\u91CD\u8BD5"
|
|
158
627
|
}
|
|
159
628
|
),
|
|
160
|
-
/* @__PURE__ */ jsx2(
|
|
629
|
+
canReloadPage && /* @__PURE__ */ jsx2(
|
|
161
630
|
"button",
|
|
162
631
|
{
|
|
163
632
|
onClick: () => window.location.reload(),
|
|
@@ -206,7 +675,7 @@ function composeProviders(providers) {
|
|
|
206
675
|
}, children);
|
|
207
676
|
};
|
|
208
677
|
}
|
|
209
|
-
function RootProvider({ config, children }) {
|
|
678
|
+
function RootProvider({ config, children, isMicroApp }) {
|
|
210
679
|
const {
|
|
211
680
|
appName = "app",
|
|
212
681
|
router,
|
|
@@ -214,7 +683,8 @@ function RootProvider({ config, children }) {
|
|
|
214
683
|
strictMode = true,
|
|
215
684
|
antd = { enabled: true },
|
|
216
685
|
providers = [],
|
|
217
|
-
lifecycle
|
|
686
|
+
lifecycle,
|
|
687
|
+
chunkRecovery
|
|
218
688
|
} = config;
|
|
219
689
|
const appContextValue = {
|
|
220
690
|
appName,
|
|
@@ -245,8 +715,30 @@ function RootProvider({ config, children }) {
|
|
|
245
715
|
...providers.map((p) => ({ ...p, order: p.order ?? 50 }))
|
|
246
716
|
];
|
|
247
717
|
const ComposedProviders = composeProviders(allProviders);
|
|
718
|
+
const rootChunkRecovery = chunkRecovery ?? {
|
|
719
|
+
enabled: process.env.NODE_ENV === "production"
|
|
720
|
+
};
|
|
721
|
+
const resolvedIsMicroApp = isMicroApp ?? isMicroAppRuntime();
|
|
248
722
|
const content = router ? /* @__PURE__ */ jsx3(Suspense, { fallback: /* @__PURE__ */ jsx3(DefaultLoading, {}), children: /* @__PURE__ */ jsx3(RouterProvider, { router }) }) : children;
|
|
249
|
-
const app = /* @__PURE__ */ jsx3(
|
|
723
|
+
const app = /* @__PURE__ */ jsx3(
|
|
724
|
+
ErrorBoundary,
|
|
725
|
+
{
|
|
726
|
+
chunkRecovery: rootChunkRecovery,
|
|
727
|
+
isMicroApp: resolvedIsMicroApp,
|
|
728
|
+
onError: lifecycle?.onError,
|
|
729
|
+
onRecoveryEvent: lifecycle?.onRecoveryEvent,
|
|
730
|
+
children: /* @__PURE__ */ jsx3(
|
|
731
|
+
ChunkRecoveryRuntimeProvider,
|
|
732
|
+
{
|
|
733
|
+
chunkRecovery: rootChunkRecovery,
|
|
734
|
+
isMicroApp: resolvedIsMicroApp,
|
|
735
|
+
onError: lifecycle?.onError,
|
|
736
|
+
onRecoveryEvent: lifecycle?.onRecoveryEvent,
|
|
737
|
+
children: /* @__PURE__ */ jsx3(ComposedProviders, { children: content })
|
|
738
|
+
}
|
|
739
|
+
)
|
|
740
|
+
}
|
|
741
|
+
);
|
|
250
742
|
if (strictMode) {
|
|
251
743
|
return /* @__PURE__ */ jsx3(StrictMode, { children: app });
|
|
252
744
|
}
|
|
@@ -263,6 +755,7 @@ function createProvider(component, props, order) {
|
|
|
263
755
|
// src/runtime/bootstrap.tsx
|
|
264
756
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
265
757
|
var root = null;
|
|
758
|
+
var disposeChunkRecoveryHandlers = null;
|
|
266
759
|
async function resolveAppConfig(config) {
|
|
267
760
|
if (config.router || !config.conventionalRoutes) {
|
|
268
761
|
return config;
|
|
@@ -285,6 +778,10 @@ async function resolveAppConfig(config) {
|
|
|
285
778
|
}
|
|
286
779
|
async function bootstrap(config) {
|
|
287
780
|
const { rootId = "root", lifecycle } = config;
|
|
781
|
+
const isMicroApp = isMicroAppEnv();
|
|
782
|
+
if (!isMicroApp) {
|
|
783
|
+
clearChunkRecoveryCacheBustParam();
|
|
784
|
+
}
|
|
288
785
|
const resolvedConfig = await resolveAppConfig(config);
|
|
289
786
|
if (lifecycle?.onBeforeMount) {
|
|
290
787
|
await lifecycle.onBeforeMount();
|
|
@@ -293,8 +790,22 @@ async function bootstrap(config) {
|
|
|
293
790
|
if (!rootElement) {
|
|
294
791
|
throw new Error(`\u627E\u4E0D\u5230\u6839\u5143\u7D20 #${rootId}`);
|
|
295
792
|
}
|
|
296
|
-
|
|
297
|
-
|
|
793
|
+
disposeChunkRecoveryHandlers?.();
|
|
794
|
+
const nextDisposeChunkRecoveryHandlers = installGlobalChunkRecoveryHandlers({
|
|
795
|
+
config: resolvedConfig.chunkRecovery,
|
|
796
|
+
isMicroApp,
|
|
797
|
+
onError: (error) => resolvedConfig.lifecycle?.onError?.(error),
|
|
798
|
+
onRecoveryEvent: resolvedConfig.lifecycle?.onRecoveryEvent
|
|
799
|
+
});
|
|
800
|
+
try {
|
|
801
|
+
root = createRoot(rootElement);
|
|
802
|
+
root.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig, isMicroApp }));
|
|
803
|
+
disposeChunkRecoveryHandlers = nextDisposeChunkRecoveryHandlers;
|
|
804
|
+
} catch (error) {
|
|
805
|
+
nextDisposeChunkRecoveryHandlers();
|
|
806
|
+
root = null;
|
|
807
|
+
throw error;
|
|
808
|
+
}
|
|
298
809
|
if (lifecycle?.onMounted) {
|
|
299
810
|
setTimeout(() => {
|
|
300
811
|
lifecycle.onMounted?.();
|
|
@@ -307,6 +818,8 @@ async function bootstrap(config) {
|
|
|
307
818
|
}
|
|
308
819
|
}
|
|
309
820
|
function unmount() {
|
|
821
|
+
disposeChunkRecoveryHandlers?.();
|
|
822
|
+
disposeChunkRecoveryHandlers = null;
|
|
310
823
|
if (root) {
|
|
311
824
|
root.unmount();
|
|
312
825
|
root = null;
|
|
@@ -314,6 +827,7 @@ function unmount() {
|
|
|
314
827
|
}
|
|
315
828
|
function createMicroApp(getConfig) {
|
|
316
829
|
let microRoot = null;
|
|
830
|
+
let disposeMicroChunkRecoveryHandlers = null;
|
|
317
831
|
return {
|
|
318
832
|
/**
|
|
319
833
|
* 微前端 bootstrap 生命周期
|
|
@@ -337,8 +851,22 @@ function createMicroApp(getConfig) {
|
|
|
337
851
|
if (resolvedConfig.lifecycle?.onBeforeMount) {
|
|
338
852
|
await resolvedConfig.lifecycle.onBeforeMount();
|
|
339
853
|
}
|
|
340
|
-
|
|
341
|
-
|
|
854
|
+
disposeMicroChunkRecoveryHandlers?.();
|
|
855
|
+
const nextDisposeMicroChunkRecoveryHandlers = installGlobalChunkRecoveryHandlers({
|
|
856
|
+
config: resolvedConfig.chunkRecovery,
|
|
857
|
+
isMicroApp: true,
|
|
858
|
+
onError: (error) => resolvedConfig.lifecycle?.onError?.(error),
|
|
859
|
+
onRecoveryEvent: resolvedConfig.lifecycle?.onRecoveryEvent
|
|
860
|
+
});
|
|
861
|
+
try {
|
|
862
|
+
microRoot = createRoot(rootElement);
|
|
863
|
+
microRoot.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig, isMicroApp: true }));
|
|
864
|
+
disposeMicroChunkRecoveryHandlers = nextDisposeMicroChunkRecoveryHandlers;
|
|
865
|
+
} catch (error) {
|
|
866
|
+
nextDisposeMicroChunkRecoveryHandlers();
|
|
867
|
+
microRoot = null;
|
|
868
|
+
throw error;
|
|
869
|
+
}
|
|
342
870
|
if (resolvedConfig.lifecycle?.onMounted) {
|
|
343
871
|
setTimeout(() => {
|
|
344
872
|
resolvedConfig.lifecycle?.onMounted?.();
|
|
@@ -358,6 +886,8 @@ function createMicroApp(getConfig) {
|
|
|
358
886
|
microRoot.unmount();
|
|
359
887
|
microRoot = null;
|
|
360
888
|
}
|
|
889
|
+
disposeMicroChunkRecoveryHandlers?.();
|
|
890
|
+
disposeMicroChunkRecoveryHandlers = null;
|
|
361
891
|
},
|
|
362
892
|
/**
|
|
363
893
|
* 微前端 update 生命周期(可选)
|
|
@@ -369,6 +899,7 @@ function createMicroApp(getConfig) {
|
|
|
369
899
|
}
|
|
370
900
|
function createGarfishProvider(getConfig) {
|
|
371
901
|
let garfishRoot = null;
|
|
902
|
+
let disposeGarfishChunkRecoveryHandlers = null;
|
|
372
903
|
return function provider(providerContext) {
|
|
373
904
|
const { dom, basename } = providerContext;
|
|
374
905
|
return {
|
|
@@ -382,8 +913,22 @@ function createGarfishProvider(getConfig) {
|
|
|
382
913
|
rootElement.id = rootId;
|
|
383
914
|
dom.appendChild(rootElement);
|
|
384
915
|
}
|
|
385
|
-
|
|
386
|
-
|
|
916
|
+
disposeGarfishChunkRecoveryHandlers?.();
|
|
917
|
+
const nextDisposeGarfishChunkRecoveryHandlers = installGlobalChunkRecoveryHandlers({
|
|
918
|
+
config: resolvedConfig.chunkRecovery,
|
|
919
|
+
isMicroApp: true,
|
|
920
|
+
onError: (error) => resolvedConfig.lifecycle?.onError?.(error),
|
|
921
|
+
onRecoveryEvent: resolvedConfig.lifecycle?.onRecoveryEvent
|
|
922
|
+
});
|
|
923
|
+
try {
|
|
924
|
+
garfishRoot = createRoot(rootElement);
|
|
925
|
+
garfishRoot.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig, isMicroApp: true }));
|
|
926
|
+
disposeGarfishChunkRecoveryHandlers = nextDisposeGarfishChunkRecoveryHandlers;
|
|
927
|
+
} catch (error) {
|
|
928
|
+
nextDisposeGarfishChunkRecoveryHandlers();
|
|
929
|
+
garfishRoot = null;
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
387
932
|
if (resolvedConfig.lifecycle?.onMounted) {
|
|
388
933
|
setTimeout(() => resolvedConfig.lifecycle?.onMounted?.(), 0);
|
|
389
934
|
}
|
|
@@ -393,6 +938,8 @@ function createGarfishProvider(getConfig) {
|
|
|
393
938
|
garfishRoot.unmount();
|
|
394
939
|
garfishRoot = null;
|
|
395
940
|
}
|
|
941
|
+
disposeGarfishChunkRecoveryHandlers?.();
|
|
942
|
+
disposeGarfishChunkRecoveryHandlers = null;
|
|
396
943
|
}
|
|
397
944
|
};
|
|
398
945
|
};
|
|
@@ -415,14 +962,26 @@ function getMicroAppPublicPath() {
|
|
|
415
962
|
export {
|
|
416
963
|
AppContext,
|
|
417
964
|
AppContextProvider,
|
|
965
|
+
CHUNK_RECOVERY_ACTIVE_SYMBOL,
|
|
966
|
+
CHUNK_RECOVERY_CACHE_BUST_PARAM,
|
|
967
|
+
CHUNK_RECOVERY_STORAGE_KEY,
|
|
968
|
+
DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS,
|
|
418
969
|
ErrorBoundary,
|
|
419
970
|
RootProvider,
|
|
971
|
+
RouteErrorBoundary,
|
|
420
972
|
bootstrap,
|
|
973
|
+
claimChunkRecoveryAttempt,
|
|
974
|
+
clearChunkRecoveryCacheBustParam,
|
|
421
975
|
createGarfishProvider,
|
|
422
976
|
createMicroApp,
|
|
423
977
|
createProvider,
|
|
424
978
|
getMicroAppPublicPath,
|
|
979
|
+
handleChunkLoadError,
|
|
980
|
+
installGlobalChunkRecoveryHandlers,
|
|
981
|
+
isChunkLoadError,
|
|
425
982
|
isMicroAppEnv,
|
|
983
|
+
isMicroAppRuntime,
|
|
984
|
+
resolveChunkRecoveryPolicy,
|
|
426
985
|
unmount,
|
|
427
986
|
useApp,
|
|
428
987
|
useAppName,
|