@fulgurjs/federation 5.0.3 → 5.1.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/CHANGELOG.md +13 -0
- package/DESIGN.md +11 -0
- package/README.en.md +441 -0
- package/README.md +120 -5
- package/dist/chunk-4PLNAYZX.js +84 -0
- package/dist/cli.js +4 -1
- package/dist/host-pages-core-BFxp-IOj.d.cts +37 -0
- package/dist/host-pages-core-DLfMeDrO.d.ts +37 -0
- package/dist/index.cjs +137 -26
- package/dist/index.js +137 -26
- package/dist/react-adapter.cjs +518 -0
- package/dist/react-adapter.d.cts +117 -0
- package/dist/react-adapter.d.ts +117 -0
- package/dist/react-adapter.js +292 -0
- package/dist/react.d.ts +442 -0
- package/dist/react.js +10 -0
- package/dist/runtime-entry.d.ts +29 -17
- package/dist/runtime.js +13 -13
- package/dist/vue-adapter.cjs +66 -50
- package/dist/vue-adapter.d.cts +4 -25
- package/dist/vue-adapter.d.ts +4 -25
- package/dist/vue-adapter.js +14 -66
- package/examples/README.md +5 -0
- package/examples/react-host/README.md +28 -0
- package/examples/react-host/fulgurjs.config.ts +19 -0
- package/examples/react-host/index.html +11 -0
- package/examples/react-host/package.json +23 -0
- package/examples/react-host/src/App.tsx +66 -0
- package/examples/react-host/src/federation/pages.data.ts +19 -0
- package/examples/react-host/src/main.tsx +8 -0
- package/examples/react-host/tsconfig.json +14 -0
- package/examples/react-host/vite.config.ts +8 -0
- package/examples/react-remote/fulgurjs.config.ts +17 -0
- package/examples/react-remote/index.html +11 -0
- package/examples/react-remote/package.json +22 -0
- package/examples/react-remote/src/Button.tsx +15 -0
- package/examples/react-remote/src/main.tsx +6 -0
- package/examples/react-remote/src/pages/Detail.tsx +10 -0
- package/examples/react-remote/src/pages/Home.tsx +14 -0
- package/examples/react-remote/src/utils.ts +3 -0
- package/examples/react-remote/tsconfig.json +14 -0
- package/examples/react-remote/vite.config.ts +8 -0
- package/package.json +30 -3
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ComponentType, ReactNode, Component, ErrorInfo, Ref } from 'react';
|
|
2
|
+
import { PageRouteLike } from './pages.js';
|
|
3
|
+
import { R as ResolvedHostPage, H as HostPagesCoreOptions } from './host-pages-core-DLfMeDrO.js';
|
|
4
|
+
|
|
5
|
+
interface LoadRemoteOptions {
|
|
6
|
+
shareScope?: string;
|
|
7
|
+
retries?: number;
|
|
8
|
+
fallbackModule?: () => any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* React 适配层:remoteComponent() / useLoadRemote() / RemoteErrorBoundary /
|
|
13
|
+
* createReactHostPages()。只接收加载函数,不静态引用运行时内核(同 vue-adapter 形态,
|
|
14
|
+
* dist/react.js 由构建脚本绑定 loadRemote 后再导出)。
|
|
15
|
+
*
|
|
16
|
+
* 语义要点(README React 章节的实现依据):
|
|
17
|
+
* - remoteComponent:工厂与页面表声明零加载副作用;首次渲染才 loadRemote;
|
|
18
|
+
* 加载状态机自带 pending/error/ready 三态,不用 React.lazy——lazy 的失败 Promise
|
|
19
|
+
* 会缓存在实例上,仅重置错误边界无法恢复,必须重建加载尝试。retry 以受控 attempt
|
|
20
|
+
* 计数触发(不在普通 render 中重建加载器,组件本地状态不因重渲染反复重置)。
|
|
21
|
+
* - timeout 是本次组件加载等待上限(适配层实现),不取消 loadRemote 的共享请求,
|
|
22
|
+
* 不修改远程入口配置超时;迟到的成功/失败一律丢弃,不产生未处理 rejection。
|
|
23
|
+
* retry 对已成功缓存的模块不重新下载(运行时缓存),只重走生命周期(onSession 按代次去重)。
|
|
24
|
+
* - 渲染期异常由内置 RemoteRenderBoundary 捕获,与网络/导出错误分开记录与展示;
|
|
25
|
+
* ErrorBoundary 不捕获事件处理器与任意异步回调(React 边界语义,README 已声明)。
|
|
26
|
+
* - 代码全部 createElement(无 JSX):适配器自身不依赖 jsx-runtime,减少共享子路径面。
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
type LoadRemoteFn = (spec: string, opts?: LoadRemoteOptions) => Promise<any>;
|
|
30
|
+
/** 渲染函数形态的错误占位:(error, retry) => ReactNode;与 remoteComponent.error 一致 */
|
|
31
|
+
type RemoteErrorFallback = (error: unknown, retry: () => void) => ReactNode;
|
|
32
|
+
interface ReactRemoteComponentOptions {
|
|
33
|
+
/** 本次加载 pending 时的占位(区别于失败占位);默认 null */
|
|
34
|
+
fallback?: ReactNode;
|
|
35
|
+
/** 加载失败或子树渲染错误的展示;默认内置中文占位(错误码+根因+修法+重试) */
|
|
36
|
+
error?: ReactNode | RemoteErrorFallback;
|
|
37
|
+
/** 透传现行 loadRemote 的重试选项(0-10 整数),不另叠自动重试 */
|
|
38
|
+
retries?: number;
|
|
39
|
+
/** 本次组件加载等待上限 ms;不提供则沿用 loadRemote 自身超时。不取消已发出的共享请求 */
|
|
40
|
+
timeout?: number;
|
|
41
|
+
}
|
|
42
|
+
interface RemoteErrorBoundaryProps {
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
/** 错误占位:节点或 ({ error, reset }) 渲染函数;默认内置中文占位 */
|
|
45
|
+
fallback?: ReactNode | ((args: {
|
|
46
|
+
error: unknown;
|
|
47
|
+
reset: () => void;
|
|
48
|
+
}) => ReactNode);
|
|
49
|
+
/** 渲染异常回调(error + React componentStack 信息) */
|
|
50
|
+
onError?: (error: unknown, info: ErrorInfo) => void;
|
|
51
|
+
/** 任一项变化时重置边界状态(受控重试的常用形态:传 [retryEpoch]) */
|
|
52
|
+
resetKeys?: readonly unknown[];
|
|
53
|
+
}
|
|
54
|
+
interface BoundaryState {
|
|
55
|
+
error: unknown | null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 页面级错误边界:只捕获传到它的子树渲染错误。remoteComponent 内置边界已处理自身错误,
|
|
59
|
+
* 外层边界看不到内层已消费的异常。reset 只重置边界状态;子组件若持有失败缓存
|
|
60
|
+
* (如 React.lazy),还需要由调用方重建加载尝试(remoteComponent 的 retry 已含两者)。
|
|
61
|
+
*/
|
|
62
|
+
declare class RemoteErrorBoundary extends Component<RemoteErrorBoundaryProps, BoundaryState> {
|
|
63
|
+
state: BoundaryState;
|
|
64
|
+
static getDerivedStateFromError(error: unknown): BoundaryState;
|
|
65
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
66
|
+
componentDidUpdate(prev: RemoteErrorBoundaryProps): void;
|
|
67
|
+
readonly reset: () => void;
|
|
68
|
+
render(): ReactNode;
|
|
69
|
+
}
|
|
70
|
+
declare function createRemoteComponent(loadRemote: LoadRemoteFn): <P = Record<string, unknown>>(spec: string, opts?: ReactRemoteComponentOptions) => ComponentType<P & {
|
|
71
|
+
ref?: Ref<unknown>;
|
|
72
|
+
}>;
|
|
73
|
+
interface UseLoadRemoteOptions {
|
|
74
|
+
/** 透传 loadRemote 的 shareScope */
|
|
75
|
+
shareScope?: string;
|
|
76
|
+
/** 透传 loadRemote 的重试次数(0-10 整数) */
|
|
77
|
+
retries?: number;
|
|
78
|
+
/**
|
|
79
|
+
* 用户显式配置的失败兜底模块(透传 loadRemote.fallbackModule):配置后加载失败返回
|
|
80
|
+
* 兜底值而不写 error。这是显式声明的行为,不是静默成功——README 已说明。
|
|
81
|
+
*/
|
|
82
|
+
fallbackModule?: () => any;
|
|
83
|
+
}
|
|
84
|
+
interface UseLoadRemoteResult<Module = any> {
|
|
85
|
+
/** 加载成功后的模块命名空间;未成功时 undefined */
|
|
86
|
+
data: Module | undefined;
|
|
87
|
+
/** 失败原因;无错误时恒为 undefined(统一空值) */
|
|
88
|
+
error: unknown;
|
|
89
|
+
/** 是否有请求在途 */
|
|
90
|
+
loading: boolean;
|
|
91
|
+
/** 重新走一次加载生命周期;已成功的模块经运行时缓存不会重新下载。Promise<void> 正常结束(不抛) */
|
|
92
|
+
reload: () => Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
declare function createUseLoadRemote(loadRemote: LoadRemoteFn): <Module = any>(spec: string, opts?: UseLoadRemoteOptions) => UseLoadRemoteResult<Module>;
|
|
95
|
+
interface ReactHostPagesOptions extends HostPagesCoreOptions {
|
|
96
|
+
/** 每次页面模块实际加载尝试前执行(同步或异步);宿主在此提供最新 context */
|
|
97
|
+
beforeLoad?: () => void | Promise<void>;
|
|
98
|
+
/** 页面加载 pending 占位(同 remoteComponent.fallback) */
|
|
99
|
+
fallback?: ReactNode;
|
|
100
|
+
/** 页面加载/渲染错误占位(同 remoteComponent.error) */
|
|
101
|
+
error?: ReactNode | RemoteErrorFallback;
|
|
102
|
+
/** 透传 loadRemote 重试(0-10 整数) */
|
|
103
|
+
retries?: number;
|
|
104
|
+
/** 单次页面组件加载等待上限 ms(同 remoteComponent.timeout) */
|
|
105
|
+
timeout?: number;
|
|
106
|
+
}
|
|
107
|
+
interface ReactHostPages {
|
|
108
|
+
/** 原页面记录 */
|
|
109
|
+
pages: PageRouteLike[];
|
|
110
|
+
/** 路径 → 页面解析(base 前缀、最长前缀、参数解码、R1–R5 语义与 Vue 完全一致) */
|
|
111
|
+
resolve(path: string): ResolvedHostPage | null;
|
|
112
|
+
/** 按 spec 取页面组件(同 spec 同登录代次复用;新非空 sessionKey 到来时重建以触发 onSession) */
|
|
113
|
+
component<P = Record<string, unknown>>(spec: string): ComponentType<P>;
|
|
114
|
+
}
|
|
115
|
+
declare function createReactHostPages(options: ReactHostPagesOptions, loadRemote: LoadRemoteFn): ReactHostPages;
|
|
116
|
+
|
|
117
|
+
export { type ReactHostPages, type ReactHostPagesOptions, type ReactRemoteComponentOptions, RemoteErrorBoundary, type RemoteErrorBoundaryProps, type RemoteErrorFallback, ResolvedHostPage, type UseLoadRemoteOptions, type UseLoadRemoteResult, createReactHostPages, createRemoteComponent, createUseLoadRemote };
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cleanCompName,
|
|
3
|
+
createHostPagesCore,
|
|
4
|
+
noRenderableExportError,
|
|
5
|
+
readSessionKey,
|
|
6
|
+
shouldResetSessionCache
|
|
7
|
+
} from "./chunk-4PLNAYZX.js";
|
|
8
|
+
import "./chunk-TSXZ7TWA.js";
|
|
9
|
+
|
|
10
|
+
// src/react-adapter.ts
|
|
11
|
+
import {
|
|
12
|
+
Component,
|
|
13
|
+
createElement,
|
|
14
|
+
forwardRef,
|
|
15
|
+
useCallback,
|
|
16
|
+
useEffect,
|
|
17
|
+
useRef,
|
|
18
|
+
useState
|
|
19
|
+
} from "react";
|
|
20
|
+
var ERROR_STYLE = {
|
|
21
|
+
padding: "16px",
|
|
22
|
+
border: "1px solid #fde2e2",
|
|
23
|
+
borderRadius: "4px",
|
|
24
|
+
background: "#fef0f0",
|
|
25
|
+
color: "#c45656",
|
|
26
|
+
fontSize: "13px",
|
|
27
|
+
lineHeight: "1.6",
|
|
28
|
+
boxSizing: "border-box"
|
|
29
|
+
};
|
|
30
|
+
var TITLE_STYLE = { margin: "0 0 4px", fontWeight: 600 };
|
|
31
|
+
var MESSAGE_STYLE = { margin: "0 0 8px", wordBreak: "break-all" };
|
|
32
|
+
var FIX_STYLE = { margin: "0 0 8px" };
|
|
33
|
+
var DefaultErrorView = function FulgurjsRemoteError({ error, retry, phase }) {
|
|
34
|
+
const err = error;
|
|
35
|
+
const code = err?.code ?? "UNKNOWN";
|
|
36
|
+
const message = err?.message ?? String(error ?? "unknown error");
|
|
37
|
+
return createElement("div", { style: ERROR_STYLE, "data-fulgurjs-error": code }, [
|
|
38
|
+
createElement(
|
|
39
|
+
"p",
|
|
40
|
+
{ key: "t", style: TITLE_STYLE },
|
|
41
|
+
phase === "load" ? `\u8FDC\u7A0B\u7EC4\u4EF6\u52A0\u8F7D\u5931\u8D25\uFF08\u9519\u8BEF\u7801 ${code}\uFF09` : `\u8FDC\u7A0B\u7EC4\u4EF6\u6E32\u67D3\u51FA\u9519\uFF08\u9519\u8BEF\u7801 ${code}\uFF09`
|
|
42
|
+
),
|
|
43
|
+
createElement("p", { key: "m", style: MESSAGE_STYLE }, message),
|
|
44
|
+
createElement("p", { key: "f", style: FIX_STYLE }, phase === "render" ? "\u4FEE\u6CD5\uFF1A\u67E5\u770B\u6D4F\u89C8\u5668 console \u4E2D\u7684\u7EC4\u4EF6\u8C03\u7528\u6808\u5B9A\u4F4D\u8FDC\u7A0B\u7EC4\u4EF6\u5185\u90E8\u7684\u6E32\u67D3\u5F02\u5E38\uFF08\u9519\u8BEF\u629B\u81EA\u8FDC\u7A0B\u4EE3\u7801\u672C\u8EAB\uFF0C\u4E0E\u7F51\u7EDC\u52A0\u8F7D\u65E0\u5173\uFF09\u3002" : "\u4FEE\u6CD5\uFF1A\u2460 \u6838\u5BF9 spec \u7684\u300C\u8FDC\u7A0B\u540D/expose \u540D\u300D\u4E0E\u8FDC\u7A0B\u5E94\u7528 exposes \u662F\u5426\u4E00\u81F4\uFF08MFU-006/008\uFF09\uFF1B\u2461 \u6838\u5BF9 remotes \u5730\u5740\u7AEF\u53E3\u4E0E\u8FDC\u7A0B\u670D\u52A1\u53EF\u8FBE\u6027\uFF08MFU-001\uFF09\uFF1B\u2462 \u67E5\u770B window \u7684 fulgurjs:error \u4E8B\u4EF6\u4E0E console \u540C\u6E90\u9519\u8BEF\u5B9A\u4F4D\u6839\u56E0\u3002"),
|
|
45
|
+
createElement("button", { key: "r", onClick: retry, type: "button" }, "\u91CD\u8BD5")
|
|
46
|
+
]);
|
|
47
|
+
};
|
|
48
|
+
function isValidComponentType(v) {
|
|
49
|
+
if (typeof v === "function") return true;
|
|
50
|
+
if (v !== null && typeof v === "object" && typeof v.$$typeof === "symbol") return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
function assertAdapterOptions(source, opts) {
|
|
54
|
+
if (opts.retries !== void 0 && (typeof opts.retries !== "number" || !Number.isInteger(opts.retries) || opts.retries < 0 || opts.retries > 10)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`[fulgurjs] ${source} \u7684 retries \u914D\u7F6E\u65E0\u6548\uFF1A\u5F53\u524D\u4E3A ${String(opts.retries)}\uFF0C\u5E94\u4E3A 0 \u5230 10 \u7684\u6574\u6570\u3002\u8BF7\u4FEE\u6B63\u9009\u9879\u3002`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (opts.timeout !== void 0 && (typeof opts.timeout !== "number" || !Number.isFinite(opts.timeout) || opts.timeout <= 0)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`[fulgurjs] ${source} \u7684 timeout \u914D\u7F6E\u65E0\u6548\uFF1A\u5F53\u524D\u4E3A ${String(opts.timeout)}\uFF0C\u5E94\u4E3A\u5927\u4E8E 0 \u7684\u6709\u9650\u6BEB\u79D2\u6570\u3002\u8BF7\u4FEE\u6B63\u9009\u9879\u3002`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function renderErrorNode(errorOpt, error, retry, phase) {
|
|
66
|
+
if (typeof errorOpt === "function") return errorOpt(error, retry);
|
|
67
|
+
if (errorOpt !== void 0) return errorOpt;
|
|
68
|
+
return createElement(DefaultErrorView, { error, retry, phase });
|
|
69
|
+
}
|
|
70
|
+
var RemoteErrorBoundary = class extends Component {
|
|
71
|
+
state = { error: null };
|
|
72
|
+
static getDerivedStateFromError(error) {
|
|
73
|
+
return { error };
|
|
74
|
+
}
|
|
75
|
+
componentDidCatch(error, info) {
|
|
76
|
+
console.error("[fulgurjs] \u8FDC\u7A0B\u7EC4\u4EF6\u6E32\u67D3\u5F02\u5E38\uFF08RemoteErrorBoundary \u6355\u83B7\uFF09\uFF1A", error, info?.componentStack ?? "");
|
|
77
|
+
this.props.onError?.(error, info);
|
|
78
|
+
}
|
|
79
|
+
componentDidUpdate(prev) {
|
|
80
|
+
if (this.state.error !== null && !shallowArrayEq(prev.resetKeys, this.props.resetKeys)) {
|
|
81
|
+
this.reset();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
reset = () => {
|
|
85
|
+
this.setState({ error: null });
|
|
86
|
+
};
|
|
87
|
+
render() {
|
|
88
|
+
if (this.state.error !== null) {
|
|
89
|
+
const f = this.props.fallback;
|
|
90
|
+
if (typeof f === "function") return f({ error: this.state.error, reset: this.reset });
|
|
91
|
+
if (f !== void 0) return f;
|
|
92
|
+
return renderErrorNode(void 0, this.state.error, this.reset, "render");
|
|
93
|
+
}
|
|
94
|
+
return this.props.children;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
function shallowArrayEq(a, b) {
|
|
98
|
+
if (a === b) return true;
|
|
99
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
100
|
+
return a.every((v, i) => v === b[i]);
|
|
101
|
+
}
|
|
102
|
+
function RemoteLoader({ spec, opts, loadRemote, beforeLoad, attempt, retry, passthrough, fwdRef }) {
|
|
103
|
+
const [state, setState] = useState({ phase: "pending" });
|
|
104
|
+
const latest = useRef({ passthrough, fwdRef });
|
|
105
|
+
latest.current = { passthrough, fwdRef };
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
let cancelled = false;
|
|
108
|
+
let settled = false;
|
|
109
|
+
let timer;
|
|
110
|
+
const gen = attempt;
|
|
111
|
+
const commit = (next) => {
|
|
112
|
+
if (cancelled || settled || gen !== attempt) return;
|
|
113
|
+
settled = true;
|
|
114
|
+
setState(next);
|
|
115
|
+
};
|
|
116
|
+
setState({ phase: "pending" });
|
|
117
|
+
const run = (async () => {
|
|
118
|
+
await beforeLoad?.();
|
|
119
|
+
return loadRemote(spec, { retries: opts.retries });
|
|
120
|
+
})();
|
|
121
|
+
run.catch(() => {
|
|
122
|
+
});
|
|
123
|
+
const race = opts.timeout === void 0 ? run : Promise.race([
|
|
124
|
+
run,
|
|
125
|
+
new Promise((_, reject) => {
|
|
126
|
+
timer = setTimeout(() => {
|
|
127
|
+
reject(new Error(
|
|
128
|
+
`[fulgurjs] \u8FDC\u7A0B\u7EC4\u4EF6 "${spec}" \u52A0\u8F7D\u7B49\u5F85\u8D85\u8FC7 ${opts.timeout} \u6BEB\u79D2\uFF08\u9002\u914D\u5C42 timeout\uFF09\u3002\u8BE5\u8D85\u65F6\u53EA\u7ED3\u675F\u672C\u6B21\u7B49\u5F85\uFF0C\u4E0D\u53D6\u6D88\u5DF2\u53D1\u51FA\u7684\u5171\u4EAB\u8BF7\u6C42\uFF1B\u53EF\u70B9\u51FB\u91CD\u8BD5\u5EFA\u7ACB\u65B0\u7684\u52A0\u8F7D\u5C1D\u8BD5\u3002`
|
|
129
|
+
));
|
|
130
|
+
}, opts.timeout);
|
|
131
|
+
})
|
|
132
|
+
]);
|
|
133
|
+
race.then(
|
|
134
|
+
(ns) => {
|
|
135
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
136
|
+
const inner = ns?.default ?? ns;
|
|
137
|
+
if (!isValidComponentType(inner)) {
|
|
138
|
+
commit({ phase: "error", error: noRenderableExportError(spec, inner) });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
commit({ phase: "ready", Comp: inner });
|
|
142
|
+
},
|
|
143
|
+
(err) => {
|
|
144
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
145
|
+
commit({ phase: "error", error: err });
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
return () => {
|
|
149
|
+
cancelled = true;
|
|
150
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
151
|
+
};
|
|
152
|
+
}, [attempt]);
|
|
153
|
+
if (state.phase === "pending") return opts.fallback ?? null;
|
|
154
|
+
if (state.phase === "error") return renderErrorNode(opts.error, state.error, retry, "load");
|
|
155
|
+
const Comp = state.Comp;
|
|
156
|
+
const merged = fwdRef === void 0 ? latest.current.passthrough : { ...latest.current.passthrough, ref: latest.current.fwdRef };
|
|
157
|
+
return createElement(Comp, merged);
|
|
158
|
+
}
|
|
159
|
+
var RemoteRenderBoundary = class extends Component {
|
|
160
|
+
state = { error: null };
|
|
161
|
+
static getDerivedStateFromError(error) {
|
|
162
|
+
return { error };
|
|
163
|
+
}
|
|
164
|
+
componentDidCatch(error, info) {
|
|
165
|
+
console.error("[fulgurjs] \u8FDC\u7A0B\u7EC4\u4EF6\u6E32\u67D3\u5F02\u5E38\uFF08remoteComponent \u5185\u7F6E\u8FB9\u754C\uFF09\uFF1A", error, info?.componentStack ?? "");
|
|
166
|
+
}
|
|
167
|
+
retry = () => {
|
|
168
|
+
this.props.onRetry();
|
|
169
|
+
};
|
|
170
|
+
render() {
|
|
171
|
+
return this.props.render(this.state.error, this.retry);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function buildRemoteComponent(spec, opts, loadRemote, beforeLoad) {
|
|
175
|
+
const displayName = cleanCompName(spec);
|
|
176
|
+
function RemoteRoot(props) {
|
|
177
|
+
const [attempt, setAttempt] = useState(0);
|
|
178
|
+
const retry = useCallback(() => setAttempt((a) => a + 1), []);
|
|
179
|
+
const { __fulgurjsRef, ...rest } = props;
|
|
180
|
+
const passthrough = rest;
|
|
181
|
+
return createElement(
|
|
182
|
+
RemoteRenderBoundary,
|
|
183
|
+
{
|
|
184
|
+
key: attempt,
|
|
185
|
+
onRetry: retry,
|
|
186
|
+
render: (boundaryError) => boundaryError !== null ? renderErrorNode(opts.error, boundaryError, retry, "render") : createElement(RemoteLoader, {
|
|
187
|
+
spec,
|
|
188
|
+
opts,
|
|
189
|
+
loadRemote,
|
|
190
|
+
beforeLoad,
|
|
191
|
+
attempt,
|
|
192
|
+
retry,
|
|
193
|
+
passthrough,
|
|
194
|
+
fwdRef: __fulgurjsRef
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
RemoteRoot.displayName = displayName;
|
|
200
|
+
const Wrapped = forwardRef(
|
|
201
|
+
(props, ref) => createElement(RemoteRoot, { ...props, __fulgurjsRef: ref })
|
|
202
|
+
);
|
|
203
|
+
Wrapped.displayName = displayName;
|
|
204
|
+
return Wrapped;
|
|
205
|
+
}
|
|
206
|
+
function createRemoteComponent(loadRemote) {
|
|
207
|
+
return function remoteComponent(spec, opts = {}) {
|
|
208
|
+
assertAdapterOptions(`remoteComponent("${spec}")`, opts);
|
|
209
|
+
return buildRemoteComponent(spec, opts, loadRemote);
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function createUseLoadRemote(loadRemote) {
|
|
213
|
+
return function useLoadRemote(spec, opts = {}) {
|
|
214
|
+
assertAdapterOptions(`useLoadRemote("${spec}")`, opts);
|
|
215
|
+
const { shareScope, retries, fallbackModule } = opts;
|
|
216
|
+
const [data, setData] = useState(void 0);
|
|
217
|
+
const [error, setError] = useState(void 0);
|
|
218
|
+
const [loading, setLoading] = useState(true);
|
|
219
|
+
const reqGen = useRef(0);
|
|
220
|
+
const optsRef = useRef(opts);
|
|
221
|
+
optsRef.current = opts;
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
const gen = ++reqGen.current;
|
|
224
|
+
let cancelled = false;
|
|
225
|
+
const isCurrent = () => gen === reqGen.current && !cancelled;
|
|
226
|
+
setData(void 0);
|
|
227
|
+
setError(void 0);
|
|
228
|
+
setLoading(true);
|
|
229
|
+
loadRemote(spec, { shareScope, retries, fallbackModule }).then(
|
|
230
|
+
(ns) => {
|
|
231
|
+
if (!isCurrent()) return;
|
|
232
|
+
setData(ns);
|
|
233
|
+
setLoading(false);
|
|
234
|
+
},
|
|
235
|
+
(err) => {
|
|
236
|
+
if (!isCurrent()) return;
|
|
237
|
+
setError(err);
|
|
238
|
+
setLoading(false);
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
return () => {
|
|
242
|
+
cancelled = true;
|
|
243
|
+
};
|
|
244
|
+
}, [spec, shareScope, retries, fallbackModule, loadRemote]);
|
|
245
|
+
const reload = useCallback(async () => {
|
|
246
|
+
const gen = ++reqGen.current;
|
|
247
|
+
setLoading(true);
|
|
248
|
+
setError(void 0);
|
|
249
|
+
const o = optsRef.current;
|
|
250
|
+
try {
|
|
251
|
+
const ns = await loadRemote(spec, { shareScope: o.shareScope, retries: o.retries, fallbackModule: o.fallbackModule });
|
|
252
|
+
if (gen === reqGen.current) {
|
|
253
|
+
setData(ns);
|
|
254
|
+
setLoading(false);
|
|
255
|
+
}
|
|
256
|
+
} catch (err) {
|
|
257
|
+
if (gen === reqGen.current) {
|
|
258
|
+
setError(err);
|
|
259
|
+
setLoading(false);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}, [spec, loadRemote]);
|
|
263
|
+
return { data, error, loading, reload };
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function createReactHostPages(options, loadRemote) {
|
|
267
|
+
const { beforeLoad, fallback, error, retries, timeout } = options;
|
|
268
|
+
assertAdapterOptions("createReactHostPages()", { retries, timeout });
|
|
269
|
+
const core = createHostPagesCore(options);
|
|
270
|
+
let cacheSession;
|
|
271
|
+
const compCache = /* @__PURE__ */ new Map();
|
|
272
|
+
const component = (spec) => {
|
|
273
|
+
const sessionKey = readSessionKey();
|
|
274
|
+
if (shouldResetSessionCache(sessionKey, cacheSession)) {
|
|
275
|
+
compCache.clear();
|
|
276
|
+
cacheSession = sessionKey;
|
|
277
|
+
}
|
|
278
|
+
let comp = compCache.get(spec);
|
|
279
|
+
if (!comp) {
|
|
280
|
+
comp = buildRemoteComponent(spec, { fallback, error, retries, timeout }, loadRemote, beforeLoad);
|
|
281
|
+
compCache.set(spec, comp);
|
|
282
|
+
}
|
|
283
|
+
return comp;
|
|
284
|
+
};
|
|
285
|
+
return { pages: core.pages, resolve: core.resolve, component };
|
|
286
|
+
}
|
|
287
|
+
export {
|
|
288
|
+
RemoteErrorBoundary,
|
|
289
|
+
createReactHostPages,
|
|
290
|
+
createRemoteComponent,
|
|
291
|
+
createUseLoadRemote
|
|
292
|
+
};
|