@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,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bridgeLifecycleError
|
|
3
|
+
} from "./chunk-E55P4TIY.js";
|
|
4
|
+
import "./chunk-OF2JCXTZ.js";
|
|
5
|
+
|
|
6
|
+
// src/bridge-app-react.ts
|
|
7
|
+
import { Component, createElement, useEffect } from "react";
|
|
8
|
+
var entriesByEl = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
var FirstCommitGate = class extends Component {
|
|
10
|
+
state = { error: null };
|
|
11
|
+
static getDerivedStateFromError(error) {
|
|
12
|
+
return { error };
|
|
13
|
+
}
|
|
14
|
+
componentDidCatch(error) {
|
|
15
|
+
this.props.onError(error);
|
|
16
|
+
if (this.props.phaseRef.current === "committed") throw error;
|
|
17
|
+
}
|
|
18
|
+
render() {
|
|
19
|
+
if (this.props.phaseRef.current === "committed") return this.props.children;
|
|
20
|
+
return this.state.error === null ? this.props.children : null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function CommitProbe({ children, onCommit }) {
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
onCommit();
|
|
26
|
+
}, []);
|
|
27
|
+
return children ?? null;
|
|
28
|
+
}
|
|
29
|
+
function defineBridgeApp(factory) {
|
|
30
|
+
return {
|
|
31
|
+
mount(el, props) {
|
|
32
|
+
if (entriesByEl.has(el)) {
|
|
33
|
+
throw bridgeLifecycleError("mount", "bridge", "\u540C\u4E00\u5BB9\u5668 el \u5DF2\u6302\u8F7D\u672C\u6865\u63A5\u5E94\u7528\uFF08\u5BB9\u5668\u5DF2\u88AB\u5360\u7528\uFF09\u3002", {
|
|
34
|
+
reason: "container-occupied"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const snapshot = { ...props ?? {} };
|
|
38
|
+
let element;
|
|
39
|
+
try {
|
|
40
|
+
element = factory(snapshot);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
43
|
+
}
|
|
44
|
+
if (element === null || typeof element !== "object") {
|
|
45
|
+
throw bridgeLifecycleError(
|
|
46
|
+
"mount",
|
|
47
|
+
"bridge",
|
|
48
|
+
`defineBridgeApp \u5DE5\u5382\u8FD4\u56DE\u503C\u4E0D\u662F ReactElement\uFF08\u5F53\u524D ${element === null ? "null" : typeof element}\uFF09\u3002`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const entry = { status: "pending" };
|
|
52
|
+
entriesByEl.set(el, entry);
|
|
53
|
+
const promise = (async () => {
|
|
54
|
+
const reactDomClient = await import("react-dom/client");
|
|
55
|
+
if (entry.status === "abort" || entriesByEl.get(el) !== entry) return;
|
|
56
|
+
const root = reactDomClient.createRoot(el);
|
|
57
|
+
entry.root = root;
|
|
58
|
+
entry.status = "live";
|
|
59
|
+
const phaseRef = { current: "pending" };
|
|
60
|
+
await new Promise((resolve, reject) => {
|
|
61
|
+
let settled = false;
|
|
62
|
+
const onError = (e) => {
|
|
63
|
+
if (settled) return;
|
|
64
|
+
settled = true;
|
|
65
|
+
reject(bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e)));
|
|
66
|
+
};
|
|
67
|
+
const onCommit = () => {
|
|
68
|
+
if (settled) return;
|
|
69
|
+
settled = true;
|
|
70
|
+
phaseRef.current = "committed";
|
|
71
|
+
resolve();
|
|
72
|
+
};
|
|
73
|
+
root.render(
|
|
74
|
+
createElement(
|
|
75
|
+
FirstCommitGate,
|
|
76
|
+
{ onError, phaseRef },
|
|
77
|
+
createElement(CommitProbe, { onCommit, children: element })
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
})();
|
|
82
|
+
return promise.then(
|
|
83
|
+
void 0,
|
|
84
|
+
(e) => {
|
|
85
|
+
if (entry.root && entry.status === "live") {
|
|
86
|
+
try {
|
|
87
|
+
entry.root.unmount();
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (entriesByEl.get(el) === entry) entriesByEl.delete(el);
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
).then(() => void 0);
|
|
95
|
+
},
|
|
96
|
+
unmount(el) {
|
|
97
|
+
const entry = entriesByEl.get(el);
|
|
98
|
+
if (!entry) return;
|
|
99
|
+
entriesByEl.delete(el);
|
|
100
|
+
if (entry.status === "pending" || !entry.root) {
|
|
101
|
+
entry.status = "abort";
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
entry.status = "abort";
|
|
105
|
+
try {
|
|
106
|
+
entry.root.unmount();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
throw bridgeLifecycleError("unmount", "bridge", e instanceof Error ? e : String(e));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
defineBridgeApp
|
|
115
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
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-app-vue.ts
|
|
21
|
+
var bridge_app_vue_exports = {};
|
|
22
|
+
__export(bridge_app_vue_exports, {
|
|
23
|
+
defineBridgeApp: () => defineBridgeApp
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(bridge_app_vue_exports);
|
|
26
|
+
|
|
27
|
+
// src/runtime/errors.ts
|
|
28
|
+
var FgError = class extends Error {
|
|
29
|
+
code;
|
|
30
|
+
details;
|
|
31
|
+
constructor(code, message, details, options) {
|
|
32
|
+
super(`[fulgurjs:${code}] ${message}`, options);
|
|
33
|
+
this.name = "FgError";
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.details = details;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/bridge-errors.ts
|
|
40
|
+
var BridgeErrorCodes = {
|
|
41
|
+
/** 桥接契约非法(./bridge 默认导出缺 mount/unmount 或不是函数) */
|
|
42
|
+
BRIDGE_INVALID_CONTRACT: "MFU-015",
|
|
43
|
+
/** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
|
|
44
|
+
BRIDGE_LIFECYCLE_FAILED: "MFU-016",
|
|
45
|
+
/** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
|
|
46
|
+
BRIDGE_SESSION_MISMATCH: "MFU-017"
|
|
47
|
+
};
|
|
48
|
+
function bridgeLifecycleError(phase, spec, cause, extra) {
|
|
49
|
+
const raw = cause instanceof Error ? cause.message : String(cause ?? "unknown");
|
|
50
|
+
const fixes = {
|
|
51
|
+
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",
|
|
52
|
+
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",
|
|
53
|
+
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"
|
|
54
|
+
};
|
|
55
|
+
return new FgError(
|
|
56
|
+
BridgeErrorCodes.BRIDGE_LIFECYCLE_FAILED,
|
|
57
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 ${phase} \u9636\u6BB5\u5931\u8D25\u3002
|
|
58
|
+
\u6839\u56E0: ${raw}
|
|
59
|
+
` + fixes[phase],
|
|
60
|
+
{ phase, spec, cause: String(raw), ...extra },
|
|
61
|
+
{ cause }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/bridge-app-vue.ts
|
|
66
|
+
var appsByEl = /* @__PURE__ */ new WeakMap();
|
|
67
|
+
function defineBridgeApp(factory) {
|
|
68
|
+
return {
|
|
69
|
+
mount(el, props) {
|
|
70
|
+
if (appsByEl.has(el)) {
|
|
71
|
+
throw bridgeLifecycleError("mount", "bridge", "\u540C\u4E00\u5BB9\u5668 el \u5DF2\u6302\u8F7D\u672C\u6865\u63A5\u5E94\u7528\uFF08\u5BB9\u5668\u5DF2\u88AB\u5360\u7528\uFF09\u3002", {
|
|
72
|
+
reason: "container-occupied"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const snapshot = { ...props ?? {} };
|
|
76
|
+
let app;
|
|
77
|
+
try {
|
|
78
|
+
app = factory(snapshot);
|
|
79
|
+
if (!app || typeof app.mount !== "function" || typeof app.unmount !== "function") {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`defineBridgeApp \u5DE5\u5382\u8FD4\u56DE\u503C\u4E0D\u662F VueApp\uFF08\u5F53\u524D ${app === null ? "null" : typeof app}\uFF09\uFF1B\u5DE5\u5382\u5FC5\u987B\u8FD4\u56DE createApp(...) \u521B\u5EFA\u7684\u5E94\u7528\u5B9E\u4F8B\u3002`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
app.mount(el);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (app) {
|
|
87
|
+
try {
|
|
88
|
+
app.unmount();
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
93
|
+
}
|
|
94
|
+
appsByEl.set(el, app);
|
|
95
|
+
},
|
|
96
|
+
unmount(el) {
|
|
97
|
+
const app = appsByEl.get(el);
|
|
98
|
+
if (!app) return;
|
|
99
|
+
appsByEl.delete(el);
|
|
100
|
+
try {
|
|
101
|
+
app.unmount();
|
|
102
|
+
} catch (e) {
|
|
103
|
+
throw bridgeLifecycleError("unmount", "bridge", e instanceof Error ? e : String(e));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
109
|
+
0 && (module.exports = {
|
|
110
|
+
defineBridgeApp
|
|
111
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import { BridgeApp } from './bridge-core.cjs';
|
|
3
|
+
import './context.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vue 子应用桥接适配(/runtime 导出 defineBridgeApp;任务书 §3.3 D1/D2)。
|
|
7
|
+
*
|
|
8
|
+
* 工厂形态:接收 props 快照,返回已装配完整的 VueApp(router/pinia 等由子应用自行装配);
|
|
9
|
+
* helper 负责 app.mount(el) / app.unmount() 与按容器 el 的实例跟踪。
|
|
10
|
+
*
|
|
11
|
+
* 契约语义(§3.2):
|
|
12
|
+
* - 契约实例按容器 el 分键(WeakMap):同一契约多处挂载互不干扰;
|
|
13
|
+
* - 同一容器未卸载再次 mount → MFU-016(phase: mount,容器已被占用)且不覆盖原实例;
|
|
14
|
+
* - Vue 的 app.mount 同步完成首次渲染,mount 成功即返回 void(首次根提交语义由 Vue 保证);
|
|
15
|
+
* - mount 抛错时清理已创建的 app(best-effort unmount),不留半挂状态;
|
|
16
|
+
* - unmount 未知容器为 no-op;unmount 抛错包装为 MFU-016(phase: unmount)由宿主捕获。
|
|
17
|
+
*
|
|
18
|
+
* 本文件零 React:/runtime 导入图隔离由 tests/runtime-entry-graph.test.ts 守护。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Vue 子应用工厂:接收挂载时 props 快照,返回装配完成的 VueApp */
|
|
22
|
+
type VueBridgeAppFactory = (props: Record<string, unknown>) => App;
|
|
23
|
+
/**
|
|
24
|
+
* 定义 Vue 子应用的桥接契约(远程 ./bridge 模块默认导出)。
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { createApp } from 'vue'
|
|
28
|
+
* import { defineBridgeApp } from '@fulgurjs/federation/runtime'
|
|
29
|
+
* export default defineBridgeApp((props) => { const app = createApp(App, props); app.use(router); return app })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function defineBridgeApp(factory: VueBridgeAppFactory): BridgeApp;
|
|
33
|
+
|
|
34
|
+
export { BridgeApp, type VueBridgeAppFactory, defineBridgeApp };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import { BridgeApp } from './bridge-core.js';
|
|
3
|
+
import './context.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vue 子应用桥接适配(/runtime 导出 defineBridgeApp;任务书 §3.3 D1/D2)。
|
|
7
|
+
*
|
|
8
|
+
* 工厂形态:接收 props 快照,返回已装配完整的 VueApp(router/pinia 等由子应用自行装配);
|
|
9
|
+
* helper 负责 app.mount(el) / app.unmount() 与按容器 el 的实例跟踪。
|
|
10
|
+
*
|
|
11
|
+
* 契约语义(§3.2):
|
|
12
|
+
* - 契约实例按容器 el 分键(WeakMap):同一契约多处挂载互不干扰;
|
|
13
|
+
* - 同一容器未卸载再次 mount → MFU-016(phase: mount,容器已被占用)且不覆盖原实例;
|
|
14
|
+
* - Vue 的 app.mount 同步完成首次渲染,mount 成功即返回 void(首次根提交语义由 Vue 保证);
|
|
15
|
+
* - mount 抛错时清理已创建的 app(best-effort unmount),不留半挂状态;
|
|
16
|
+
* - unmount 未知容器为 no-op;unmount 抛错包装为 MFU-016(phase: unmount)由宿主捕获。
|
|
17
|
+
*
|
|
18
|
+
* 本文件零 React:/runtime 导入图隔离由 tests/runtime-entry-graph.test.ts 守护。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Vue 子应用工厂:接收挂载时 props 快照,返回装配完成的 VueApp */
|
|
22
|
+
type VueBridgeAppFactory = (props: Record<string, unknown>) => App;
|
|
23
|
+
/**
|
|
24
|
+
* 定义 Vue 子应用的桥接契约(远程 ./bridge 模块默认导出)。
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { createApp } from 'vue'
|
|
28
|
+
* import { defineBridgeApp } from '@fulgurjs/federation/runtime'
|
|
29
|
+
* export default defineBridgeApp((props) => { const app = createApp(App, props); app.use(router); return app })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function defineBridgeApp(factory: VueBridgeAppFactory): BridgeApp;
|
|
33
|
+
|
|
34
|
+
export { BridgeApp, type VueBridgeAppFactory, defineBridgeApp };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bridgeLifecycleError
|
|
3
|
+
} from "./chunk-E55P4TIY.js";
|
|
4
|
+
import "./chunk-OF2JCXTZ.js";
|
|
5
|
+
|
|
6
|
+
// src/bridge-app-vue.ts
|
|
7
|
+
var appsByEl = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
function defineBridgeApp(factory) {
|
|
9
|
+
return {
|
|
10
|
+
mount(el, props) {
|
|
11
|
+
if (appsByEl.has(el)) {
|
|
12
|
+
throw bridgeLifecycleError("mount", "bridge", "\u540C\u4E00\u5BB9\u5668 el \u5DF2\u6302\u8F7D\u672C\u6865\u63A5\u5E94\u7528\uFF08\u5BB9\u5668\u5DF2\u88AB\u5360\u7528\uFF09\u3002", {
|
|
13
|
+
reason: "container-occupied"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const snapshot = { ...props ?? {} };
|
|
17
|
+
let app;
|
|
18
|
+
try {
|
|
19
|
+
app = factory(snapshot);
|
|
20
|
+
if (!app || typeof app.mount !== "function" || typeof app.unmount !== "function") {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`defineBridgeApp \u5DE5\u5382\u8FD4\u56DE\u503C\u4E0D\u662F VueApp\uFF08\u5F53\u524D ${app === null ? "null" : typeof app}\uFF09\uFF1B\u5DE5\u5382\u5FC5\u987B\u8FD4\u56DE createApp(...) \u521B\u5EFA\u7684\u5E94\u7528\u5B9E\u4F8B\u3002`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
app.mount(el);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (app) {
|
|
28
|
+
try {
|
|
29
|
+
app.unmount();
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
34
|
+
}
|
|
35
|
+
appsByEl.set(el, app);
|
|
36
|
+
},
|
|
37
|
+
unmount(el) {
|
|
38
|
+
const app = appsByEl.get(el);
|
|
39
|
+
if (!app) return;
|
|
40
|
+
appsByEl.delete(el);
|
|
41
|
+
try {
|
|
42
|
+
app.unmount();
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw bridgeLifecycleError("unmount", "bridge", e instanceof Error ? e : String(e));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
defineBridgeApp
|
|
51
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 跨应用上下文标准字段表(§4.3,0.8.2 精简后)。
|
|
3
|
+
*
|
|
4
|
+
* 读写约定:宿主桥先写标准字段;远程 boot 只增不改宿主键;
|
|
5
|
+
* 嵌套对象(如 events)是引用共享,子应用挂属性即时可见(同 realm 直引用)。
|
|
6
|
+
*
|
|
7
|
+
* 精简记录(0.8.2):`token` 快照与 `formUrl`/`baseUrl` 从默认 provide 集合移除——
|
|
8
|
+
* token 快照与 getToken 函数引用重复(快照会过期),formUrl/baseUrl 自 0.7.0
|
|
9
|
+
* remoteComponent 直渲染后无消费点;项目如需可经扩展位自行提供。
|
|
10
|
+
*/
|
|
11
|
+
interface AppContext {
|
|
12
|
+
/** 宿主登录用户原始形态(只读约定) */
|
|
13
|
+
user: Record<string, any>;
|
|
14
|
+
/** 取最新 token(拉取式防过期;0.8.2 起 bridge 不再传一次性 token 快照,用本函数取值) */
|
|
15
|
+
getToken?: () => string | undefined;
|
|
16
|
+
/** 宿主 pinia 实例:子应用 useUserStore(ctx.store) 拿共享响应式状态 */
|
|
17
|
+
store?: unknown;
|
|
18
|
+
/** 宿主 Vue App 实例(同 realm 直引用):全局组件/指令注册目标 */
|
|
19
|
+
hostApp?: unknown;
|
|
20
|
+
/** EP locale 等 UI 配置(原 W4 字段) */
|
|
21
|
+
locale?: unknown;
|
|
22
|
+
/** 事件/方法池:events.main.* 宿主提供、events.bpm.* / events.lowcode.* 子应用反向注册 */
|
|
23
|
+
events?: Record<string, any>;
|
|
24
|
+
/**
|
|
25
|
+
* 非敏感登录代次 ID:宿主每次成功登录/重新登录生成新值,token 刷新但会话未变时沿用。
|
|
26
|
+
* 远程 onSession 按它去重(同一代次只执行一次)。不是用户 ID、不是 token、不作为授权凭证。
|
|
27
|
+
* 远程声明了 onSession 时必填(缺省即 MFU-013)。
|
|
28
|
+
*/
|
|
29
|
+
sessionKey?: string;
|
|
30
|
+
/** 项目扩展位(formUrl/baseUrl 等自定义键,按需自行提供) */
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 子应用桥接契约(远程 ./bridge 模块的默认导出;任务书 §3.2) */
|
|
35
|
+
interface BridgeApp {
|
|
36
|
+
/** 首次根提交完成时才算挂载成功;同一容器未卸载前重复 mount 是契约违例 */
|
|
37
|
+
mount(el: HTMLElement, props?: Record<string, unknown>): void | Promise<void>;
|
|
38
|
+
/** 同步使当前挂载代次失效并清理已创建的 root;未知容器为 no-op */
|
|
39
|
+
unmount(el: HTMLElement): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type { AppContext as A, BridgeApp as B };
|
|
@@ -0,0 +1,224 @@
|
|
|
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-core.ts
|
|
21
|
+
var bridge_core_exports = {};
|
|
22
|
+
__export(bridge_core_exports, {
|
|
23
|
+
acquireBridgeSession: () => acquireBridgeSession,
|
|
24
|
+
assertBridgeContract: () => assertBridgeContract,
|
|
25
|
+
assertBridgeOptions: () => assertBridgeOptions,
|
|
26
|
+
assertControlledSessionKey: () => assertControlledSessionKey,
|
|
27
|
+
releaseBridgeSession: () => releaseBridgeSession,
|
|
28
|
+
resolveBridgeContext: () => resolveBridgeContext,
|
|
29
|
+
withBridgeTimeout: () => withBridgeTimeout
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(bridge_core_exports);
|
|
32
|
+
|
|
33
|
+
// src/runtime/errors.ts
|
|
34
|
+
var FgError = class extends Error {
|
|
35
|
+
code;
|
|
36
|
+
details;
|
|
37
|
+
constructor(code, message, details, options) {
|
|
38
|
+
super(`[fulgurjs:${code}] ${message}`, options);
|
|
39
|
+
this.name = "FgError";
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.details = details;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/context.ts
|
|
46
|
+
var APP_CONTEXT_STORAGE_KEY = "__FULGURJS_APP_CONFIG__";
|
|
47
|
+
function clearAppContext() {
|
|
48
|
+
const g = globalThis;
|
|
49
|
+
delete g[APP_CONTEXT_STORAGE_KEY];
|
|
50
|
+
try {
|
|
51
|
+
g.__FULGURJS_RUNTIME__?.clearSessionState?.();
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/bridge-errors.ts
|
|
57
|
+
var BridgeErrorCodes = {
|
|
58
|
+
/** 桥接契约非法(./bridge 默认导出缺 mount/unmount 或不是函数) */
|
|
59
|
+
BRIDGE_INVALID_CONTRACT: "MFU-015",
|
|
60
|
+
/** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
|
|
61
|
+
BRIDGE_LIFECYCLE_FAILED: "MFU-016",
|
|
62
|
+
/** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
|
|
63
|
+
BRIDGE_SESSION_MISMATCH: "MFU-017"
|
|
64
|
+
};
|
|
65
|
+
function invalidBridgeContractError(spec, mod) {
|
|
66
|
+
const actual = mod === null ? "null" : typeof mod;
|
|
67
|
+
const missing = [];
|
|
68
|
+
if (typeof mod?.mount !== "function") missing.push("mount");
|
|
69
|
+
if (typeof mod?.unmount !== "function") missing.push("unmount");
|
|
70
|
+
return new FgError(
|
|
71
|
+
BridgeErrorCodes.BRIDGE_INVALID_CONTRACT,
|
|
72
|
+
`\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
|
|
73
|
+
\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") + `
|
|
74
|
+
\u4FEE\u6CD5: \u8FDC\u7A0B\u4FA7\u7528 defineBridgeApp(...) \u521B\u5EFA\u5951\u7EA6\u5E76\u4F5C\u4E3A\u6A21\u5757\u9ED8\u8BA4\u5BFC\u51FA\u2014\u2014
|
|
75
|
+
Vue \u5B50\u5E94\u7528: import { defineBridgeApp } from '@fulgurjs/federation/runtime'\uFF1B
|
|
76
|
+
React \u5B50\u5E94\u7528: import { defineBridgeApp } from '@fulgurjs/federation/react'\u3002
|
|
77
|
+
\u5951\u7EA6\u5FC5\u987B\u5305\u542B mount(el, props?) \u4E0E unmount(el) \u4E24\u4E2A\u51FD\u6570\u3002`,
|
|
78
|
+
{ spec, missing, actual }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function bridgeLifecycleError(phase, spec, cause, extra) {
|
|
82
|
+
const raw = cause instanceof Error ? cause.message : String(cause ?? "unknown");
|
|
83
|
+
const fixes = {
|
|
84
|
+
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",
|
|
85
|
+
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",
|
|
86
|
+
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"
|
|
87
|
+
};
|
|
88
|
+
return new FgError(
|
|
89
|
+
BridgeErrorCodes.BRIDGE_LIFECYCLE_FAILED,
|
|
90
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 ${phase} \u9636\u6BB5\u5931\u8D25\u3002
|
|
91
|
+
\u6839\u56E0: ${raw}
|
|
92
|
+
` + fixes[phase],
|
|
93
|
+
{ phase, spec, cause: String(raw), ...extra },
|
|
94
|
+
{ cause }
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
function bridgeSessionMismatchError(spec, controlled, actual, extra) {
|
|
98
|
+
const actualText = actual === void 0 ? "undefined\uFF08\u9875\u9762\u5C1A\u65E0 AppContext\uFF09" : typeof actual === "string" ? `"${actual}"` : String(actual);
|
|
99
|
+
return new FgError(
|
|
100
|
+
BridgeErrorCodes.BRIDGE_SESSION_MISMATCH,
|
|
101
|
+
`\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
|
|
102
|
+
\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
|
|
103
|
+
getContext \u5FEB\u7167\u7684 sessionKey\uFF08\u6216\u73B0\u6709 AppContext \u7684 sessionKey\uFF09\u4E0E\u53D7\u63A7\u503C\u4E0D\u540C\u3002
|
|
104
|
+
\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
|
|
105
|
+
\u51C6\u5907\u65B0\u8D26\u53F7\u5B8C\u6574\u5FEB\u7167\u540E\u66F4\u65B0\u53D7\u63A7 prop\uFF1B
|
|
106
|
+
\u2461 \u65E0 getContext \u65F6\uFF0C\u5148\u7531\u5BBF\u4E3B\u6865 provideAppContext({...\u65B0\u4F1A\u8BDD, sessionKey}) \u518D\u6302\u8F7D\uFF1B
|
|
107
|
+
\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`,
|
|
108
|
+
{ spec, controlled, actual: String(actual), ...extra }
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function invalidSessionKeyError(spec, value) {
|
|
112
|
+
return new FgError(
|
|
113
|
+
BridgeErrorCodes.BRIDGE_SESSION_MISMATCH,
|
|
114
|
+
`\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
|
|
115
|
+
\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
|
|
116
|
+
\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`,
|
|
117
|
+
{ spec, value: String(value) }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/bridge-core.ts
|
|
122
|
+
function assertBridgeContract(spec, mod) {
|
|
123
|
+
const contract = mod?.default ?? mod;
|
|
124
|
+
if (contract === null || typeof contract !== "object" || typeof contract.mount !== "function" || typeof contract.unmount !== "function") {
|
|
125
|
+
throw invalidBridgeContractError(spec, mod);
|
|
126
|
+
}
|
|
127
|
+
return contract;
|
|
128
|
+
}
|
|
129
|
+
var activeBridgeSessions = /* @__PURE__ */ new Map();
|
|
130
|
+
function acquireBridgeSession(spec, sessionKey) {
|
|
131
|
+
for (const [active] of activeBridgeSessions) {
|
|
132
|
+
if (active !== sessionKey && (activeBridgeSessions.get(active) ?? 0) > 0) {
|
|
133
|
+
throw bridgeSessionMismatchError(spec, sessionKey, active, { reason: "active-instance" });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
activeBridgeSessions.set(sessionKey, (activeBridgeSessions.get(sessionKey) ?? 0) + 1);
|
|
137
|
+
}
|
|
138
|
+
function releaseBridgeSession(sessionKey) {
|
|
139
|
+
const n = activeBridgeSessions.get(sessionKey) ?? 0;
|
|
140
|
+
if (n <= 1) activeBridgeSessions.delete(sessionKey);
|
|
141
|
+
else activeBridgeSessions.set(sessionKey, n - 1);
|
|
142
|
+
}
|
|
143
|
+
function resolveBridgeContext(spec, controlled, getContext) {
|
|
144
|
+
if (getContext) {
|
|
145
|
+
let snapshot;
|
|
146
|
+
try {
|
|
147
|
+
snapshot = getContext();
|
|
148
|
+
} catch (e) {
|
|
149
|
+
throw bridgeLifecycleError("getContext", spec, e);
|
|
150
|
+
}
|
|
151
|
+
if (snapshot === null || typeof snapshot !== "object" || typeof snapshot.then === "function") {
|
|
152
|
+
throw bridgeLifecycleError(
|
|
153
|
+
"getContext",
|
|
154
|
+
spec,
|
|
155
|
+
`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`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (controlled !== void 0 && snapshot.sessionKey !== controlled) {
|
|
159
|
+
throw bridgeSessionMismatchError(spec, controlled, snapshot.sessionKey, { reason: "getter-snapshot" });
|
|
160
|
+
}
|
|
161
|
+
const current = currentSessionKey();
|
|
162
|
+
if (snapshot.sessionKey !== void 0 && snapshot.sessionKey !== current) {
|
|
163
|
+
clearAppContext();
|
|
164
|
+
}
|
|
165
|
+
return { provide: snapshot };
|
|
166
|
+
}
|
|
167
|
+
if (controlled !== void 0) {
|
|
168
|
+
const current = currentSessionKey();
|
|
169
|
+
if (current !== controlled) {
|
|
170
|
+
throw bridgeSessionMismatchError(spec, controlled, current, { reason: "app-context" });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
function currentSessionKey() {
|
|
176
|
+
const sk = (globalThis.__FULGURJS_APP_CONFIG__ ?? {}).sessionKey;
|
|
177
|
+
return typeof sk === "string" && sk !== "" ? sk : void 0;
|
|
178
|
+
}
|
|
179
|
+
function assertControlledSessionKey(spec, value) {
|
|
180
|
+
if (value === void 0 || value === null) return;
|
|
181
|
+
if (typeof value === "string" && value !== "") return;
|
|
182
|
+
throw invalidSessionKeyError(spec, value);
|
|
183
|
+
}
|
|
184
|
+
function assertBridgeOptions(source, opts) {
|
|
185
|
+
if (opts.retries !== void 0 && (typeof opts.retries !== "number" || !Number.isInteger(opts.retries) || opts.retries < 0 || opts.retries > 10)) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`[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`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (opts.timeout !== void 0 && (typeof opts.timeout !== "number" || !Number.isFinite(opts.timeout) || opts.timeout <= 0)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`[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`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function withBridgeTimeout(p, ms, spec) {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
const timer = setTimeout(() => {
|
|
199
|
+
reject(new Error(
|
|
200
|
+
`[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`
|
|
201
|
+
));
|
|
202
|
+
}, ms);
|
|
203
|
+
p.then(
|
|
204
|
+
(v) => {
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
resolve(v);
|
|
207
|
+
},
|
|
208
|
+
(e) => {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
reject(e);
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
216
|
+
0 && (module.exports = {
|
|
217
|
+
acquireBridgeSession,
|
|
218
|
+
assertBridgeContract,
|
|
219
|
+
assertBridgeOptions,
|
|
220
|
+
assertControlledSessionKey,
|
|
221
|
+
releaseBridgeSession,
|
|
222
|
+
resolveBridgeContext,
|
|
223
|
+
withBridgeTimeout
|
|
224
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AppContext } from './context.cjs';
|
|
2
|
+
|
|
3
|
+
/** 子应用桥接契约(远程 ./bridge 模块的默认导出;任务书 §3.2) */
|
|
4
|
+
interface BridgeApp {
|
|
5
|
+
/** 首次根提交完成时才算挂载成功;同一容器未卸载前重复 mount 是契约违例 */
|
|
6
|
+
mount(el: HTMLElement, props?: Record<string, unknown>): void | Promise<void>;
|
|
7
|
+
/** 同步使当前挂载代次失效并清理已创建的 root;未知容器为 no-op */
|
|
8
|
+
unmount(el: HTMLElement): void;
|
|
9
|
+
}
|
|
10
|
+
/** 远程模块 → 契约校验(默认导出须含函数类型的 mount/unmount,否则 MFU-015) */
|
|
11
|
+
declare function assertBridgeContract(spec: string, mod: unknown): BridgeApp;
|
|
12
|
+
/**
|
|
13
|
+
* 登记一个受控会话代次;页面已有不同 sessionKey 的活跃桥接实例时拒绝(MFU-017)。
|
|
14
|
+
* 同一会话的多实例并存合法(计数递增)。
|
|
15
|
+
*/
|
|
16
|
+
declare function acquireBridgeSession(spec: string, sessionKey: string): void;
|
|
17
|
+
/** 释放一个受控会话代次(unmount/换代/失败清理时调用;幂等) */
|
|
18
|
+
declare function releaseBridgeSession(sessionKey: string): void;
|
|
19
|
+
interface BridgeContextResolution {
|
|
20
|
+
/** 校验通过后需要由桥接层写入全局的快照;undefined = 复用现有 AppContext,无需写入 */
|
|
21
|
+
provide?: Partial<AppContext> & Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 挂载前校验会话与上下文快照(同步、无副作用;只在全部通过后由调用方 provide)。
|
|
25
|
+
*
|
|
26
|
+
* - getter 抛错/返回非对象/thenable → MFU-016(phase: getContext);
|
|
27
|
+
* - 受控 sessionKey 与快照或现有 AppContext 不一致 → MFU-017(不写任何全局状态);
|
|
28
|
+
* - 新会话(与全局当前 sessionKey 不同)先 clearAppContext 清旧账号残留,
|
|
29
|
+
* 避免 provideAppContext 顶层 merge 留下旧账号独有字段(BR06)。
|
|
30
|
+
*
|
|
31
|
+
* 会话登记(acquireBridgeSession/releaseBridgeSession)由宿主适配器在代次边界管理,
|
|
32
|
+
* 本函数不重复登记——重试同会话不得叠加计数。
|
|
33
|
+
*
|
|
34
|
+
* controlled 语义:非空字符串 = 受控校验;undefined = 不启用受控校验(快照照常提供,
|
|
35
|
+
* 远程 onSession 由 runtime 按 AppContext.sessionKey 判定);null 由调用方先行短路。
|
|
36
|
+
*/
|
|
37
|
+
declare function resolveBridgeContext(spec: string, controlled: string | undefined, getContext: (() => Partial<AppContext> & Record<string, unknown>) | undefined): BridgeContextResolution;
|
|
38
|
+
/** 受控 sessionKey 受控值合法性:只接受 undefined / null / 非空字符串(任务书 BN10) */
|
|
39
|
+
declare function assertControlledSessionKey(spec: string, value: unknown): void;
|
|
40
|
+
/** 适配层参数校验(与 vue/react 适配器同口径:retries 0-10 整数、timeout 正有限数) */
|
|
41
|
+
declare function assertBridgeOptions(source: string, opts: {
|
|
42
|
+
retries?: number;
|
|
43
|
+
timeout?: number;
|
|
44
|
+
}): void;
|
|
45
|
+
/**
|
|
46
|
+
* 桥接加载等待上限(与 React remoteComponent.timeout 同语义):
|
|
47
|
+
* 只结束本次等待,不取消已发出的共享 loadRemote 请求;迟到的成功/失败一律丢弃。
|
|
48
|
+
*/
|
|
49
|
+
declare function withBridgeTimeout<T>(p: Promise<T>, ms: number, spec: string): Promise<T>;
|
|
50
|
+
|
|
51
|
+
export { type BridgeApp, type BridgeContextResolution, acquireBridgeSession, assertBridgeContract, assertBridgeOptions, assertControlledSessionKey, releaseBridgeSession, resolveBridgeContext, withBridgeTimeout };
|