@4399ywkf/core 5.1.5 → 5.1.6

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/index.js CHANGED
@@ -2743,12 +2743,13 @@ function LazyRoute({
2743
2743
 
2744
2744
  import React, { lazy, Suspense } from "react";
2745
2745
  import { createBrowserRouter, type RouteObject } from "react-router";
2746
+ import { RouteErrorBoundary } from "@4399ywkf/core/runtime";
2747
+ ${errorImports.length > 0 ? `
2748
+ // \u540C\u6B65\u9519\u8BEF\u8FB9\u754C\u7EC4\u4EF6
2749
+ ${errorImports.join("\n")}` : ""}
2746
2750
 
2747
2751
  // \u61D2\u52A0\u8F7D\u9875\u9762\u7EC4\u4EF6
2748
2752
  ${lazyImports.join("\n")}
2749
- ${errorImports.length > 0 ? `
2750
- // \u9519\u8BEF\u8FB9\u754C\u7EC4\u4EF6
2751
- ${errorImports.join("\n")}` : ""}
2752
2753
  ${loadingImports.length > 0 ? `
2753
2754
  // \u52A0\u8F7D\u72B6\u6001\u7EC4\u4EF6
2754
2755
  ${loadingImports.join("\n")}` : ""}
@@ -2783,7 +2784,7 @@ export default routes;
2783
2784
  }
2784
2785
  if (route.errorFile) {
2785
2786
  errorImports.push(
2786
- `const ${name}Error = lazy(() => import("@/pages/${toImportPath(route.errorFile)}"));`
2787
+ `import ${name}Error from "@/pages/${toImportPath(route.errorFile)}";`
2787
2788
  );
2788
2789
  }
2789
2790
  if (route.loadingFile) {
@@ -2798,13 +2799,13 @@ export default routes;
2798
2799
  }
2799
2800
  }
2800
2801
  }
