@fulgurjs/federation 5.2.4 → 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/CHANGELOG.md +18 -0
- package/DESIGN.md +7 -0
- package/README.en.md +86 -4
- package/README.md +125 -3
- package/dist/bridge-app-react.cjs +185 -0
- package/dist/bridge-app-react.d.cts +37 -0
- package/dist/bridge-app-react.d.ts +37 -0
- package/dist/bridge-app-react.js +115 -0
- package/dist/bridge-app-vue.cjs +111 -0
- package/dist/bridge-app-vue.d.cts +34 -0
- package/dist/bridge-app-vue.d.ts +34 -0
- package/dist/bridge-app-vue.js +51 -0
- package/dist/bridge-core-6uLJiMVa.d.ts +42 -0
- package/dist/bridge-core.cjs +224 -0
- package/dist/bridge-core.d.cts +51 -0
- package/dist/bridge-core.d.ts +51 -0
- package/dist/bridge-core.js +21 -0
- package/dist/bridge-errors.cjs +114 -0
- package/dist/bridge-errors.d.cts +36 -0
- package/dist/bridge-errors.d.ts +36 -0
- package/dist/bridge-errors.js +15 -0
- package/dist/bridge-host-react-C55RmGll.d.ts +35 -0
- package/dist/bridge-host-react.cjs +392 -0
- package/dist/bridge-host-react.d.cts +45 -0
- package/dist/bridge-host-react.d.ts +45 -0
- package/dist/bridge-host-react.js +183 -0
- package/dist/bridge-host-vue-BAxDKqGH.d.ts +32 -0
- package/dist/bridge-host-vue.cjs +426 -0
- package/dist/bridge-host-vue.d.cts +39 -0
- package/dist/bridge-host-vue.d.ts +39 -0
- package/dist/bridge-host-vue.js +211 -0
- package/dist/bridge-react.d.ts +12 -0
- package/dist/bridge-react.js +3 -0
- package/dist/bridge-vue.d.ts +8 -0
- package/dist/bridge-vue.js +3 -0
- package/dist/bridge.d.ts +16 -0
- package/dist/bridge.js +5 -0
- package/dist/chunk-E55P4TIY.js +76 -0
- package/dist/chunk-EH34L46P.js +207 -0
- package/dist/chunk-FVYGSLAQ.js +179 -0
- package/dist/chunk-LWJBHUJQ.js +114 -0
- package/dist/chunk-OF2JCXTZ.js +15 -0
- package/dist/chunk-TSYLDY6H.js +64 -0
- package/dist/chunk-UJFCPQGN.js +1210 -0
- package/dist/context.js +8 -64
- package/dist/index.cjs +84 -8
- package/dist/index.js +84 -8
- package/dist/react.d.ts +44 -2
- package/dist/react.js +2 -0
- package/dist/runtime-entry.d.ts +41 -2
- package/dist/runtime-entry.js +1 -1
- package/dist/runtime.js +1 -1
- package/dist/vue.js +2 -0
- package/docs/webpack-mf-/345/257/271/347/205/247/344/270/216/347/274/272/345/217/243.md +1 -1
- package/docs//350/277/201/347/247/273/346/214/207/345/215/227.md +1 -1
- package/examples/README.en.md +20 -0
- package/examples/README.md +20 -0
- package/examples/bridge/README.md +36 -0
- package/examples/bridge/react-host/README.md +23 -0
- package/examples/bridge/react-host/fulgurjs.config.ts +20 -0
- package/examples/bridge/react-host/index.html +11 -0
- package/examples/bridge/react-host/package-lock.json +2457 -0
- package/examples/bridge/react-host/package.json +24 -0
- package/examples/bridge/react-host/src/env.d.ts +7 -0
- package/examples/bridge/react-host/src/host-session.ts +26 -0
- package/examples/bridge/react-host/src/main.tsx +62 -0
- package/examples/bridge/react-host/tsconfig.json +13 -0
- package/examples/bridge/react-host/vite.config.ts +9 -0
- package/examples/bridge/react-remote/README.md +20 -0
- package/examples/bridge/react-remote/fulgurjs.config.ts +17 -0
- package/examples/bridge/react-remote/index.html +11 -0
- package/examples/bridge/react-remote/package-lock.json +2382 -0
- package/examples/bridge/react-remote/package.json +24 -0
- package/examples/bridge/react-remote/src/bridge.tsx +66 -0
- package/examples/bridge/react-remote/src/env.d.ts +7 -0
- package/examples/bridge/react-remote/src/main.tsx +7 -0
- package/examples/bridge/react-remote/tsconfig.json +13 -0
- package/examples/bridge/react-remote/vite.config.ts +9 -0
- package/examples/bridge/vue-host/README.md +24 -0
- package/examples/bridge/vue-host/fulgurjs.config.ts +22 -0
- package/examples/bridge/vue-host/index.html +11 -0
- package/examples/bridge/vue-host/package-lock.json +1885 -0
- package/examples/bridge/vue-host/package.json +24 -0
- package/examples/bridge/vue-host/src/App.vue +70 -0
- package/examples/bridge/vue-host/src/env.d.ts +7 -0
- package/examples/bridge/vue-host/src/host-session.ts +29 -0
- package/examples/bridge/vue-host/src/main.ts +4 -0
- package/examples/bridge/vue-host/tsconfig.json +13 -0
- package/examples/bridge/vue-host/vite.config.ts +9 -0
- package/examples/bridge/vue-remote/README.md +19 -0
- package/examples/bridge/vue-remote/fulgurjs.config.ts +16 -0
- package/examples/bridge/vue-remote/index.html +11 -0
- package/examples/bridge/vue-remote/package-lock.json +1856 -0
- package/examples/bridge/vue-remote/package.json +21 -0
- package/examples/bridge/vue-remote/src/App.vue +12 -0
- package/examples/bridge/vue-remote/src/bridge.ts +57 -0
- package/examples/bridge/vue-remote/src/env.d.ts +7 -0
- package/examples/bridge/vue-remote/src/main.ts +15 -0
- package/examples/bridge/vue-remote/tsconfig.json +13 -0
- package/examples/bridge/vue-remote/vite.config.ts +9 -0
- package/examples/react/host/package-lock.json +3 -3
- package/examples/react/host/package.json +1 -1
- package/examples/react/remote/package-lock.json +3 -3
- package/examples/react/remote/package.json +1 -1
- package/examples/react/remote/vite.config.ts +3 -2
- package/examples/vue/README.md +1 -1
- package/examples/vue/host/package-lock.json +3 -3
- package/examples/vue/host/package.json +1 -1
- package/examples/vue/remote/README.md +1 -1
- package/examples/vue/remote/package-lock.json +3 -3
- package/examples/vue/remote/package.json +1 -1
- package/examples/vue/remote/vite.config.ts +3 -2
- package/package.json +43 -2
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/bridge-host-react.ts
|
|
21
|
+
var bridge_host_react_exports = {};
|
|
22
|
+
__export(bridge_host_react_exports, {
|
|
23
|
+
createReactBridgeAppWithLoader: () => createReactBridgeAppWithLoader
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(bridge_host_react_exports);
|
|
26
|
+
var import_react = require("react");
|
|
27
|
+
|
|
28
|
+
// src/runtime/errors.ts
|
|
29
|
+
var FgError = class extends Error {
|
|
30
|
+
code;
|
|
31
|
+
details;
|
|
32
|
+
constructor(code, message, details, options) {
|
|
33
|
+
super(`[fulgurjs:${code}] ${message}`, options);
|
|
34
|
+
this.name = "FgError";
|
|
35
|
+
this.code = code;
|
|
36
|
+
this.details = details;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/context.ts
|
|
41
|
+
var ContextErrorCodes = {
|
|
42
|
+
/** context 必需字段缺失(三段式,修法指向宿主桥) */
|
|
43
|
+
CONTEXT_MISSING_KEY: "CC-001",
|
|
44
|
+
/** 运行时单例不可用(独立直开远程页,修法=经宿主联邦加载) */
|
|
45
|
+
CONTEXT_NO_RUNTIME: "CC-002"
|
|
46
|
+
};
|
|
47
|
+
var APP_CONTEXT_STORAGE_KEY = "__FULGURJS_APP_CONFIG__";
|
|
48
|
+
function requireRuntime() {
|
|
49
|
+
const rt = globalThis.__FULGURJS_RUNTIME__;
|
|
50
|
+
if (!rt || typeof rt.loadRemote !== "function") {
|
|
51
|
+
throw new FgError(
|
|
52
|
+
ContextErrorCodes.CONTEXT_NO_RUNTIME,
|
|
53
|
+
"\u73B0\u8C61\uFF1A\u5F53\u524D\u9875\u9762\u627E\u4E0D\u5230 fulgurjs \u8FD0\u884C\u65F6\u5355\u4F8B\uFF08globalThis.__FULGURJS_RUNTIME__ \u672A\u5B9A\u4E49\uFF09\u3002\n \u6839\u56E0: \u5F53\u524D\u9875\u9762\u6CA1\u6709\u7ECF\u5BBF\u4E3B\u7684\u8054\u90A6\u8FD0\u884C\u65F6\u52A0\u8F7D\uFF08\u72EC\u7ACB\u76F4\u5F00\u8FDC\u7A0B\u9875\uFF0C\u6216\u5BBF\u4E3B\u6865\u665A\u4E8E\u672C\u8C03\u7528\u6267\u884C\uFF09\u3002\n \u4FEE\u6CD5: \u2460 \u4ECE\u5BBF\u4E3B\u5E94\u7528\u7684\u8054\u90A6\u8DEF\u7531\u6253\u5F00\u672C\u9875\u9762\uFF08\u5BBF\u4E3B\u6865\u4F1A\u5148\u52A0\u8F7D\u8FD0\u884C\u65F6\u5E76\u63D0\u4F9B context\uFF09\uFF1B\n \u2461 \u82E5\u4F60\u662F\u5BBF\u4E3B\u6865\u4F5C\u8005\uFF1A\u628A provideAppContext \u653E\u5728\u6865\u521D\u59CB\u5316\u5C3E\u90E8\uFF08\u65F6\u5E8F\u5951\u7EA6 bridge \u2192 \u8FDC\u7A0B setup \u2192 \u9875\u9762\u6A21\u5757\uFF09\u3002",
|
|
54
|
+
{ runtime: false }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return rt;
|
|
58
|
+
}
|
|
59
|
+
function provideAppContext(config) {
|
|
60
|
+
requireRuntime();
|
|
61
|
+
const g = globalThis;
|
|
62
|
+
g[APP_CONTEXT_STORAGE_KEY] = { ...g[APP_CONTEXT_STORAGE_KEY] ?? {}, ...config };
|
|
63
|
+
}
|
|
64
|
+
function clearAppContext() {
|
|
65
|
+
const g = globalThis;
|
|
66
|
+
delete g[APP_CONTEXT_STORAGE_KEY];
|
|
67
|
+
try {
|
|
68
|
+
g.__FULGURJS_RUNTIME__?.clearSessionState?.();
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/bridge-errors.ts
|
|
74
|
+
var BridgeErrorCodes = {
|
|
75
|
+
/** 桥接契约非法(./bridge 默认导出缺 mount/unmount 或不是函数) */
|
|
76
|
+
BRIDGE_INVALID_CONTRACT: "MFU-015",
|
|
77
|
+
/** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
|
|
78
|
+
BRIDGE_LIFECYCLE_FAILED: "MFU-016",
|
|
79
|
+
/** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
|
|
80
|
+
BRIDGE_SESSION_MISMATCH: "MFU-017"
|
|
81
|
+
};
|
|
82
|
+
function invalidBridgeContractError(spec, mod) {
|
|
83
|
+
const actual = mod === null ? "null" : typeof mod;
|
|
84
|
+
const missing = [];
|
|
85
|
+
if (typeof mod?.mount !== "function") missing.push("mount");
|
|
86
|
+
if (typeof mod?.unmount !== "function") missing.push("unmount");
|
|
87
|
+
return new FgError(
|
|
88
|
+
BridgeErrorCodes.BRIDGE_INVALID_CONTRACT,
|
|
89
|
+
`\u73B0\u8C61\uFF1A\u8FDC\u7A0B\u6A21\u5757 "${spec}" \u7684\u9ED8\u8BA4\u5BFC\u51FA\u4E0D\u662F\u5408\u6CD5\u7684\u6865\u63A5\u5951\u7EA6\uFF08BridgeApp\uFF09\u3002
|
|
90
|
+
\u6839\u56E0: \u5BFC\u51FA\u503C\u4E3A ${actual}` + (missing.length ? `\uFF0C\u7F3A\u5C11\u51FD\u6570\u7C7B\u578B\u7684 ${missing.map((m) => `"${m}"`).join(" / ")} \u5BFC\u51FA\u3002` : "\u3002") + `
|
|
91
|
+
\u4FEE\u6CD5: \u8FDC\u7A0B\u4FA7\u7528 defineBridgeApp(...) \u521B\u5EFA\u5951\u7EA6\u5E76\u4F5C\u4E3A\u6A21\u5757\u9ED8\u8BA4\u5BFC\u51FA\u2014\u2014
|
|
92
|
+
Vue \u5B50\u5E94\u7528: import { defineBridgeApp } from '@fulgurjs/federation/runtime'\uFF1B
|
|
93
|
+
React \u5B50\u5E94\u7528: import { defineBridgeApp } from '@fulgurjs/federation/react'\u3002
|
|
94
|
+
\u5951\u7EA6\u5FC5\u987B\u5305\u542B mount(el, props?) \u4E0E unmount(el) \u4E24\u4E2A\u51FD\u6570\u3002`,
|
|
95
|
+
{ spec, missing, actual }
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function bridgeLifecycleError(phase, spec, cause, extra) {
|
|
99
|
+
const raw = cause instanceof Error ? cause.message : String(cause ?? "unknown");
|
|
100
|
+
const fixes = {
|
|
101
|
+
getContext: "\u4FEE\u6CD5: \u2460 getContext \u5FC5\u987B\u662F\u540C\u6B65\u3001\u65E0\u526F\u4F5C\u7528\u7684 getter\uFF08\u4E0D\u5F97\u8FD4\u56DE Promise/thenable\uFF09\uFF1B\n \u2461 \u8FD4\u56DE\u503C\u987B\u662F\u5305\u542B\u672C\u6B21\u4F1A\u8BDD\u5FEB\u7167\u7684\u5BF9\u8C61\uFF08\u53D7\u63A7 sessionKey \u65F6\u987B\u542B\u76F8\u540C sessionKey\uFF09\uFF1B\n \u2462 \u5F02\u6B65\u83B7\u53D6\u7528\u6237\u8D44\u6599\u8BF7\u5728\u5BBF\u4E3B\u5B8C\u6210\u540E\u518D\u8BA9\u6865\u63A5\u7EC4\u4EF6\u8FDB\u5165\u53EF\u6302\u8F7D\u72B6\u6001\u3002",
|
|
102
|
+
mount: "\u4FEE\u6CD5: \u2460 \u67E5\u770B\u6839\u56E0\u4E2D\u5B50\u5E94\u7528 mount \u7684\u539F\u59CB\u9519\u8BEF\uFF08\u5DE5\u5382\u629B\u9519\u6216\u9996\u6B21\u6E32\u67D3\u5931\u8D25\uFF09\uFF1B\n \u2461 \u540C\u4E00\u5BB9\u5668\u672A\u5378\u8F7D\u524D\u91CD\u590D mount \u662F\u5951\u7EA6\u8FDD\u4F8B\u2014\u2014\u5148 unmount \u518D mount\uFF1B\n \u2462 \u4FEE\u590D\u540E\u70B9\u51FB\u300C\u91CD\u8BD5\u52A0\u8F7D\u300D\u5728\u540C\u9875\u91CD\u5EFA\u6302\u8F7D\u3002",
|
|
103
|
+
unmount: "\u4FEE\u6CD5: \u2460 \u6839\u56E0\u6765\u81EA\u5B50\u5E94\u7528 unmount \u5B9E\u73B0\uFF08app.unmount/root.unmount \u629B\u9519\uFF09\uFF1B\n \u2461 \u8BE5\u5BB9\u5668\u6E05\u7406\u72B6\u6001\u4E0D\u786E\u5B9A\u2014\u2014\u4E0D\u8981\u76F4\u63A5\u5728\u540C\u4E00 el \u4E0A\u542F\u52A8\u65B0\u5B9E\u4F8B\uFF1B\n \u2462 \u5237\u65B0\u9875\u9762\u53EF\u5F7B\u5E95\u6E05\u7406\uFF0C\u6B8B\u7559\u8D44\u6E90\u8BF7\u5982\u5B9E\u6392\u67E5\uFF08\u4E8B\u4EF6\u8BA2\u9605/\u5B9A\u65F6\u5668/\u5168\u5C40\u526F\u4F5C\u7528\uFF09\u3002"
|
|
104
|
+
};
|
|
105
|
+
return new FgError(
|
|
106
|
+
BridgeErrorCodes.BRIDGE_LIFECYCLE_FAILED,
|
|
107
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 ${phase} \u9636\u6BB5\u5931\u8D25\u3002
|
|
108
|
+
\u6839\u56E0: ${raw}
|
|
109
|
+
` + fixes[phase],
|
|
110
|
+
{ phase, spec, cause: String(raw), ...extra },
|
|
111
|
+
{ cause }
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
function bridgeSessionMismatchError(spec, controlled, actual, extra) {
|
|
115
|
+
const actualText = actual === void 0 ? "undefined\uFF08\u9875\u9762\u5C1A\u65E0 AppContext\uFF09" : typeof actual === "string" ? `"${actual}"` : String(actual);
|
|
116
|
+
return new FgError(
|
|
117
|
+
BridgeErrorCodes.BRIDGE_SESSION_MISMATCH,
|
|
118
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684\u53D7\u63A7 sessionKey\uFF08"${controlled}"\uFF09\u4E0E\u5F53\u524D\u4F1A\u8BDD\u4E0D\u4E00\u81F4\uFF08\u5F53\u524D ${actualText}\uFF09\u3002
|
|
119
|
+
\u6839\u56E0: AppContext \u662F\u9875\u9762\u7EA7\u5355\u4F8B\uFF0C\u540C\u9875\u6240\u6709\u6865\u63A5\u5B9E\u4F8B\u5FC5\u987B\u4F7F\u7528\u540C\u4E00\u767B\u5F55\u4EE3\u6B21\uFF1B
|
|
120
|
+
getContext \u5FEB\u7167\u7684 sessionKey\uFF08\u6216\u73B0\u6709 AppContext \u7684 sessionKey\uFF09\u4E0E\u53D7\u63A7\u503C\u4E0D\u540C\u3002
|
|
121
|
+
\u4FEE\u6CD5: \u2460 \u6362\u8D26\u53F7\u65F6\u5148\u8BA9\u53D7\u63A7 prop \u7F6E null \u5E76\u7B49\u5F85\u5378\u8F7D\uFF0C\u518D clearAppContext() \u6E05\u65E7\u8D26\u53F7\u3001
|
|
122
|
+
\u51C6\u5907\u65B0\u8D26\u53F7\u5B8C\u6574\u5FEB\u7167\u540E\u66F4\u65B0\u53D7\u63A7 prop\uFF1B
|
|
123
|
+
\u2461 \u65E0 getContext \u65F6\uFF0C\u5148\u7531\u5BBF\u4E3B\u6865 provideAppContext({...\u65B0\u4F1A\u8BDD, sessionKey}) \u518D\u6302\u8F7D\uFF1B
|
|
124
|
+
\u2462 \u540C\u9875\u4E0D\u8981\u540C\u65F6\u6302\u8F7D\u4E0D\u540C\u8D26\u53F7\u7684\u6865\u63A5\u5B9E\u4F8B\uFF08\u9875\u9762\u7EA7\u5355\u4F1A\u8BDD\u7EA6\u675F\uFF09\u3002`,
|
|
125
|
+
{ spec, controlled, actual: String(actual), ...extra }
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
function invalidSessionKeyError(spec, value) {
|
|
129
|
+
return new FgError(
|
|
130
|
+
BridgeErrorCodes.BRIDGE_SESSION_MISMATCH,
|
|
131
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u6536\u5230\u975E\u6CD5\u7684\u53D7\u63A7 sessionKey\uFF1A${typeof value === "string" ? '\u7A7A\u5B57\u7B26\u4E32 ""' : `${typeof value}\uFF08${String(value)}\uFF09`}\u3002
|
|
132
|
+
\u6839\u56E0: sessionKey \u53EA\u63A5\u53D7 undefined\uFF08\u4E0D\u542F\u7528\u53D7\u63A7\u4F1A\u8BDD\uFF09\u3001null\uFF08\u767B\u51FA\u6001\uFF09\u6216\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u767B\u5F55\u4EE3\u6B21 ID\uFF09\u3002
|
|
133
|
+
\u4FEE\u6CD5: \u767B\u51FA\u4F20 null \u800C\u4E0D\u662F\u7A7A\u5B57\u7B26\u4E32\uFF1B\u767B\u5F55\u4EE3\u6B21\u7528\u975E\u7A7A\u5B57\u7B26\u4E32\uFF1B\u4E0D\u8981\u4F20\u6570\u5B57/\u5E03\u5C14\u7B49\u5176\u4ED6\u7C7B\u578B\u3002`,
|
|
134
|
+
{ spec, value: String(value) }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/bridge-core.ts
|
|
139
|
+
function assertBridgeContract(spec, mod) {
|
|
140
|
+
const contract = mod?.default ?? mod;
|
|
141
|
+
if (contract === null || typeof contract !== "object" || typeof contract.mount !== "function" || typeof contract.unmount !== "function") {
|
|
142
|
+
throw invalidBridgeContractError(spec, mod);
|
|
143
|
+
}
|
|
144
|
+
return contract;
|
|
145
|
+
}
|
|
146
|
+
var activeBridgeSessions = /* @__PURE__ */ new Map();
|
|
147
|
+
function acquireBridgeSession(spec, sessionKey) {
|
|
148
|
+
for (const [active] of activeBridgeSessions) {
|
|
149
|
+
if (active !== sessionKey && (activeBridgeSessions.get(active) ?? 0) > 0) {
|
|
150
|
+
throw bridgeSessionMismatchError(spec, sessionKey, active, { reason: "active-instance" });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
activeBridgeSessions.set(sessionKey, (activeBridgeSessions.get(sessionKey) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
function releaseBridgeSession(sessionKey) {
|
|
156
|
+
const n = activeBridgeSessions.get(sessionKey) ?? 0;
|
|
157
|
+
if (n <= 1) activeBridgeSessions.delete(sessionKey);
|
|
158
|
+
else activeBridgeSessions.set(sessionKey, n - 1);
|
|
159
|
+
}
|
|
160
|
+
function resolveBridgeContext(spec, controlled, getContext) {
|
|
161
|
+
if (getContext) {
|
|
162
|
+
let snapshot;
|
|
163
|
+
try {
|
|
164
|
+
snapshot = getContext();
|
|
165
|
+
} catch (e) {
|
|
166
|
+
throw bridgeLifecycleError("getContext", spec, e);
|
|
167
|
+
}
|
|
168
|
+
if (snapshot === null || typeof snapshot !== "object" || typeof snapshot.then === "function") {
|
|
169
|
+
throw bridgeLifecycleError(
|
|
170
|
+
"getContext",
|
|
171
|
+
spec,
|
|
172
|
+
`getContext \u8FD4\u56DE\u4E86${typeof snapshot.then === "function" ? " Promise/thenable" : "\u975E\u5BF9\u8C61\u503C\uFF08" + typeof snapshot + "\uFF09"}\uFF1B\u5B83\u5FC5\u987B\u662F\u540C\u6B65\u8FD4\u56DE\u5FEB\u7167\u5BF9\u8C61\u7684\u7EAF getter\u3002`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (controlled !== void 0 && snapshot.sessionKey !== controlled) {
|
|
176
|
+
throw bridgeSessionMismatchError(spec, controlled, snapshot.sessionKey, { reason: "getter-snapshot" });
|
|
177
|
+
}
|
|
178
|
+
const current = currentSessionKey();
|
|
179
|
+
if (snapshot.sessionKey !== void 0 && snapshot.sessionKey !== current) {
|
|
180
|
+
clearAppContext();
|
|
181
|
+
}
|
|
182
|
+
return { provide: snapshot };
|
|
183
|
+
}
|
|
184
|
+
if (controlled !== void 0) {
|
|
185
|
+
const current = currentSessionKey();
|
|
186
|
+
if (current !== controlled) {
|
|
187
|
+
throw bridgeSessionMismatchError(spec, controlled, current, { reason: "app-context" });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {};
|
|
191
|
+
}
|
|
192
|
+
function currentSessionKey() {
|
|
193
|
+
const sk = (globalThis.__FULGURJS_APP_CONFIG__ ?? {}).sessionKey;
|
|
194
|
+
return typeof sk === "string" && sk !== "" ? sk : void 0;
|
|
195
|
+
}
|
|
196
|
+
function assertControlledSessionKey(spec, value) {
|
|
197
|
+
if (value === void 0 || value === null) return;
|
|
198
|
+
if (typeof value === "string" && value !== "") return;
|
|
199
|
+
throw invalidSessionKeyError(spec, value);
|
|
200
|
+
}
|
|
201
|
+
function assertBridgeOptions(source, opts) {
|
|
202
|
+
if (opts.retries !== void 0 && (typeof opts.retries !== "number" || !Number.isInteger(opts.retries) || opts.retries < 0 || opts.retries > 10)) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`[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`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (opts.timeout !== void 0 && (typeof opts.timeout !== "number" || !Number.isFinite(opts.timeout) || opts.timeout <= 0)) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`[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`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function withBridgeTimeout(p, ms, spec) {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
const timer = setTimeout(() => {
|
|
216
|
+
reject(new Error(
|
|
217
|
+
`[fulgurjs] \u6865\u63A5\u5E94\u7528 "${spec}" \u52A0\u8F7D\u7B49\u5F85\u8D85\u8FC7 ${ms} \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\u300C\u91CD\u8BD5\u52A0\u8F7D\u300D\u5EFA\u7ACB\u65B0\u7684\u5C1D\u8BD5\u3002`
|
|
218
|
+
));
|
|
219
|
+
}, ms);
|
|
220
|
+
p.then(
|
|
221
|
+
(v) => {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
resolve(v);
|
|
224
|
+
},
|
|
225
|
+
(e) => {
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
reject(e);
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/bridge-host-react.ts
|
|
234
|
+
var ERROR_STYLE = {
|
|
235
|
+
padding: "16px",
|
|
236
|
+
border: "1px solid #fde2e2",
|
|
237
|
+
borderRadius: "4px",
|
|
238
|
+
background: "#fef0f0",
|
|
239
|
+
color: "#c45656",
|
|
240
|
+
fontSize: "13px",
|
|
241
|
+
lineHeight: "1.6",
|
|
242
|
+
boxSizing: "border-box"
|
|
243
|
+
};
|
|
244
|
+
var MESSAGE_STYLE = { margin: "0 0 8px", wordBreak: "break-all" };
|
|
245
|
+
var BUTTON_STYLE = {
|
|
246
|
+
padding: "4px 14px",
|
|
247
|
+
marginRight: "8px",
|
|
248
|
+
border: "1px solid #c45656",
|
|
249
|
+
borderRadius: "4px",
|
|
250
|
+
background: "#c45656",
|
|
251
|
+
color: "#fff",
|
|
252
|
+
fontSize: "13px",
|
|
253
|
+
cursor: "pointer"
|
|
254
|
+
};
|
|
255
|
+
var SECONDARY_BUTTON_STYLE = {
|
|
256
|
+
padding: "4px 14px",
|
|
257
|
+
border: "1px solid #c45656",
|
|
258
|
+
borderRadius: "4px",
|
|
259
|
+
background: "#fff",
|
|
260
|
+
color: "#c45656",
|
|
261
|
+
fontSize: "13px",
|
|
262
|
+
cursor: "pointer"
|
|
263
|
+
};
|
|
264
|
+
function DefaultBridgeErrorView({ error, retry }) {
|
|
265
|
+
const err = error;
|
|
266
|
+
const code = err?.code ?? "UNKNOWN";
|
|
267
|
+
const message = err?.message ?? String(error ?? "unknown error");
|
|
268
|
+
return (0, import_react.createElement)("div", { style: ERROR_STYLE, "data-fulgurjs-error": code }, [
|
|
269
|
+
(0, import_react.createElement)("p", { key: "t", style: { margin: "0 0 4px", fontWeight: 600 } }, `\u6865\u63A5\u5E94\u7528\u52A0\u8F7D\u5931\u8D25\uFF08\u9519\u8BEF\u7801 ${code}\uFF09`),
|
|
270
|
+
(0, import_react.createElement)("p", { key: "m", style: MESSAGE_STYLE }, message),
|
|
271
|
+
(0, import_react.createElement)(
|
|
272
|
+
"p",
|
|
273
|
+
{ key: "n", style: { margin: "0 0 10px" } },
|
|
274
|
+
"\u300C\u91CD\u8BD5\u52A0\u8F7D\u300D\u5728\u5F53\u524D\u9875\u9762\u91CD\u5EFA\u6302\u8F7D\uFF1B\u82E5\u6D4F\u89C8\u5668\u5DF2\u7F13\u5B58\u5931\u8D25\u7684\u6A21\u5757\uFF0C\u8BF7\u7528\u300C\u5237\u65B0\u9875\u9762\u91CD\u8BD5\u300D\u2014\u2014\u5B83\u4F1A\u6574\u9875\u5237\u65B0\uFF08\u4FDD\u7559\u5F53\u524D\u5730\u5740\uFF09\uFF0C\u672A\u4FDD\u5B58\u7684\u9875\u9762\u72B6\u6001\u4F1A\u4E22\u5931\u3002"
|
|
275
|
+
),
|
|
276
|
+
(0, import_react.createElement)("button", { key: "r", type: "button", style: BUTTON_STYLE, "data-fulgurjs-retry": "", onClick: retry }, "\u91CD\u8BD5\u52A0\u8F7D"),
|
|
277
|
+
(0, import_react.createElement)("button", {
|
|
278
|
+
key: "rl",
|
|
279
|
+
type: "button",
|
|
280
|
+
style: SECONDARY_BUTTON_STYLE,
|
|
281
|
+
"data-fulgurjs-reload": "",
|
|
282
|
+
onClick: () => window.location.reload()
|
|
283
|
+
}, "\u5237\u65B0\u9875\u9762\u91CD\u8BD5")
|
|
284
|
+
]);
|
|
285
|
+
}
|
|
286
|
+
function renderBridgeErrorNode(errorOpt, error, retry) {
|
|
287
|
+
if (typeof errorOpt === "function") return errorOpt(error, retry);
|
|
288
|
+
if (errorOpt !== void 0) return errorOpt;
|
|
289
|
+
return (0, import_react.createElement)(DefaultBridgeErrorView, { error, retry });
|
|
290
|
+
}
|
|
291
|
+
function createReactBridgeAppWithLoader(loadRemote) {
|
|
292
|
+
return function createReactBridgeApp(spec, options = {}) {
|
|
293
|
+
assertBridgeOptions(`createReactBridgeApp("${spec}")`, options);
|
|
294
|
+
const displayName = "FulgurjsBridge_" + spec.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
295
|
+
function BridgeHost(props) {
|
|
296
|
+
const [status, setStatus] = (0, import_react.useState)("idle");
|
|
297
|
+
const [error, setError] = (0, import_react.useState)(void 0);
|
|
298
|
+
const [attempt, setAttempt] = (0, import_react.useState)(0);
|
|
299
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
300
|
+
const generationRef = (0, import_react.useRef)(0);
|
|
301
|
+
const contractRef = (0, import_react.useRef)(void 0);
|
|
302
|
+
const acquiredRef = (0, import_react.useRef)(void 0);
|
|
303
|
+
const blockedRef = (0, import_react.useRef)(false);
|
|
304
|
+
const latest = (0, import_react.useRef)(props);
|
|
305
|
+
latest.current = props;
|
|
306
|
+
(0, import_react.useEffect)(() => {
|
|
307
|
+
if (blockedRef.current) return;
|
|
308
|
+
const myGen = ++generationRef.current;
|
|
309
|
+
const el = containerRef.current;
|
|
310
|
+
const release = () => {
|
|
311
|
+
if (acquiredRef.current !== void 0) {
|
|
312
|
+
releaseBridgeSession(acquiredRef.current);
|
|
313
|
+
acquiredRef.current = void 0;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const run = async () => {
|
|
317
|
+
if (!el) return;
|
|
318
|
+
const sk = latest.current.sessionKey;
|
|
319
|
+
setStatus("pending");
|
|
320
|
+
setError(void 0);
|
|
321
|
+
if (sk === null) {
|
|
322
|
+
setStatus("idle");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
assertControlledSessionKey(spec, sk);
|
|
327
|
+
const controlled = typeof sk === "string" ? sk : void 0;
|
|
328
|
+
if (controlled !== void 0 && controlled !== acquiredRef.current) {
|
|
329
|
+
release();
|
|
330
|
+
acquireBridgeSession(spec, controlled);
|
|
331
|
+
acquiredRef.current = controlled;
|
|
332
|
+
}
|
|
333
|
+
const resolution = resolveBridgeContext(spec, controlled, options.getContext);
|
|
334
|
+
if (resolution.provide) provideAppContext(resolution.provide);
|
|
335
|
+
const load = loadRemote(spec, { retries: options.retries });
|
|
336
|
+
const mod = options.timeout !== void 0 ? await withBridgeTimeout(load, options.timeout, spec) : await load;
|
|
337
|
+
if (generationRef.current !== myGen) return;
|
|
338
|
+
const contract = assertBridgeContract(spec, mod);
|
|
339
|
+
contractRef.current = contract;
|
|
340
|
+
await contract.mount(el, { ...latest.current.appProps ?? {} });
|
|
341
|
+
if (generationRef.current !== myGen) return;
|
|
342
|
+
setStatus("ready");
|
|
343
|
+
} catch (e) {
|
|
344
|
+
if (generationRef.current !== myGen) return;
|
|
345
|
+
setError(e);
|
|
346
|
+
setStatus("error");
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
void run();
|
|
350
|
+
return () => {
|
|
351
|
+
generationRef.current++;
|
|
352
|
+
release();
|
|
353
|
+
const c = contractRef.current;
|
|
354
|
+
contractRef.current = void 0;
|
|
355
|
+
if (c && el) {
|
|
356
|
+
try {
|
|
357
|
+
c.unmount(el);
|
|
358
|
+
} catch (e) {
|
|
359
|
+
const err = bridgeLifecycleError("unmount", spec, e instanceof Error ? e : String(e));
|
|
360
|
+
console.error("[fulgurjs] \u6865\u63A5\u5E94\u7528\u5378\u8F7D\u5931\u8D25\uFF1A", err);
|
|
361
|
+
blockedRef.current = true;
|
|
362
|
+
setError(err);
|
|
363
|
+
setStatus("error");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}, [props.sessionKey, attempt]);
|
|
368
|
+
const retry = () => setAttempt((a) => a + 1);
|
|
369
|
+
const children = [];
|
|
370
|
+
if (status === "pending" && options.fallback !== void 0) {
|
|
371
|
+
children.push((0, import_react.createElement)(import_react.Fragment, { key: "loading" }, options.fallback));
|
|
372
|
+
}
|
|
373
|
+
if (status === "error") {
|
|
374
|
+
children.push((0, import_react.createElement)(import_react.Fragment, { key: "error" }, renderBridgeErrorNode(options.error, error, retry)));
|
|
375
|
+
}
|
|
376
|
+
children.push((0, import_react.createElement)("div", {
|
|
377
|
+
key: "bridge-root",
|
|
378
|
+
ref: containerRef,
|
|
379
|
+
"data-fulgurjs-bridge-root": spec,
|
|
380
|
+
"data-fulgurjs-bridge-status": status
|
|
381
|
+
}));
|
|
382
|
+
return (0, import_react.createElement)(import_react.Fragment, null, children);
|
|
383
|
+
}
|
|
384
|
+
;
|
|
385
|
+
BridgeHost.displayName = displayName;
|
|
386
|
+
return BridgeHost;
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
390
|
+
0 && (module.exports = {
|
|
391
|
+
createReactBridgeAppWithLoader
|
|
392
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import { AppContext } from './context.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React 宿主桥接工厂(/bridge、/bridge/react 导出 createReactBridgeApp;任务书 §3.4)。
|
|
6
|
+
*
|
|
7
|
+
* 定位:接收注入的 loadRemote(不静态引用运行时内核,dist 壳绑定),返回
|
|
8
|
+
* `ComponentType<{ appProps: P; sessionKey?: string | null }>`,负责:
|
|
9
|
+
* - DOM 所有权(§4.5):只创建并保持稳定的空挂载 el(fragment 的固定子节点);
|
|
10
|
+
* pending/error 占位是它的兄弟节点,宿主重渲染不 patch 子应用 root 内部;
|
|
11
|
+
* - 会话代次(§4.3):受控 sessionKey 驱动 挂载/换会话/登出;同会话重渲染不重挂;
|
|
12
|
+
* - StrictMode 安全(§4.5):双 effect 走 cleanup(作废+卸载)→setup(新代次) 顺序,
|
|
13
|
+
* 顺序 unmount/mount 不产生双实例或残留;
|
|
14
|
+
* - unmount 抛错(BN09):报告 MFU-016(phase: unmount)并封锁该容器的新挂载。
|
|
15
|
+
*
|
|
16
|
+
* appProps 是挂载时浅拷贝快照(顶层替换不追踪,重挂由 key 重建);sessionKey
|
|
17
|
+
* 是桥接控制参数,不混入业务 props。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
type LoadRemoteFn = (spec: string, opts?: {
|
|
21
|
+
retries?: number;
|
|
22
|
+
}) => Promise<any>;
|
|
23
|
+
/** 渲染函数形态的错误占位:(error, retry) => ReactNode */
|
|
24
|
+
type BridgeErrorFallback = (error: unknown, retry: () => void) => ReactNode;
|
|
25
|
+
interface ReactBridgeAppOptions {
|
|
26
|
+
/** 加载 pending 占位(默认 null) */
|
|
27
|
+
fallback?: ReactNode;
|
|
28
|
+
/** 加载/挂载失败占位;默认内置中文诊断 + 「重试加载 / 刷新页面重试」 */
|
|
29
|
+
error?: ReactNode | BridgeErrorFallback;
|
|
30
|
+
/** 透传现行 loadRemote 的重试选项(0-10 整数),不另叠自动重试 */
|
|
31
|
+
retries?: number;
|
|
32
|
+
/** 本次桥接加载等待上限 ms;不取消已发出的共享请求,迟到结果一律丢弃 */
|
|
33
|
+
timeout?: number;
|
|
34
|
+
/** 无副作用的同步 getter:首次、重试及换会话的实际加载前返回本次所需上下文快照 */
|
|
35
|
+
getContext?: () => Partial<AppContext> & Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 构建 React 宿主桥接工厂(dist 壳与 dev 门面绑定各自运行时的 loadRemote 后导出)。
|
|
39
|
+
*/
|
|
40
|
+
declare function createReactBridgeAppWithLoader(loadRemote: LoadRemoteFn): <P = Record<string, unknown>>(spec: string, options?: ReactBridgeAppOptions) => ComponentType<{
|
|
41
|
+
appProps?: P;
|
|
42
|
+
sessionKey?: string | null;
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
export { type BridgeErrorFallback, type ReactBridgeAppOptions, createReactBridgeAppWithLoader };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import { AppContext } from './context.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React 宿主桥接工厂(/bridge、/bridge/react 导出 createReactBridgeApp;任务书 §3.4)。
|
|
6
|
+
*
|
|
7
|
+
* 定位:接收注入的 loadRemote(不静态引用运行时内核,dist 壳绑定),返回
|
|
8
|
+
* `ComponentType<{ appProps: P; sessionKey?: string | null }>`,负责:
|
|
9
|
+
* - DOM 所有权(§4.5):只创建并保持稳定的空挂载 el(fragment 的固定子节点);
|
|
10
|
+
* pending/error 占位是它的兄弟节点,宿主重渲染不 patch 子应用 root 内部;
|
|
11
|
+
* - 会话代次(§4.3):受控 sessionKey 驱动 挂载/换会话/登出;同会话重渲染不重挂;
|
|
12
|
+
* - StrictMode 安全(§4.5):双 effect 走 cleanup(作废+卸载)→setup(新代次) 顺序,
|
|
13
|
+
* 顺序 unmount/mount 不产生双实例或残留;
|
|
14
|
+
* - unmount 抛错(BN09):报告 MFU-016(phase: unmount)并封锁该容器的新挂载。
|
|
15
|
+
*
|
|
16
|
+
* appProps 是挂载时浅拷贝快照(顶层替换不追踪,重挂由 key 重建);sessionKey
|
|
17
|
+
* 是桥接控制参数,不混入业务 props。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
type LoadRemoteFn = (spec: string, opts?: {
|
|
21
|
+
retries?: number;
|
|
22
|
+
}) => Promise<any>;
|
|
23
|
+
/** 渲染函数形态的错误占位:(error, retry) => ReactNode */
|
|
24
|
+
type BridgeErrorFallback = (error: unknown, retry: () => void) => ReactNode;
|
|
25
|
+
interface ReactBridgeAppOptions {
|
|
26
|
+
/** 加载 pending 占位(默认 null) */
|
|
27
|
+
fallback?: ReactNode;
|
|
28
|
+
/** 加载/挂载失败占位;默认内置中文诊断 + 「重试加载 / 刷新页面重试」 */
|
|
29
|
+
error?: ReactNode | BridgeErrorFallback;
|
|
30
|
+
/** 透传现行 loadRemote 的重试选项(0-10 整数),不另叠自动重试 */
|
|
31
|
+
retries?: number;
|
|
32
|
+
/** 本次桥接加载等待上限 ms;不取消已发出的共享请求,迟到结果一律丢弃 */
|
|
33
|
+
timeout?: number;
|
|
34
|
+
/** 无副作用的同步 getter:首次、重试及换会话的实际加载前返回本次所需上下文快照 */
|
|
35
|
+
getContext?: () => Partial<AppContext> & Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 构建 React 宿主桥接工厂(dist 壳与 dev 门面绑定各自运行时的 loadRemote 后导出)。
|
|
39
|
+
*/
|
|
40
|
+
declare function createReactBridgeAppWithLoader(loadRemote: LoadRemoteFn): <P = Record<string, unknown>>(spec: string, options?: ReactBridgeAppOptions) => ComponentType<{
|
|
41
|
+
appProps?: P;
|
|
42
|
+
sessionKey?: string | null;
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
export { type BridgeErrorFallback, type ReactBridgeAppOptions, createReactBridgeAppWithLoader };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquireBridgeSession,
|
|
3
|
+
assertBridgeContract,
|
|
4
|
+
assertBridgeOptions,
|
|
5
|
+
assertControlledSessionKey,
|
|
6
|
+
releaseBridgeSession,
|
|
7
|
+
resolveBridgeContext,
|
|
8
|
+
withBridgeTimeout
|
|
9
|
+
} from "./chunk-LWJBHUJQ.js";
|
|
10
|
+
import {
|
|
11
|
+
bridgeLifecycleError
|
|
12
|
+
} from "./chunk-E55P4TIY.js";
|
|
13
|
+
import {
|
|
14
|
+
provideAppContext
|
|
15
|
+
} from "./chunk-TSYLDY6H.js";
|
|
16
|
+
import "./chunk-OF2JCXTZ.js";
|
|
17
|
+
|
|
18
|
+
// src/bridge-host-react.ts
|
|
19
|
+
import {
|
|
20
|
+
createElement,
|
|
21
|
+
Fragment,
|
|
22
|
+
useEffect,
|
|
23
|
+
useRef,
|
|
24
|
+
useState
|
|
25
|
+
} from "react";
|
|
26
|
+
var ERROR_STYLE = {
|
|
27
|
+
padding: "16px",
|
|
28
|
+
border: "1px solid #fde2e2",
|
|
29
|
+
borderRadius: "4px",
|
|
30
|
+
background: "#fef0f0",
|
|
31
|
+
color: "#c45656",
|
|
32
|
+
fontSize: "13px",
|
|
33
|
+
lineHeight: "1.6",
|
|
34
|
+
boxSizing: "border-box"
|
|
35
|
+
};
|
|
36
|
+
var MESSAGE_STYLE = { margin: "0 0 8px", wordBreak: "break-all" };
|
|
37
|
+
var BUTTON_STYLE = {
|
|
38
|
+
padding: "4px 14px",
|
|
39
|
+
marginRight: "8px",
|
|
40
|
+
border: "1px solid #c45656",
|
|
41
|
+
borderRadius: "4px",
|
|
42
|
+
background: "#c45656",
|
|
43
|
+
color: "#fff",
|
|
44
|
+
fontSize: "13px",
|
|
45
|
+
cursor: "pointer"
|
|
46
|
+
};
|
|
47
|
+
var SECONDARY_BUTTON_STYLE = {
|
|
48
|
+
padding: "4px 14px",
|
|
49
|
+
border: "1px solid #c45656",
|
|
50
|
+
borderRadius: "4px",
|
|
51
|
+
background: "#fff",
|
|
52
|
+
color: "#c45656",
|
|
53
|
+
fontSize: "13px",
|
|
54
|
+
cursor: "pointer"
|
|
55
|
+
};
|
|
56
|
+
function DefaultBridgeErrorView({ error, retry }) {
|
|
57
|
+
const err = error;
|
|
58
|
+
const code = err?.code ?? "UNKNOWN";
|
|
59
|
+
const message = err?.message ?? String(error ?? "unknown error");
|
|
60
|
+
return createElement("div", { style: ERROR_STYLE, "data-fulgurjs-error": code }, [
|
|
61
|
+
createElement("p", { key: "t", style: { margin: "0 0 4px", fontWeight: 600 } }, `\u6865\u63A5\u5E94\u7528\u52A0\u8F7D\u5931\u8D25\uFF08\u9519\u8BEF\u7801 ${code}\uFF09`),
|
|
62
|
+
createElement("p", { key: "m", style: MESSAGE_STYLE }, message),
|
|
63
|
+
createElement(
|
|
64
|
+
"p",
|
|
65
|
+
{ key: "n", style: { margin: "0 0 10px" } },
|
|
66
|
+
"\u300C\u91CD\u8BD5\u52A0\u8F7D\u300D\u5728\u5F53\u524D\u9875\u9762\u91CD\u5EFA\u6302\u8F7D\uFF1B\u82E5\u6D4F\u89C8\u5668\u5DF2\u7F13\u5B58\u5931\u8D25\u7684\u6A21\u5757\uFF0C\u8BF7\u7528\u300C\u5237\u65B0\u9875\u9762\u91CD\u8BD5\u300D\u2014\u2014\u5B83\u4F1A\u6574\u9875\u5237\u65B0\uFF08\u4FDD\u7559\u5F53\u524D\u5730\u5740\uFF09\uFF0C\u672A\u4FDD\u5B58\u7684\u9875\u9762\u72B6\u6001\u4F1A\u4E22\u5931\u3002"
|
|
67
|
+
),
|
|
68
|
+
createElement("button", { key: "r", type: "button", style: BUTTON_STYLE, "data-fulgurjs-retry": "", onClick: retry }, "\u91CD\u8BD5\u52A0\u8F7D"),
|
|
69
|
+
createElement("button", {
|
|
70
|
+
key: "rl",
|
|
71
|
+
type: "button",
|
|
72
|
+
style: SECONDARY_BUTTON_STYLE,
|
|
73
|
+
"data-fulgurjs-reload": "",
|
|
74
|
+
onClick: () => window.location.reload()
|
|
75
|
+
}, "\u5237\u65B0\u9875\u9762\u91CD\u8BD5")
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
function renderBridgeErrorNode(errorOpt, error, retry) {
|
|
79
|
+
if (typeof errorOpt === "function") return errorOpt(error, retry);
|
|
80
|
+
if (errorOpt !== void 0) return errorOpt;
|
|
81
|
+
return createElement(DefaultBridgeErrorView, { error, retry });
|
|
82
|
+
}
|
|
83
|
+
function createReactBridgeAppWithLoader(loadRemote) {
|
|
84
|
+
return function createReactBridgeApp(spec, options = {}) {
|
|
85
|
+
assertBridgeOptions(`createReactBridgeApp("${spec}")`, options);
|
|
86
|
+
const displayName = "FulgurjsBridge_" + spec.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
87
|
+
function BridgeHost(props) {
|
|
88
|
+
const [status, setStatus] = useState("idle");
|
|
89
|
+
const [error, setError] = useState(void 0);
|
|
90
|
+
const [attempt, setAttempt] = useState(0);
|
|
91
|
+
const containerRef = useRef(null);
|
|
92
|
+
const generationRef = useRef(0);
|
|
93
|
+
const contractRef = useRef(void 0);
|
|
94
|
+
const acquiredRef = useRef(void 0);
|
|
95
|
+
const blockedRef = useRef(false);
|
|
96
|
+
const latest = useRef(props);
|
|
97
|
+
latest.current = props;
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (blockedRef.current) return;
|
|
100
|
+
const myGen = ++generationRef.current;
|
|
101
|
+
const el = containerRef.current;
|
|
102
|
+
const release = () => {
|
|
103
|
+
if (acquiredRef.current !== void 0) {
|
|
104
|
+
releaseBridgeSession(acquiredRef.current);
|
|
105
|
+
acquiredRef.current = void 0;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const run = async () => {
|
|
109
|
+
if (!el) return;
|
|
110
|
+
const sk = latest.current.sessionKey;
|
|
111
|
+
setStatus("pending");
|
|
112
|
+
setError(void 0);
|
|
113
|
+
if (sk === null) {
|
|
114
|
+
setStatus("idle");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
assertControlledSessionKey(spec, sk);
|
|
119
|
+
const controlled = typeof sk === "string" ? sk : void 0;
|
|
120
|
+
if (controlled !== void 0 && controlled !== acquiredRef.current) {
|
|
121
|
+
release();
|
|
122
|
+
acquireBridgeSession(spec, controlled);
|
|
123
|
+
acquiredRef.current = controlled;
|
|
124
|
+
}
|
|
125
|
+
const resolution = resolveBridgeContext(spec, controlled, options.getContext);
|
|
126
|
+
if (resolution.provide) provideAppContext(resolution.provide);
|
|
127
|
+
const load = loadRemote(spec, { retries: options.retries });
|
|
128
|
+
const mod = options.timeout !== void 0 ? await withBridgeTimeout(load, options.timeout, spec) : await load;
|
|
129
|
+
if (generationRef.current !== myGen) return;
|
|
130
|
+
const contract = assertBridgeContract(spec, mod);
|
|
131
|
+
contractRef.current = contract;
|
|
132
|
+
await contract.mount(el, { ...latest.current.appProps ?? {} });
|
|
133
|
+
if (generationRef.current !== myGen) return;
|
|
134
|
+
setStatus("ready");
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (generationRef.current !== myGen) return;
|
|
137
|
+
setError(e);
|
|
138
|
+
setStatus("error");
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
void run();
|
|
142
|
+
return () => {
|
|
143
|
+
generationRef.current++;
|
|
144
|
+
release();
|
|
145
|
+
const c = contractRef.current;
|
|
146
|
+
contractRef.current = void 0;
|
|
147
|
+
if (c && el) {
|
|
148
|
+
try {
|
|
149
|
+
c.unmount(el);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
const err = bridgeLifecycleError("unmount", spec, e instanceof Error ? e : String(e));
|
|
152
|
+
console.error("[fulgurjs] \u6865\u63A5\u5E94\u7528\u5378\u8F7D\u5931\u8D25\uFF1A", err);
|
|
153
|
+
blockedRef.current = true;
|
|
154
|
+
setError(err);
|
|
155
|
+
setStatus("error");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}, [props.sessionKey, attempt]);
|
|
160
|
+
const retry = () => setAttempt((a) => a + 1);
|
|
161
|
+
const children = [];
|
|
162
|
+
if (status === "pending" && options.fallback !== void 0) {
|
|
163
|
+
children.push(createElement(Fragment, { key: "loading" }, options.fallback));
|
|
164
|
+
}
|
|
165
|
+
if (status === "error") {
|
|
166
|
+
children.push(createElement(Fragment, { key: "error" }, renderBridgeErrorNode(options.error, error, retry)));
|
|
167
|
+
}
|
|
168
|
+
children.push(createElement("div", {
|
|
169
|
+
key: "bridge-root",
|
|
170
|
+
ref: containerRef,
|
|
171
|
+
"data-fulgurjs-bridge-root": spec,
|
|
172
|
+
"data-fulgurjs-bridge-status": status
|
|
173
|
+
}));
|
|
174
|
+
return createElement(Fragment, null, children);
|
|
175
|
+
}
|
|
176
|
+
;
|
|
177
|
+
BridgeHost.displayName = displayName;
|
|
178
|
+
return BridgeHost;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
createReactBridgeAppWithLoader
|
|
183
|
+
};
|