2801
- emitRouteArray(routes, parentPath) {
2802
- const items = routes.map((r) => this.emitRouteObject(r, parentPath));
2802
+ emitRouteArray(routes, parentPath, depth = 0) {
2803
+ const items = routes.map((r) => this.emitRouteObject(r, parentPath, depth));
2803
2804
  return `[
2804
2805
  ${items.join(",\n ")}
2805
2806
  ]`;
2806
2807
  }
2807
- emitRouteObject(route, parentPath) {
2808
+ emitRouteObject(route, parentPath, depth) {
2808
2809
  const parts = [];
2809
2810
  const name = route.name;
2810
2811
  if (route.index) {
@@ -2825,11 +2826,13 @@ export default routes;
2825
2826
  parts.push(`element: <LazyRoute Component={${name}Page} />`);
2826
2827
  }
2827
2828
  if (route.errorFile) {
2828
- parts.push(`errorElement: <LazyRoute Component={${name}Error} />`);
2829
+ parts.push(`errorElement: <RouteErrorBoundary fallback={<${name}Error />} />`);
2830
+ } else if (depth === 0) {
2831
+ parts.push(`errorElement: <RouteErrorBoundary />`);
2829
2832
  }
2830
2833
  if (route.children && route.children.length > 0) {
2831
2834
  const childParent = route.pathless ? parentPath : route.path;
2832
- parts.push(`children: ${this.emitRouteArray(route.children, childParent)}`);
2835
+ parts.push(`children: ${this.emitRouteArray(route.children, childParent, depth + 1)}`);
2833
2836
  }
2834
2837
  return `{
2835
2838
  ${parts.join(",\n ")}
@@ -3441,10 +3444,20 @@ var YwkfGeneratorPlugin = class {
3441
3444
  var require2 = createRequire(import.meta.url);
3442
3445
  var __dirname = dirname(fileURLToPath(import.meta.url));
3443
3446
  var coreNodeModules = join12(__dirname, "../../node_modules");
3447
+ function resolveYwkfBuildId(appName) {
3448
+ const releaseId = [
3449
+ process.env.YWKF_BUILD_ID,
3450
+ process.env.CI_COMMIT_SHA,
3451
+ process.env.GITHUB_SHA,
3452
+ process.env.GIT_COMMIT
3453
+ ].find((value) => value?.trim())?.trim();
3454
+ return `${appName}:${releaseId || Date.now().toString(36)}`;
3455
+ }
3444
3456
  function createBaseConfig(config, cwd, options = {}) {
3445
3457
  const isDev = options.isDev ?? process.env.NODE_ENV !== "production";
3446
3458
  const { resolveApp } = createPathResolver(cwd);
3447
3459
  const { appName, appCName, output, html, alias: userAlias, microFrontend, router, reactCompiler } = config;
3460
+ const buildId = resolveYwkfBuildId(appName);
3448
3461
  const ywkfOutputDir = resolveApp(".ywkf");
3449
3462
  const defaultAlias = {
3450
3463
  "@": resolveApp("src"),
@@ -3568,6 +3581,7 @@ function createBaseConfig(config, cwd, options = {}) {
3568
3581
  Buffer: ["buffer", "Buffer"]
3569
3582
  }),
3570
3583
  new rspack2.DefinePlugin({
3584
+ "globalThis.__YWKF_BUILD_ID__": JSON.stringify(buildId),
3571
3585
  "process.env": JSON.stringify(process.env)
3572
3586
  }),
3573
3587
  new rspack2.HtmlRspackPlugin({
@@ -4840,6 +4854,370 @@ var ConventionalRoutePlugin = class {
4840
4854
  }
4841
4855
  };
4842
4856
 
4857
+ // src/runtime/chunk-recovery.ts
4858
+ var DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS = 3e4;
4859
+ var CHUNK_RECOVERY_STORAGE_KEY = "@4399ywkf/core:chunk-recovery-attempt";
4860
+ var CHUNK_RECOVERY_CACHE_BUST_PARAM = "__ywkf_chunk_retry";
4861
+ var CHUNK_RECOVERY_ACTIVE_SYMBOL = /* @__PURE__ */ Symbol.for(
4862
+ "@4399ywkf/core/chunk-recovery-active"
4863
+ );
4864
+ var CHUNK_RECOVERY_REGISTRY_SYMBOL = /* @__PURE__ */ Symbol.for(
4865
+ "@4399ywkf/core/chunk-recovery-registry"
4866
+ );
4867
+ var CHUNK_LOAD_ERROR_PATTERNS = [
4868
+ /\bChunkLoadError\b/i,
4869
+ /\bLoading (?:CSS )?chunk\s+\S+\s+failed\b/i,
4870
+ /\bFailed to fetch dynamically imported module\b/i,
4871
+ /\bImporting a module script failed\b/i,
4872
+ /\berror loading dynamically imported module\b/i,
4873
+ /\bUnable to preload CSS for\b/i,
4874
+ /\bFailed to load module script\b/i
4875
+ ];
4876
+ function getErrorLikeValue(error, key) {
4877
+ if ((typeof error !== "object" || error === null) && typeof error !== "function") {
4878
+ return void 0;
4879
+ }
4880
+ try {
4881
+ return error[key];
4882
+ } catch {
4883
+ return void 0;
4884
+ }
4885
+ }
4886
+ function matchesChunkLoadError(error, depth) {
4887
+ if (depth > 3) {
4888
+ return false;
4889
+ }
4890
+ if (typeof error === "string") {
4891
+ return CHUNK_LOAD_ERROR_PATTERNS.some((pattern) => pattern.test(error));
4892
+ }
4893
+ const name = getErrorLikeValue(error, "name");
4894
+ const message = getErrorLikeValue(error, "message");
4895
+ const code = getErrorLikeValue(error, "code");
4896
+ const signature = [name, message].filter((value) => typeof value === "string").join(": ");
4897
+ if (code === "CSS_CHUNK_LOAD_FAILED" || CHUNK_LOAD_ERROR_PATTERNS.some((pattern) => pattern.test(signature))) {
4898
+ return true;
4899
+ }
4900
+ const cause = getErrorLikeValue(error, "cause");
4901
+ return cause !== void 0 && cause !== error && matchesChunkLoadError(cause, depth + 1);
4902
+ }
4903
+ function isChunkLoadError(error) {
4904
+ return matchesChunkLoadError(error, 0);
4905
+ }
4906
+ function hashRecoveryIdentity(value) {
4907
+ let hash = 2166136261;
4908
+ for (let index = 0; index < value.length; index += 1) {
4909
+ hash ^= value.charCodeAt(index);
4910
+ hash = Math.imul(hash, 16777619);
4911
+ }
4912
+ return (hash >>> 0).toString(36);
4913
+ }
4914
+ function getFrameworkBuildId() {
4915
+ const buildId = globalThis.__YWKF_BUILD_ID__;
4916
+ return typeof buildId === "string" ? buildId.trim() : "";
4917
+ }
4918
+ function resolveRecoveryId(error, config) {
4919
+ const configuredBuildId = typeof config === "object" && typeof config.buildId === "string" ? config.buildId.trim() : "";
4920
+ const buildId = configuredBuildId || getFrameworkBuildId();
4921
+ if (buildId) {
4922
+ return `build-${hashRecoveryIdentity(buildId)}`;
4923
+ }
4924
+ const request = getErrorLikeValue(error, "request");
4925
+ const name = getErrorLikeValue(error, "name");
4926
+ const message = getErrorLikeValue(error, "message");
4927
+ const signature = [request, name, message].filter((value) => typeof value === "string").join(": ");
4928
+ return `error-${hashRecoveryIdentity(signature || String(error))}`;
4929
+ }
4930
+ function toError(error, fallbackMessage = "Chunk loading failed") {
4931
+ if (error instanceof Error) {
4932
+ return error;
4933
+ }
4934
+ if (typeof error === "string") {
4935
+ return new Error(error);
4936
+ }
4937
+ const message = getErrorLikeValue(error, "message");
4938
+ const name = getErrorLikeValue(error, "name");
4939
+ const normalizedError = new Error(typeof message === "string" ? message : fallbackMessage);
4940
+ if (typeof name === "string") {
4941
+ normalizedError.name = name;
4942
+ }
4943
+ return normalizedError;
4944
+ }
4945
+ function normalizeCooldownMs(value) {
4946
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1e3) {
4947
+ return DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS;
4948
+ }
4949
+ return value;
4950
+ }
4951
+ function resolveChunkRecoveryPolicy(config, context) {
4952
+ const options = typeof config === "object" ? config : void 0;
4953
+ const configuredEnabled = typeof config === "boolean" ? config : options?.enabled;
4954
+ const enabledForEnvironment = configuredEnabled ?? context.isProduction;
4955
+ if (!enabledForEnvironment) {
4956
+ return {
4957
+ enabled: false,
4958
+ disabledReason: configuredEnabled === false ? "configuration" : "environment",
4959
+ cooldownMs: normalizeCooldownMs(options?.cooldownMs),
4960
+ globalHandlers: options?.globalHandlers === true
4961
+ };
4962
+ }
4963
+ if (context.isMicroApp && options?.allowInMicroApp !== true) {
4964
+ return {
4965
+ enabled: false,
4966
+ disabledReason: "micro-app",
4967
+ cooldownMs: normalizeCooldownMs(options?.cooldownMs),
4968
+ globalHandlers: options?.globalHandlers === true
4969
+ };
4970
+ }
4971
+ return {
4972
+ enabled: true,
4973
+ cooldownMs: normalizeCooldownMs(options?.cooldownMs),
4974
+ globalHandlers: options?.globalHandlers === true
4975
+ };
4976
+ }
4977
+ function parseStoredChunkRecoveryState(value) {
4978
+ if (value === null) {
4979
+ return { attempts: [] };
4980
+ }
4981
+ const parsed = JSON.parse(value);
4982
+ if (!Array.isArray(parsed.attempts)) {
4983
+ throw new Error("Invalid chunk recovery state");
4984
+ }
4985
+ const attempts = parsed.attempts.map((attempt) => {
4986
+ if (typeof attempt !== "object" || attempt === null || typeof attempt.recoveryId !== "string" || typeof attempt.attemptedAt !== "number" || !Number.isFinite(attempt.attemptedAt)) {
4987
+ throw new Error("Invalid chunk recovery attempt");
4988
+ }
4989
+ return attempt;
4990
+ });
4991
+ if (parsed.lastAttemptAt !== void 0 && (typeof parsed.lastAttemptAt !== "number" || !Number.isFinite(parsed.lastAttemptAt))) {
4992
+ throw new Error("Invalid chunk recovery timestamp");
4993
+ }
4994
+ return { attempts, lastAttemptAt: parsed.lastAttemptAt };
4995
+ }
4996
+ function claimChunkRecoveryAttempt(storage, options) {
4997
+ const {
4998
+ cooldownMs = DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS,
4999
+ now,
5000
+ recoveryId,
5001
+ storageKey = CHUNK_RECOVERY_STORAGE_KEY
5002
+ } = options;
5003
+ let state;
5004
+ try {
5005
+ state = parseStoredChunkRecoveryState(storage.getItem(storageKey));
5006
+ } catch {
5007
+ return { claimed: false, reason: "storage-unavailable" };
5008
+ }
5009
+ const existingAttempt = state.attempts.find((attempt) => attempt.recoveryId === recoveryId);
5010
+ if (existingAttempt) {
5011
+ return {
5012
+ claimed: false,
5013
+ reason: "already-attempted",
5014
+ attemptedAt: existingAttempt.attemptedAt
5015
+ };
5016
+ }
5017
+ const inCooldown = state.lastAttemptAt !== void 0 && (state.lastAttemptAt > now || now - state.lastAttemptAt < cooldownMs);
5018
+ if (inCooldown) {
5019
+ return { claimed: false, reason: "cooldown", attemptedAt: state.lastAttemptAt };
5020
+ }
5021
+ try {
5022
+ storage.setItem(
5023
+ storageKey,
5024
+ JSON.stringify({
5025
+ attempts: [...state.attempts, { attemptedAt: now, recoveryId }],
5026
+ lastAttemptAt: now
5027
+ })
5028
+ );
5029
+ } catch {
5030
+ return { claimed: false, reason: "storage-unavailable" };
5031
+ }
5032
+ return { claimed: true, attemptedAt: now };
5033
+ }
5034
+ function isMicroAppRuntime() {
5035
+ if (typeof window === "undefined") {
5036
+ return false;
5037
+ }
5038
+ const runtimeWindow = window;
5039
+ return Boolean(runtimeWindow.__POWERED_BY_QIANKUN__ || runtimeWindow.__GARFISH__);
5040
+ }
5041
+ function getSessionStorage() {
5042
+ if (typeof window === "undefined") {
5043
+ return null;
5044
+ }
5045
+ try {
5046
+ return window.sessionStorage;
5047
+ } catch {
5048
+ return null;
5049
+ }
5050
+ }
5051
+ function invokeSafely(callback, label) {
5052
+ if (!callback) {
5053
+ return;
5054
+ }
5055
+ try {
5056
+ callback();
5057
+ } catch (error) {
5058
+ console.error(`[ywkf] ${label} \u6267\u884C\u5931\u8D25:`, error);
5059
+ }
5060
+ }
5061
+ function schedulePageReplace(timestamp) {
5062
+ setTimeout(() => {
5063
+ try {
5064
+ const reloadUrl = new URL(window.location.href);
5065
+ reloadUrl.searchParams.set(CHUNK_RECOVERY_CACHE_BUST_PARAM, String(timestamp));
5066
+ window.location.replace(reloadUrl.toString());
5067
+ } catch (error) {
5068
+ console.error("[ywkf] chunk \u6062\u590D\u5237\u65B0\u5931\u8D25:", error);
5069
+ }
5070
+ }, 0);
5071
+ }
5072
+ function clearChunkRecoveryCacheBustParam() {
5073
+ if (typeof window === "undefined") {
5074
+ return;
5075
+ }
5076
+ try {
5077
+ const currentUrl = new URL(window.location.href);
5078
+ if (!currentUrl.searchParams.has(CHUNK_RECOVERY_CACHE_BUST_PARAM)) {
5079
+ return;
5080
+ }
5081
+ currentUrl.searchParams.delete(CHUNK_RECOVERY_CACHE_BUST_PARAM);
5082
+ window.history.replaceState(window.history.state, "", currentUrl.toString());
5083
+ } catch (error) {
5084
+ console.error("[ywkf] \u6E05\u7406 chunk \u6062\u590D\u53C2\u6570\u5931\u8D25:", error);
5085
+ }
5086
+ }
5087
+ function handleChunkLoadErrorInternal(reason, options, trustedChunkEvent) {
5088
+ if (!trustedChunkEvent && !isChunkLoadError(reason)) {
5089
+ return null;
5090
+ }
5091
+ const error = toError(reason);
5092
+ const timestamp = Date.now();
5093
+ const recoveryId = resolveRecoveryId(reason, options.config);
5094
+ const isMicroApp = options.isMicroApp ?? isMicroAppRuntime();
5095
+ const policy = resolveChunkRecoveryPolicy(options.config, {
5096
+ isProduction: process.env.NODE_ENV === "production",
5097
+ isMicroApp
5098
+ });
5099
+ let outcome;
5100
+ if (!policy.enabled) {
5101
+ outcome = policy.disabledReason === "micro-app" ? "reload-skipped-micro-app" : "reload-skipped-disabled";
5102
+ } else {
5103
+ const storage = getSessionStorage();
5104
+ const claim = storage ? claimChunkRecoveryAttempt(storage, {
5105
+ cooldownMs: policy.cooldownMs,
5106
+ now: timestamp,
5107
+ recoveryId
5108
+ }) : { claimed: false, reason: "storage-unavailable" };
5109
+ if (claim.claimed) {
5110
+ outcome = "reload-scheduled";
5111
+ } else if (claim.reason === "already-attempted") {
5112
+ outcome = "reload-skipped-already-attempted";
5113
+ } else if (claim.reason === "cooldown") {
5114
+ outcome = "reload-skipped-cooldown";
5115
+ } else {
5116
+ outcome = "reload-skipped-storage-unavailable";
5117
+ }
5118
+ }
5119
+ const event = {
5120
+ error,
5121
+ source: options.source,
5122
+ outcome,
5123
+ timestamp,
5124
+ cooldownMs: policy.cooldownMs,
5125
+ recoveryId
5126
+ };
5127
+ invokeSafely(() => options.onError?.(error), "onError");
5128
+ invokeSafely(() => options.onRecoveryEvent?.(event), "onRecoveryEvent");
5129
+ if (outcome === "reload-scheduled") {
5130
+ schedulePageReplace(timestamp);
5131
+ }
5132
+ return event;
5133
+ }
5134
+ function handleChunkLoadError(reason, options) {
5135
+ return handleChunkLoadErrorInternal(reason, options, false);
5136
+ }
5137
+ function getLatestSubscriber(registry) {
5138
+ let latest;
5139
+ for (const subscriber of registry.subscribers.values()) {
5140
+ latest = subscriber;
5141
+ }
5142
+ return latest;
5143
+ }
5144
+ function createGlobalChunkRecoveryRegistry() {
5145
+ const registry = {
5146
+ subscribers: /* @__PURE__ */ new Map()
5147
+ };
5148
+ registry.handleUnhandledRejection = (event) => {
5149
+ const subscriber = getLatestSubscriber(registry);
5150
+ if (!subscriber) return;
5151
+ const recoveryEvent = handleChunkLoadError(event.reason, {
5152
+ ...subscriber,
5153
+ source: "unhandled-rejection"
5154
+ });
5155
+ if (recoveryEvent?.outcome === "reload-scheduled") {
5156
+ event.preventDefault();
5157
+ }
5158
+ };
5159
+ registry.handleVitePreloadError = (event) => {
5160
+ const subscriber = getLatestSubscriber(registry);
5161
+ if (!subscriber) return;
5162
+ const payload = event.payload;
5163
+ const recoveryEvent = handleChunkLoadErrorInternal(
5164
+ payload,
5165
+ {
5166
+ ...subscriber,
5167
+ source: "vite-preload-error"
5168
+ },
5169
+ true
5170
+ );
5171
+ if (recoveryEvent?.outcome === "reload-scheduled") {
5172
+ event.preventDefault();
5173
+ }
5174
+ };
5175
+ return registry;
5176
+ }
5177
+ function installGlobalChunkRecoveryHandlers(options) {
5178
+ if (typeof window === "undefined") {
5179
+ return () => {
5180
+ };
5181
+ }
5182
+ const isMicroApp = options.isMicroApp ?? isMicroAppRuntime();
5183
+ const policy = resolveChunkRecoveryPolicy(options.config, {
5184
+ isProduction: process.env.NODE_ENV === "production",
5185
+ isMicroApp
5186
+ });
5187
+ if (!policy.enabled || !policy.globalHandlers) {
5188
+ return () => {
5189
+ };
5190
+ }
5191
+ const runtimeWindow = window;
5192
+ let registry = runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL];
5193
+ if (!registry && runtimeWindow[CHUNK_RECOVERY_ACTIVE_SYMBOL] === true) {
5194
+ return () => {
5195
+ };
5196
+ }
5197
+ if (!registry) {
5198
+ registry = createGlobalChunkRecoveryRegistry();
5199
+ runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL] = registry;
5200
+ runtimeWindow[CHUNK_RECOVERY_ACTIVE_SYMBOL] = true;
5201
+ window.addEventListener("unhandledrejection", registry.handleUnhandledRejection);
5202
+ window.addEventListener("vite:preloadError", registry.handleVitePreloadError);
5203
+ }
5204
+ const subscriberId = /* @__PURE__ */ Symbol("chunk-recovery-subscriber");
5205
+ registry.subscribers.set(subscriberId, { ...options, isMicroApp });
5206
+ let disposed = false;
5207
+ return () => {
5208
+ if (disposed) return;
5209
+ disposed = true;
5210
+ registry.subscribers.delete(subscriberId);
5211
+ if (registry.subscribers.size > 0) return;
5212
+ window.removeEventListener("unhandledrejection", registry.handleUnhandledRejection);
5213
+ window.removeEventListener("vite:preloadError", registry.handleVitePreloadError);
5214
+ if (runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL] === registry) {
5215
+ delete runtimeWindow[CHUNK_RECOVERY_REGISTRY_SYMBOL];
5216
+ delete runtimeWindow[CHUNK_RECOVERY_ACTIVE_SYMBOL];
5217
+ }
5218
+ };
5219
+ }
5220
+
4843
5221
  // src/runtime/bootstrap.tsx
4844
5222
  import { createRoot } from "react-dom/client";
4845
5223
 
@@ -4891,8 +5269,23 @@ function useIsDev() {
4891
5269
  }
4892
5270
 
4893
5271
  // src/runtime/error-boundary.tsx
4894
- import { Component } from "react";
5272
+ import {
5273
+ Component,
5274
+ createContext as createContext2,
5275
+ useContext as useContext2,
5276
+ useEffect,
5277
+ useMemo,
5278
+ useRef
5279
+ } from "react";
5280
+ import { isRouteErrorResponse, useRouteError } from "react-router";
4895
5281
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
5282
+ var ChunkRecoveryRuntimeContext = createContext2(null);
5283
+ function ChunkRecoveryRuntimeProvider({
5284
+ children,
5285
+ ...value
5286
+ }) {
5287
+ return /* @__PURE__ */ jsx2(ChunkRecoveryRuntimeContext.Provider, { value, children });
5288
+ }
4896
5289
  var ErrorBoundary = class extends Component {
4897
5290
  constructor(props) {
4898
5291
  super(props);
@@ -4902,15 +5295,31 @@ var ErrorBoundary = class extends Component {
4902
5295
  return { hasError: true, error };
4903
5296
  }
4904
5297
  componentDidCatch(error, errorInfo) {
4905
- console.error("[ErrorBoundary] Caught error:", error, errorInfo);
4906
- this.props.onError?.(error, errorInfo);
5298
+ const reportError = () => {
5299
+ console.error("[ErrorBoundary] Caught error:", error, errorInfo);
5300
+ this.props.onError?.(error, errorInfo);
5301
+ };
5302
+ if (this.props.chunkRecovery === void 0) {
5303
+ reportError();
5304
+ return;
5305
+ }
5306
+ const recoveryEvent = handleChunkLoadError(error, {
5307
+ source: "error-boundary",
5308
+ config: this.props.chunkRecovery,
5309
+ isMicroApp: this.props.isMicroApp,
5310
+ onError: reportError,
5311
+ onRecoveryEvent: this.props.onRecoveryEvent
5312
+ });
5313
+ if (!recoveryEvent) {
5314
+ reportError();
5315
+ }
4907
5316
  }
4908
5317
  reset = () => {
4909
5318
  this.setState({ hasError: false, error: null });
4910
5319
  };
4911
5320
  render() {
4912
5321
  const { hasError, error } = this.state;
4913
- const { children, fallback } = this.props;
5322
+ const { children, chunkRecovery, fallback, isMicroApp } = this.props;
4914
5323
  if (hasError && error) {
4915
5324
  if (typeof fallback === "function") {
4916
5325
  return fallback(error, this.reset);
@@ -4918,12 +5327,86 @@ var ErrorBoundary = class extends Component {
4918
5327
  if (fallback) {
4919
5328
  return fallback;
4920
5329
  }
4921
- return /* @__PURE__ */ jsx2(DefaultErrorFallback, { error, onReset: this.reset });
5330
+ const resolvedIsMicroApp = isMicroApp ?? isMicroAppRuntime();
5331
+ const chunkRecoveryEnabled = chunkRecovery !== void 0 && resolveChunkRecoveryPolicy(chunkRecovery, {
5332
+ isProduction: process.env.NODE_ENV === "production",
5333
+ isMicroApp: resolvedIsMicroApp
5334
+ }).enabled;
5335
+ const canReloadPage = !resolvedIsMicroApp || typeof chunkRecovery === "object" && chunkRecovery.allowInMicroApp === true;
5336
+ return /* @__PURE__ */ jsx2(
5337
+ DefaultErrorFallback,
5338
+ {
5339
+ canReloadPage,
5340
+ error,
5341
+ isChunkError: chunkRecoveryEnabled && isChunkLoadError(error),
5342
+ onReset: this.reset
5343
+ }
5344
+ );
4922
5345
  }
4923
5346
  return children;
4924
5347
  }
4925
5348
  };
4926
- function DefaultErrorFallback({ error, onReset }) {
5349
+ var UNHANDLED_ROUTE_ERROR = /* @__PURE__ */ Symbol("unhandled-route-error");
5350
+ function normalizeRouteError(reason) {
5351
+ if (reason instanceof Error) return reason;
5352
+ if (typeof reason === "string") return new Error(reason);
5353
+ if (isRouteErrorResponse(reason)) {
5354
+ const message = [reason.status, reason.statusText].filter(Boolean).join(" ");
5355
+ const error = new Error(message || "Route request failed");
5356
+ error.name = "RouteErrorResponse";
5357
+ return error;
5358
+ }
5359
+ return new Error("Route rendering failed");
5360
+ }
5361
+ function RouteErrorBoundary({ fallback }) {
5362
+ const reason = useRouteError();
5363
+ const error = useMemo(() => normalizeRouteError(reason), [reason]);
5364
+ const recoveryRuntime = useContext2(ChunkRecoveryRuntimeContext);
5365
+ const handledReasonRef = useRef(UNHANDLED_ROUTE_ERROR);
5366
+ useEffect(() => {
5367
+ if (Object.is(handledReasonRef.current, reason)) return;
5368
+ handledReasonRef.current = reason;
5369
+ const reportError = () => {
5370
+ console.error("[RouteErrorBoundary] Caught error:", error);
5371
+ recoveryRuntime?.onError?.(error);
5372
+ };
5373
+ if (!recoveryRuntime) {
5374
+ reportError();
5375
+ return;
5376
+ }
5377
+ const recoveryEvent = handleChunkLoadError(reason, {
5378
+ source: "route-error-boundary",
5379
+ config: recoveryRuntime.chunkRecovery,
5380
+ isMicroApp: recoveryRuntime.isMicroApp,
5381
+ onError: reportError,
5382
+ onRecoveryEvent: recoveryRuntime.onRecoveryEvent
5383
+ });
5384
+ if (!recoveryEvent) reportError();
5385
+ }, [error, reason, recoveryRuntime]);
5386
+ if (typeof fallback === "function") return fallback(error);
5387
+ if (fallback) return fallback;
5388
+ const chunkRecovery = recoveryRuntime?.chunkRecovery;
5389
+ const resolvedIsMicroApp = recoveryRuntime?.isMicroApp ?? isMicroAppRuntime();
5390
+ const chunkRecoveryEnabled = chunkRecovery !== void 0 && resolveChunkRecoveryPolicy(chunkRecovery, {
5391
+ isProduction: process.env.NODE_ENV === "production",
5392
+ isMicroApp: resolvedIsMicroApp
5393
+ }).enabled;
5394
+ const canReloadPage = !resolvedIsMicroApp || typeof chunkRecovery === "object" && chunkRecovery.allowInMicroApp === true;
5395
+ return /* @__PURE__ */ jsx2(
5396
+ DefaultErrorFallback,
5397
+ {
5398
+ canReloadPage,
5399
+ error,
5400
+ isChunkError: chunkRecoveryEnabled && isChunkLoadError(reason)
5401
+ }
5402
+ );
5403
+ }
5404
+ function DefaultErrorFallback({
5405
+ canReloadPage,
5406
+ error,
5407
+ isChunkError,
5408
+ onReset
5409
+ }) {
4927
5410
  const isDev = process.env.NODE_ENV === "development";
4928
5411
  return /* @__PURE__ */ jsx2(
4929
5412
  "div",
@@ -4949,7 +5432,7 @@ function DefaultErrorFallback({ error, onReset }) {
4949
5432
  },
4950
5433
  children: [
4951
5434
  /* @__PURE__ */ jsx2("h1", { style: { color: "#ff4d4f", marginBottom: 16 }, children: "\u9875\u9762\u51FA\u9519\u4E86" }),
4952
- /* @__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" }),
5435
+ /* @__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" }),
4953
5436
  isDev && /* @__PURE__ */ jsxs(
4954
5437
  "details",
4955
5438
  {
@@ -4983,8 +5466,8 @@ function DefaultErrorFallback({ error, onReset }) {
4983
5466
  ]
4984
5467
  }
4985
5468
  ),
4986
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
4987
- /* @__PURE__ */ jsx2(
5469
+ (canReloadPage || !isChunkError && onReset) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
5470
+ !isChunkError && onReset && /* @__PURE__ */ jsx2(
4988
5471
  "button",
4989
5472
  {
4990
5473
  onClick: onReset,
@@ -4999,7 +5482,7 @@ function DefaultErrorFallback({ error, onReset }) {
4999
5482
  children: "\u91CD\u8BD5"
5000
5483
  }
5001
5484
  ),
5002
- /* @__PURE__ */ jsx2(
5485
+ canReloadPage && /* @__PURE__ */ jsx2(
5003
5486
  "button",
5004
5487
  {
5005
5488
  onClick: () => window.location.reload(),
@@ -5048,7 +5531,7 @@ function composeProviders(providers) {
5048
5531
  }, children);
5049
5532
  };
5050
5533
  }
5051
- function RootProvider({ config, children }) {
5534
+ function RootProvider({ config, children, isMicroApp }) {
5052
5535
  const {
5053
5536
  appName = "app",
5054
5537
  router,
@@ -5056,7 +5539,8 @@ function RootProvider({ config, children }) {
5056
5539
  strictMode = true,
5057
5540
  antd = { enabled: true },
5058
5541
  providers = [],
5059
- lifecycle
5542
+ lifecycle,
5543
+ chunkRecovery
5060
5544
  } = config;
5061
5545
  const appContextValue = {
5062
5546
  appName,
@@ -5087,8 +5571,30 @@ function RootProvider({ config, children }) {
5087
5571
  ...providers.map((p) => ({ ...p, order: p.order ?? 50 }))
5088
5572
  ];
5089
5573
  const ComposedProviders = composeProviders(allProviders);
5574
+ const rootChunkRecovery = chunkRecovery ?? {
5575
+ enabled: process.env.NODE_ENV === "production"
5576
+ };
5577
+ const resolvedIsMicroApp = isMicroApp ?? isMicroAppRuntime();
5090
5578
  const content = router ? /* @__PURE__ */ jsx3(Suspense, { fallback: /* @__PURE__ */ jsx3(DefaultLoading, {}), children: /* @__PURE__ */ jsx3(RouterProvider, { router }) }) : children;
5091
- const app = /* @__PURE__ */ jsx3(ErrorBoundary, { onError: lifecycle?.onError, children: /* @__PURE__ */ jsx3(ComposedProviders, { children: content }) });
5579
+ const app = /* @__PURE__ */ jsx3(
5580
+ ErrorBoundary,
5581
+ {
5582
+ chunkRecovery: rootChunkRecovery,
5583
+ isMicroApp: resolvedIsMicroApp,
5584
+ onError: lifecycle?.onError,
5585
+ onRecoveryEvent: lifecycle?.onRecoveryEvent,
5586
+ children: /* @__PURE__ */ jsx3(
5587
+ ChunkRecoveryRuntimeProvider,
5588
+ {
5589
+ chunkRecovery: rootChunkRecovery,
5590
+ isMicroApp: resolvedIsMicroApp,
5591
+ onError: lifecycle?.onError,
5592
+ onRecoveryEvent: lifecycle?.onRecoveryEvent,
5593
+ children: /* @__PURE__ */ jsx3(ComposedProviders, { children: content })
5594
+ }
5595
+ )
5596
+ }
5597
+ );
5092
5598
  if (strictMode) {
5093
5599
  return /* @__PURE__ */ jsx3(StrictMode, { children: app });
5094
5600
  }
@@ -5105,6 +5611,7 @@ function createProvider(component, props, order) {
5105
5611
  // src/runtime/bootstrap.tsx
5106
5612
  import { jsx as jsx4 } from "react/jsx-runtime";
5107
5613
  var root = null;
5614
+ var disposeChunkRecoveryHandlers = null;
5108
5615
  async function resolveAppConfig(config) {
5109
5616
  if (config.router || !config.conventionalRoutes) {
5110
5617
  return config;
@@ -5127,6 +5634,10 @@ async function resolveAppConfig(config) {
5127
5634
  }
5128
5635
  async function bootstrap(config) {
5129
5636
  const { rootId = "root", lifecycle } = config;
5637
+ const isMicroApp = isMicroAppEnv();
5638
+ if (!isMicroApp) {
5639
+ clearChunkRecoveryCacheBustParam();
5640
+ }
5130
5641
  const resolvedConfig = await resolveAppConfig(config);
5131
5642
  if (lifecycle?.onBeforeMount) {
5132
5643
  await lifecycle.onBeforeMount();
@@ -5135,8 +5646,22 @@ async function bootstrap(config) {
5135
5646
  if (!rootElement) {
5136
5647
  throw new Error(`\u627E\u4E0D\u5230\u6839\u5143\u7D20 #${rootId}`);
5137
5648
  }
5138
- root = createRoot(rootElement);
5139
- root.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig }));
5649
+ disposeChunkRecoveryHandlers?.();
5650
+ const nextDisposeChunkRecoveryHandlers = installGlobalChunkRecoveryHandlers({
5651
+ config: resolvedConfig.chunkRecovery,
5652
+ isMicroApp,
5653
+ onError: (error) => resolvedConfig.lifecycle?.onError?.(error),
5654
+ onRecoveryEvent: resolvedConfig.lifecycle?.onRecoveryEvent
5655
+ });
5656
+ try {
5657
+ root = createRoot(rootElement);
5658
+ root.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig, isMicroApp }));
5659
+ disposeChunkRecoveryHandlers = nextDisposeChunkRecoveryHandlers;
5660
+ } catch (error) {
5661
+ nextDisposeChunkRecoveryHandlers();
5662
+ root = null;
5663
+ throw error;
5664
+ }
5140
5665
  if (lifecycle?.onMounted) {
5141
5666
  setTimeout(() => {
5142
5667
  lifecycle.onMounted?.();
@@ -5149,6 +5674,8 @@ async function bootstrap(config) {
5149
5674
  }
5150
5675
  }
5151
5676
  function unmount() {
5677
+ disposeChunkRecoveryHandlers?.();
5678
+ disposeChunkRecoveryHandlers = null;
5152
5679
  if (root) {
5153
5680
  root.unmount();
5154
5681
  root = null;
@@ -5156,6 +5683,7 @@ function unmount() {
5156
5683
  }
5157
5684
  function createMicroApp(getConfig) {
5158
5685
  let microRoot = null;
5686
+ let disposeMicroChunkRecoveryHandlers = null;
5159
5687
  return {
5160
5688
  /**
5161
5689
  * 微前端 bootstrap 生命周期
@@ -5179,8 +5707,22 @@ function createMicroApp(getConfig) {
5179
5707
  if (resolvedConfig.lifecycle?.onBeforeMount) {
5180
5708
  await resolvedConfig.lifecycle.onBeforeMount();
5181
5709
  }
5182
- microRoot = createRoot(rootElement);
5183
- microRoot.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig }));
5710
+ disposeMicroChunkRecoveryHandlers?.();
5711
+ const nextDisposeMicroChunkRecoveryHandlers = installGlobalChunkRecoveryHandlers({
5712
+ config: resolvedConfig.chunkRecovery,
5713
+ isMicroApp: true,
5714
+ onError: (error) => resolvedConfig.lifecycle?.onError?.(error),
5715
+ onRecoveryEvent: resolvedConfig.lifecycle?.onRecoveryEvent
5716
+ });
5717
+ try {
5718
+ microRoot = createRoot(rootElement);
5719
+ microRoot.render(/* @__PURE__ */ jsx4(RootProvider, { config: resolvedConfig, isMicroApp: true }));
5720
+ disposeMicroChunkRecoveryHandlers = nextDisposeMicroChunkRecoveryHandlers;
5721
+ } catch (error) {
5722
+ nextDisposeMicroChunkRecoveryHandlers();
5723
+ microRoot = null;
5724
+ throw error;
5725
+ }
5184
5726
  if (resolvedConfig.lifecycle?.onMounted) {
5185
5727
  setTimeout(() => {
5186
5728
  resolvedConfig.lifecycle?.onMounted?.();
@@ -5200,6 +5742,8 @@ function createMicroApp(getConfig) {
5200
5742
  microRoot.unmount();
5201
5743
  microRoot = null;
5202
5744
  }
5745
+ disposeMicroChunkRecoveryHandlers?.();
5746
+ disposeMicroChunkRecoveryHandlers = null;
5203
5747
  },
5204
5748
  /**
5205
5749
  * 微前端 update 生命周期(可选)
@@ -5227,11 +5771,16 @@ function getMicroAppPublicPath() {
5227
5771
  export {
5228
5772
  AppContext,
5229
5773
  AppContextProvider,
5774
+ CHUNK_RECOVERY_ACTIVE_SYMBOL,
5775
+ CHUNK_RECOVERY_CACHE_BUST_PARAM,
5776
+ CHUNK_RECOVERY_STORAGE_KEY,
5230
5777
  ConventionalRouteGenerator,
5231
5778
  ConventionalRoutePlugin,
5779
+ DEFAULT_CHUNK_RECOVERY_COOLDOWN_MS,
5232
5780
  ErrorBoundary,
5233
5781
  PluginManager,
5234
5782
  RootProvider,
5783
+ RouteErrorBoundary,
5235
5784
  YwkfGenerator,
5236
5785
  YwkfGeneratorPlugin,
5237
5786
  analyticsPlugin,
@@ -5239,6 +5788,8 @@ export {
5239
5788
  bootstrap,
5240
5789
  browserCheckPlugin,
5241
5790
  build,
5791
+ claimChunkRecoveryAttempt,
5792
+ clearChunkRecoveryCacheBustParam,
5242
5793
  createBaseConfig,
5243
5794
  createDevConfig,
5244
5795
  createMicroApp,
@@ -5254,10 +5805,15 @@ export {
5254
5805
  generateConventionalRoutes,
5255
5806
  getDefaultBiomeConfig,
5256
5807
  getMicroAppPublicPath,
5808
+ handleChunkLoadError,
5257
5809
  i18nPlugin,
5810
+ installGlobalChunkRecoveryHandlers,
5811
+ isChunkLoadError,
5258
5812
  isMicroAppEnv,
5813
+ isMicroAppRuntime,
5259
5814
  mockPlugin,
5260
5815
  reactQueryPlugin,
5816
+ resolveChunkRecoveryPolicy,
5261
5817
  tailwindPlugin,
5262
5818
  themePlugin,
5263
5819
  unmount,