@fulgurjs/federation 5.3.3 → 5.4.1

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/DESIGN.md +4 -0
  3. package/README.en.md +25 -3
  4. package/README.md +88 -1
  5. package/dist/bridge-app-react.cjs +14 -4
  6. package/dist/bridge-app-react.d.cts +16 -4
  7. package/dist/bridge-app-react.d.ts +16 -4
  8. package/dist/bridge-app-react.js +6 -4
  9. package/dist/bridge-app-vue.cjs +55 -9
  10. package/dist/bridge-app-vue.d.cts +25 -4
  11. package/dist/bridge-app-vue.d.ts +25 -4
  12. package/dist/bridge-app-vue.js +47 -9
  13. package/dist/bridge-core-ejlY9ddN.d.ts +93 -0
  14. package/dist/bridge-core.cjs +9 -1
  15. package/dist/bridge-core.d.cts +8 -1
  16. package/dist/bridge-core.d.ts +8 -1
  17. package/dist/bridge-core.js +2 -2
  18. package/dist/bridge-errors.cjs +56 -3
  19. package/dist/bridge-errors.d.cts +18 -2
  20. package/dist/bridge-errors.d.ts +18 -2
  21. package/dist/bridge-errors.js +11 -3
  22. package/dist/{bridge-host-react-C55RmGll.d.ts → bridge-host-react-Bua1TCVh.d.ts} +1 -1
  23. package/dist/bridge-host-react.cjs +333 -3
  24. package/dist/bridge-host-react.d.cts +2 -0
  25. package/dist/bridge-host-react.d.ts +2 -0
  26. package/dist/bridge-host-react.js +30 -4
  27. package/dist/{bridge-host-vue-BAxDKqGH.d.ts → bridge-host-vue-Dd1kkwDb.d.ts} +1 -1
  28. package/dist/bridge-host-vue.cjs +344 -3
  29. package/dist/bridge-host-vue.js +42 -5
  30. package/dist/bridge-react.d.ts +5 -3
  31. package/dist/bridge-router-core-BRZQYDPm.d.cts +46 -0
  32. package/dist/bridge-router-core-BRZQYDPm.d.ts +46 -0
  33. package/dist/bridge-router-react.d.ts +73 -0
  34. package/dist/bridge-router-react.js +247 -0
  35. package/dist/bridge-router-vue.d.ts +50 -0
  36. package/dist/bridge-router-vue.js +217 -0
  37. package/dist/bridge-vue.d.ts +2 -2
  38. package/dist/bridge.d.ts +6 -4
  39. package/dist/{chunk-GWJKABE7.js → chunk-43UX3UZE.js} +39 -4
  40. package/dist/chunk-7CQOVLSI.js +274 -0
  41. package/dist/{chunk-DH2QMM4Y.js → chunk-HZGQOW35.js} +51 -2
  42. package/dist/{chunk-ZTRJLCOF.js → chunk-KT3YARK5.js} +27 -3
  43. package/dist/{chunk-LE2G23E7.js → chunk-RXXBTPAY.js} +1 -1
  44. package/dist/{chunk-WD2ON7SN.js → chunk-UFWZPE36.js} +313 -12
  45. package/dist/index.cjs +30 -10
  46. package/dist/index.js +30 -10
  47. package/dist/react.d.ts +51 -4
  48. package/dist/runtime-entry.d.ts +60 -4
  49. package/dist/runtime.js +9 -9
  50. package/examples/bridge/react-host/package-lock.json +105 -104
  51. package/examples/bridge/react-remote/package-lock.json +105 -104
  52. package/examples/bridge/vue-host/package-lock.json +105 -104
  53. package/examples/bridge/vue-remote/package-lock.json +105 -104
  54. package/examples/react/host/package-lock.json +1 -1
  55. package/examples/react/remote/package-lock.json +1 -1
  56. package/examples/vue/host/package-lock.json +1 -1
  57. package/examples/vue/remote/package-lock.json +1 -1
  58. package/package.json +28 -4
  59. package/dist/bridge-core-6uLJiMVa.d.ts +0 -42
@@ -0,0 +1,73 @@
1
+ import { ReactElement } from 'react';
2
+ import { RouteObject } from 'react-router-dom';
3
+
4
+ /** 统一逻辑位置:pathname 以 / 开头;search 空串或 ? 开头;hash 空串或 # 开头 */
5
+ interface BridgeLocation {
6
+ pathname: string;
7
+ search: string;
8
+ hash: string;
9
+ }
10
+ type BridgeNavigationResult = {
11
+ status: 'committed';
12
+ location: BridgeLocation;
13
+ } | {
14
+ status: 'cancelled';
15
+ location: BridgeLocation;
16
+ };
17
+ /** 宿主导航端口(宿主适配器把 Router 包装成本接口;逻辑路径不含部署 base) */
18
+ interface BridgeHostNavigation {
19
+ getLocation(): BridgeLocation;
20
+ subscribe(listener: (location: BridgeLocation) => void): () => void;
21
+ navigate(target: BridgeLocation, action: 'push' | 'replace', options?: {
22
+ signal?: AbortSignal;
23
+ }): Promise<BridgeNavigationResult>;
24
+ go(delta: number): void;
25
+ }
26
+ /** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
27
+ interface BridgeChildRoute {
28
+ getLocation(): BridgeLocation;
29
+ subscribe(listener: (location: BridgeLocation) => void): () => void;
30
+ navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
31
+ go(delta: number): void;
32
+ }
33
+
34
+ /** React Router data router 的宿主端口与子应用 memory router 接线(按需入口)。 */
35
+
36
+ type ReactBridgeCancelPolicy = (next: BridgeLocation) => boolean;
37
+ interface ReactDataRouterLike {
38
+ state: {
39
+ location: {
40
+ pathname: string;
41
+ search: string;
42
+ hash: string;
43
+ key?: string;
44
+ };
45
+ navigation?: {
46
+ state: string;
47
+ location?: BridgeLocation;
48
+ };
49
+ blockers?: ReadonlyMap<string, {
50
+ state: string;
51
+ reset?: () => void;
52
+ }>;
53
+ };
54
+ subscribe(cb: () => void): () => void;
55
+ navigate(to: string | number, opts?: {
56
+ replace?: boolean;
57
+ }): void | Promise<void>;
58
+ }
59
+ /** canNavigate 是可选预判;最终结果同时核对真实 blocker 与已提交位置。 */
60
+ declare function createReactBridgeNavigation(router: ReactDataRouterLike, options?: {
61
+ canNavigate?: ReactBridgeCancelPolicy;
62
+ basename?: string;
63
+ }): BridgeHostNavigation;
64
+ interface ReactBridgeRouterConnection {
65
+ element: ReactElement;
66
+ dispose(): void;
67
+ }
68
+ /** 返回 RouterProvider 元素,直接作为 defineBridgeApp 工厂的 ReactElement 返回值。 */
69
+ declare function createReactBridgeRouter(routing: BridgeChildRoute, routes: RouteObject[], options?: {
70
+ signal?: AbortSignal;
71
+ }): ReactBridgeRouterConnection;
72
+
73
+ export { type ReactBridgeCancelPolicy, type ReactBridgeRouterConnection, type ReactDataRouterLike, createReactBridgeNavigation, createReactBridgeRouter };
@@ -0,0 +1,247 @@
1
+ // src/bridge-router-react.tsx
2
+ import { createElement } from "react";
3
+ import { RouterProvider, createMemoryRouter } from "react-router-dom";
4
+
5
+ // src/runtime/errors.ts
6
+ var FgError = class extends Error {
7
+ code;
8
+ details;
9
+ constructor(code, message, details, options) {
10
+ super(`[fulgurjs:${code}] ${message}`, options);
11
+ this.name = "FgError";
12
+ this.code = code;
13
+ this.details = details;
14
+ }
15
+ };
16
+
17
+ // src/bridge-errors.ts
18
+ var BridgeErrorCodes = {
19
+ /** 桥接契约非法(./bridge 默认导出缺 mount/unmount 或不是函数) */
20
+ BRIDGE_INVALID_CONTRACT: "MFU-015",
21
+ /** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
22
+ BRIDGE_LIFECYCLE_FAILED: "MFU-016",
23
+ /** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
24
+ BRIDGE_SESSION_MISMATCH: "MFU-017",
25
+ /** 路由同步配置/前缀冲突(basePath 非法、同页前缀冲突或重叠) */
26
+ ROUTING_CONFIG_INVALID: "MFU-030",
27
+ /** 路由协议缺失/通道失效(子应用未声明 routing 协议,或已销毁通道被再次使用) */
28
+ ROUTING_PROTOCOL_MISSING: "MFU-031",
29
+ /** 非法导航(目标越界前缀、非法 go 参数、失效实例的请求) */
30
+ ROUTING_NAVIGATION_INVALID: "MFU-032",
31
+ /** 路由准备/同步失败(重定向循环、子应用路由准备失败且已阻止错误页面显示) */
32
+ ROUTING_SYNC_FAILED: "MFU-033"
33
+ };
34
+ function routingSyncError(spec, reason, chain, cause) {
35
+ return new FgError(
36
+ BridgeErrorCodes.ROUTING_SYNC_FAILED,
37
+ `\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5931\u8D25\u3002
38
+ \u539F\u56E0\uFF1A${reason}
39
+ \u4FEE\u6CD5\uFF1A\u68C0\u67E5\u8DEF\u7531\u5B88\u536B\u3001\u52A0\u8F7D\u5668\u4E0E\u5BFC\u822A\u5F02\u5E38\uFF1B\u91CD\u5B9A\u5411\u94FE\u4E0D\u5E94\u6210\u73AF\u3002\u76EE\u6807\u94FE\uFF1A${chain.join(" \u2192 ")}\u3002`,
40
+ { spec, chain: [...chain] },
41
+ { cause }
42
+ );
43
+ }
44
+
45
+ // src/bridge-router-core.ts
46
+ function sameLocation(a, b) {
47
+ return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
48
+ }
49
+
50
+ // src/bridge-router-sync.ts
51
+ function connectChildNavigation(routing, apply, onError) {
52
+ let disposed = false;
53
+ let generation = 0;
54
+ let tail = Promise.resolve();
55
+ let confirmed = routing.getLocation();
56
+ let pending;
57
+ const restore = () => apply(routing.getLocation());
58
+ const unsub = routing.subscribe((location) => {
59
+ if (disposed) return;
60
+ const changed = !sameLocation(location, confirmed);
61
+ confirmed = location;
62
+ if (pending && sameLocation(location, pending)) return;
63
+ if (changed) generation++;
64
+ tail = tail.then(() => disposed ? void 0 : restore()).catch(onError);
65
+ });
66
+ return {
67
+ enqueue(prepare, action) {
68
+ const version = generation;
69
+ const cancelled = () => ({ status: "cancelled", location: routing.getLocation() });
70
+ const task = tail.then(async () => {
71
+ if (disposed || version !== generation) return cancelled();
72
+ try {
73
+ const target = await prepare();
74
+ if (disposed) return cancelled();
75
+ if (version !== generation) {
76
+ await restore();
77
+ return cancelled();
78
+ }
79
+ if (!target) return cancelled();
80
+ pending = target;
81
+ const result = await routing.navigate(target, action);
82
+ if (!disposed) await restore();
83
+ return result;
84
+ } catch (cause) {
85
+ if (!disposed) {
86
+ try {
87
+ await restore();
88
+ } catch (restoreError) {
89
+ onError(restoreError);
90
+ }
91
+ }
92
+ throw routingSyncError("child-router", `\u5B50\u5E94\u7528\u8DEF\u7531\u51C6\u5907\u6216\u540C\u6B65\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause);
93
+ } finally {
94
+ pending = void 0;
95
+ }
96
+ });
97
+ tail = task.then(() => {
98
+ }, () => {
99
+ });
100
+ return task;
101
+ },
102
+ dispose() {
103
+ disposed = true;
104
+ generation++;
105
+ unsub();
106
+ }
107
+ };
108
+ }
109
+
110
+ // src/bridge-router-react.tsx
111
+ function createReactBridgeNavigation(router, options = {}) {
112
+ const base = options.basename && options.basename !== "/" ? options.basename.replace(/\/$/, "") : "";
113
+ const toLogic = () => {
114
+ const l = router.state.location;
115
+ const pathname = base && (l.pathname === base || l.pathname.startsWith(base + "/")) ? l.pathname.slice(base.length) || "/" : l.pathname;
116
+ return { pathname, search: l.search, hash: l.hash };
117
+ };
118
+ return {
119
+ getLocation: toLogic,
120
+ subscribe(listener) {
121
+ let previous = toLogic();
122
+ return router.subscribe(() => {
123
+ const next = toLogic();
124
+ if (!sameLocation(previous, next)) {
125
+ previous = next;
126
+ listener(next);
127
+ }
128
+ });
129
+ },
130
+ async navigate(target, action, context) {
131
+ if (context?.signal?.aborted || options.canNavigate && !options.canNavigate(target)) return { status: "cancelled", location: toLogic() };
132
+ const initial = router.state.location;
133
+ return new Promise((resolve, reject) => {
134
+ let finished = false;
135
+ let returned = false;
136
+ let blocked = false;
137
+ let unwatch = () => {
138
+ };
139
+ const finish = (status) => {
140
+ if (finished) return;
141
+ finished = true;
142
+ unwatch();
143
+ context?.signal?.removeEventListener("abort", abort);
144
+ resolve({ status, location: toLogic() });
145
+ };
146
+ const abort = () => {
147
+ finish("cancelled");
148
+ for (const blocker of router.state.blockers?.values() ?? []) if (blocker.state === "blocked") blocker.reset?.();
149
+ const loading = router.state.navigation;
150
+ if (loading && loading.state !== "idle" && loading.location?.pathname === base + target.pathname) {
151
+ const current = toLogic();
152
+ void Promise.resolve().then(() => {
153
+ const latest = router.state.navigation;
154
+ if (!latest || latest.state === "idle" || latest.location?.pathname !== base + target.pathname) return;
155
+ return router.navigate(current.pathname + current.search + current.hash, { replace: true });
156
+ }).catch((error) => console.error("[fulgurjs] \u53D6\u6D88\u5BBF\u4E3B\u5F85\u63D0\u4EA4\u5BFC\u822A\u5931\u8D25\uFF1A", error));
157
+ }
158
+ };
159
+ const check = () => {
160
+ if (finished) return;
161
+ if (context?.signal?.aborted) {
162
+ abort();
163
+ return;
164
+ }
165
+ const blockers = [...router.state.blockers?.values() ?? []];
166
+ if (blockers.some((b) => b.state === "blocked" || b.state === "proceeding")) {
167
+ blocked = true;
168
+ return;
169
+ }
170
+ if (router.state.navigation && router.state.navigation.state !== "idle") return;
171
+ const actual = router.state.location;
172
+ const committed = actual.key !== initial.key || !sameLocation(actual, initial);
173
+ if (committed) finish("committed");
174
+ else if (returned || blocked) finish("cancelled");
175
+ };
176
+ unwatch = router.subscribe(check);
177
+ context?.signal?.addEventListener("abort", abort, { once: true });
178
+ Promise.resolve().then(() => {
179
+ if (finished || context?.signal?.aborted) return;
180
+ return router.navigate(target.pathname + target.search + target.hash, { replace: action === "replace" });
181
+ }).then(() => {
182
+ returned = true;
183
+ check();
184
+ }, (error) => {
185
+ if (finished) return;
186
+ finished = true;
187
+ unwatch();
188
+ context?.signal?.removeEventListener("abort", abort);
189
+ reject(error);
190
+ });
191
+ });
192
+ },
193
+ go(delta) {
194
+ void Promise.resolve().then(() => router.navigate(delta)).catch((error) => console.error("[fulgurjs] \u5BBF\u4E3B\u5386\u53F2\u5BFC\u822A\u5931\u8D25\uFF1A", error));
195
+ }
196
+ };
197
+ }
198
+ function createReactBridgeRouter(routing, routes, options = {}) {
199
+ const init = routing.getLocation();
200
+ const router = createMemoryRouter(routes, { initialEntries: [init.pathname + init.search + init.hash] });
201
+ const navigate = router.navigate.bind(router);
202
+ let disposed = false;
203
+ const sync = connectChildNavigation(routing, async (loc) => {
204
+ const cur = router.state.location;
205
+ if (!sameLocation(cur, loc)) await navigate(loc.pathname + loc.search + loc.hash, { replace: true });
206
+ }, (error) => console.error("[fulgurjs] \u5B50\u5E94\u7528 React \u8DEF\u7531\u540C\u6B65\u5931\u8D25\uFF1A", error));
207
+ router.navigate = ((to, options2) => {
208
+ if (typeof to === "number") {
209
+ if (!disposed) routing.go(to);
210
+ return Promise.resolve();
211
+ }
212
+ const task = sync.enqueue(async () => {
213
+ await navigate(to, options2);
214
+ const l = router.state.location;
215
+ return { pathname: l.pathname, search: l.search, hash: l.hash };
216
+ }, options2?.replace ? "replace" : "push").then(() => {
217
+ });
218
+ void task.catch((error) => console.error("[fulgurjs] \u5B50\u5E94\u7528 React \u8DEF\u7531\u540C\u6B65\u5931\u8D25\uFF1A", error));
219
+ return task;
220
+ });
221
+ let unwatch = () => {
222
+ };
223
+ if (!router.state.initialized) unwatch = router.subscribe(() => {
224
+ if (!router.state.initialized) return;
225
+ unwatch();
226
+ const actual = router.state.location;
227
+ if (!sameLocation(actual, init)) void sync.enqueue(async () => actual, "replace").catch((error) => console.error("[fulgurjs] \u521D\u59CB React \u8DEF\u7531\u540C\u6B65\u5931\u8D25\uFF1A", error));
228
+ });
229
+ const connection = {
230
+ element: createElement(RouterProvider, { router }),
231
+ dispose() {
232
+ if (disposed) return;
233
+ options.signal?.removeEventListener("abort", connection.dispose);
234
+ disposed = true;
235
+ unwatch();
236
+ sync.dispose();
237
+ router.dispose();
238
+ }
239
+ };
240
+ options.signal?.addEventListener("abort", connection.dispose, { once: true });
241
+ if (options.signal?.aborted) connection.dispose();
242
+ return connection;
243
+ }
244
+ export {
245
+ createReactBridgeNavigation,
246
+ createReactBridgeRouter
247
+ };
@@ -0,0 +1,50 @@
1
+ import { Router } from 'vue-router';
2
+
3
+ /** 统一逻辑位置:pathname 以 / 开头;search 空串或 ? 开头;hash 空串或 # 开头 */
4
+ interface BridgeLocation {
5
+ pathname: string;
6
+ search: string;
7
+ hash: string;
8
+ }
9
+ type BridgeNavigationResult = {
10
+ status: 'committed';
11
+ location: BridgeLocation;
12
+ } | {
13
+ status: 'cancelled';
14
+ location: BridgeLocation;
15
+ };
16
+ /** 宿主导航端口(宿主适配器把 Router 包装成本接口;逻辑路径不含部署 base) */
17
+ interface BridgeHostNavigation {
18
+ getLocation(): BridgeLocation;
19
+ subscribe(listener: (location: BridgeLocation) => void): () => void;
20
+ navigate(target: BridgeLocation, action: 'push' | 'replace', options?: {
21
+ signal?: AbortSignal;
22
+ }): Promise<BridgeNavigationResult>;
23
+ go(delta: number): void;
24
+ }
25
+ /** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
26
+ interface BridgeChildRoute {
27
+ getLocation(): BridgeLocation;
28
+ subscribe(listener: (location: BridgeLocation) => void): () => void;
29
+ navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
30
+ go(delta: number): void;
31
+ }
32
+
33
+ /** Vue Router 4 的宿主导航端口与子应用 memory router 接线(按需入口)。 */
34
+
35
+ interface VueBridgeNavigationOptions {
36
+ /** 保留配置兼容;Vue Router fullPath 已剥离 history base,不再二次剥离。 */
37
+ routerBase?: string;
38
+ }
39
+ declare function createVueBridgeNavigation(router: Router, _options?: VueBridgeNavigationOptions): BridgeHostNavigation;
40
+ interface VueBridgeRouterConnection {
41
+ /** 必须 await ready 后再 app.use(router),初始错误会拒绝此 Promise。 */
42
+ ready: Promise<void>;
43
+ dispose(): void;
44
+ }
45
+ /** 接线限子应用自己的 memory router;dispose 恢复原始导航方法。 */
46
+ declare function connectVueBridgeRouter(routing: BridgeChildRoute, router: Router, options?: {
47
+ signal?: AbortSignal;
48
+ }): VueBridgeRouterConnection;
49
+
50
+ export { type VueBridgeNavigationOptions, type VueBridgeRouterConnection, connectVueBridgeRouter, createVueBridgeNavigation };
@@ -0,0 +1,217 @@
1
+ // src/runtime/errors.ts
2
+ var FgError = class extends Error {
3
+ code;
4
+ details;
5
+ constructor(code, message, details, options) {
6
+ super(`[fulgurjs:${code}] ${message}`, options);
7
+ this.name = "FgError";
8
+ this.code = code;
9
+ this.details = details;
10
+ }
11
+ };
12
+
13
+ // src/bridge-errors.ts
14
+ var BridgeErrorCodes = {
15
+ /** 桥接契约非法(./bridge 默认导出缺 mount/unmount 或不是函数) */
16
+ BRIDGE_INVALID_CONTRACT: "MFU-015",
17
+ /** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
18
+ BRIDGE_LIFECYCLE_FAILED: "MFU-016",
19
+ /** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
20
+ BRIDGE_SESSION_MISMATCH: "MFU-017",
21
+ /** 路由同步配置/前缀冲突(basePath 非法、同页前缀冲突或重叠) */
22
+ ROUTING_CONFIG_INVALID: "MFU-030",
23
+ /** 路由协议缺失/通道失效(子应用未声明 routing 协议,或已销毁通道被再次使用) */
24
+ ROUTING_PROTOCOL_MISSING: "MFU-031",
25
+ /** 非法导航(目标越界前缀、非法 go 参数、失效实例的请求) */
26
+ ROUTING_NAVIGATION_INVALID: "MFU-032",
27
+ /** 路由准备/同步失败(重定向循环、子应用路由准备失败且已阻止错误页面显示) */
28
+ ROUTING_SYNC_FAILED: "MFU-033"
29
+ };
30
+ function routingSyncError(spec, reason, chain, cause) {
31
+ return new FgError(
32
+ BridgeErrorCodes.ROUTING_SYNC_FAILED,
33
+ `\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5931\u8D25\u3002
34
+ \u539F\u56E0\uFF1A${reason}
35
+ \u4FEE\u6CD5\uFF1A\u68C0\u67E5\u8DEF\u7531\u5B88\u536B\u3001\u52A0\u8F7D\u5668\u4E0E\u5BFC\u822A\u5F02\u5E38\uFF1B\u91CD\u5B9A\u5411\u94FE\u4E0D\u5E94\u6210\u73AF\u3002\u76EE\u6807\u94FE\uFF1A${chain.join(" \u2192 ")}\u3002`,
36
+ { spec, chain: [...chain] },
37
+ { cause }
38
+ );
39
+ }
40
+
41
+ // src/bridge-router-core.ts
42
+ function sameLocation(a, b) {
43
+ return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
44
+ }
45
+
46
+ // src/bridge-router-sync.ts
47
+ function connectChildNavigation(routing, apply, onError) {
48
+ let disposed = false;
49
+ let generation = 0;
50
+ let tail = Promise.resolve();
51
+ let confirmed = routing.getLocation();
52
+ let pending;
53
+ const restore = () => apply(routing.getLocation());
54
+ const unsub = routing.subscribe((location) => {
55
+ if (disposed) return;
56
+ const changed = !sameLocation(location, confirmed);
57
+ confirmed = location;
58
+ if (pending && sameLocation(location, pending)) return;
59
+ if (changed) generation++;
60
+ tail = tail.then(() => disposed ? void 0 : restore()).catch(onError);
61
+ });
62
+ return {
63
+ enqueue(prepare, action) {
64
+ const version = generation;
65
+ const cancelled = () => ({ status: "cancelled", location: routing.getLocation() });
66
+ const task = tail.then(async () => {
67
+ if (disposed || version !== generation) return cancelled();
68
+ try {
69
+ const target = await prepare();
70
+ if (disposed) return cancelled();
71
+ if (version !== generation) {
72
+ await restore();
73
+ return cancelled();
74
+ }
75
+ if (!target) return cancelled();
76
+ pending = target;
77
+ const result = await routing.navigate(target, action);
78
+ if (!disposed) await restore();
79
+ return result;
80
+ } catch (cause) {
81
+ if (!disposed) {
82
+ try {
83
+ await restore();
84
+ } catch (restoreError) {
85
+ onError(restoreError);
86
+ }
87
+ }
88
+ throw routingSyncError("child-router", `\u5B50\u5E94\u7528\u8DEF\u7531\u51C6\u5907\u6216\u540C\u6B65\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause);
89
+ } finally {
90
+ pending = void 0;
91
+ }
92
+ });
93
+ tail = task.then(() => {
94
+ }, () => {
95
+ });
96
+ return task;
97
+ },
98
+ dispose() {
99
+ disposed = true;
100
+ generation++;
101
+ unsub();
102
+ }
103
+ };
104
+ }
105
+
106
+ // src/bridge-router-vue.ts
107
+ var locationFromPath = (path) => {
108
+ const raw = new URL(path || "/", "http://f.invalid");
109
+ return { pathname: raw.pathname, search: raw.search, hash: raw.hash };
110
+ };
111
+ function createVueBridgeNavigation(router, _options = {}) {
112
+ const toLogic = () => locationFromPath(router.currentRoute.value.fullPath);
113
+ return {
114
+ getLocation: toLogic,
115
+ subscribe(listener) {
116
+ return router.afterEach((_to, _from, failure) => {
117
+ if (!failure) listener(toLogic());
118
+ });
119
+ },
120
+ async navigate(target, action, options) {
121
+ if (options?.signal?.aborted) return { status: "cancelled", location: toLogic() };
122
+ const removeGuard = router.beforeResolve(() => options?.signal?.aborted ? false : void 0);
123
+ try {
124
+ const to = target.pathname + target.search + target.hash;
125
+ const failure = action === "replace" ? await router.replace(to) : await router.push(to);
126
+ return { status: failure ? "cancelled" : "committed", location: toLogic() };
127
+ } finally {
128
+ removeGuard();
129
+ }
130
+ },
131
+ go(delta) {
132
+ router.go(delta);
133
+ }
134
+ };
135
+ }
136
+ function connectVueBridgeRouter(routing, router, options = {}) {
137
+ const push = router.push;
138
+ const replace = router.replace;
139
+ const go = router.go;
140
+ const back = router.back;
141
+ const forward = router.forward;
142
+ let disposed = false;
143
+ const report = (error) => console.error("[fulgurjs] \u5B50\u5E94\u7528 Vue \u8DEF\u7531\u540C\u6B65\u5931\u8D25\uFF1A", error);
144
+ const apply = async (loc) => {
145
+ const path = loc.pathname + loc.search + loc.hash;
146
+ if (router.currentRoute.value.fullPath !== path) {
147
+ const failure = await replace.call(router, path);
148
+ if (failure && router.currentRoute.value.fullPath !== path) throw routingSyncError("vue-router", "\u5B50\u5E94\u7528\u5B88\u536B\u62D2\u7EDD\u5E94\u7528\u5BBF\u4E3B\u786E\u8BA4\u7684\u4F4D\u7F6E\uFF1B\u8BF7\u5728\u5BBF\u4E3B\u4FA7\u8BBE\u7F6E\u53D6\u6D88\u5B88\u536B\u3002", []);
149
+ }
150
+ };
151
+ const sync = connectChildNavigation(routing, (loc) => ready.then(() => apply(loc)), report);
152
+ const init = routing.getLocation();
153
+ const ready = push.call(router, init.pathname + init.search + init.hash).then(async (failure) => {
154
+ if (disposed) return;
155
+ if (routing.getLocation().pathname + routing.getLocation().search + routing.getLocation().hash !== init.pathname + init.search + init.hash) {
156
+ await apply(routing.getLocation());
157
+ return;
158
+ }
159
+ if (failure) throw routingSyncError("vue-router", "\u5B50\u5E94\u7528\u521D\u59CB\u8DEF\u7531\u88AB\u5B88\u536B\u53D6\u6D88\u3002", []);
160
+ const target = locationFromPath(router.currentRoute.value.fullPath);
161
+ if (router.currentRoute.value.fullPath !== init.pathname + init.search + init.hash) {
162
+ await routing.navigate(target, "replace");
163
+ await apply(routing.getLocation());
164
+ }
165
+ }).catch((cause) => {
166
+ throw routingSyncError("vue-router", `\u521D\u59CB\u8DEF\u7531\u51C6\u5907\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause);
167
+ });
168
+ const intercept = (native, action) => (to) => {
169
+ const task = (async () => {
170
+ let failure;
171
+ const result = await sync.enqueue(async () => {
172
+ await ready;
173
+ failure = await native.call(router, to);
174
+ return failure ? void 0 : locationFromPath(router.currentRoute.value.fullPath);
175
+ }, typeof to === "object" && to.replace ? "replace" : action);
176
+ if (failure) return failure;
177
+ if (result.status === "cancelled") {
178
+ const remove = router.beforeEach(() => false);
179
+ try {
180
+ return await native.call(router, to);
181
+ } finally {
182
+ remove();
183
+ }
184
+ }
185
+ })();
186
+ void task.catch(report);
187
+ return task;
188
+ };
189
+ router.push = intercept(push, "push");
190
+ router.replace = intercept(replace, "replace");
191
+ router.go = (delta) => {
192
+ if (!disposed) routing.go(delta);
193
+ };
194
+ router.back = () => router.go(-1);
195
+ router.forward = () => router.go(1);
196
+ const connection = {
197
+ ready,
198
+ dispose() {
199
+ if (disposed) return;
200
+ disposed = true;
201
+ options.signal?.removeEventListener("abort", connection.dispose);
202
+ sync.dispose();
203
+ router.push = push;
204
+ router.replace = replace;
205
+ router.go = go;
206
+ router.back = back;
207
+ router.forward = forward;
208
+ }
209
+ };
210
+ options.signal?.addEventListener("abort", connection.dispose, { once: true });
211
+ if (options.signal?.aborted) connection.dispose();
212
+ return connection;
213
+ }
214
+ export {
215
+ connectVueBridgeRouter,
216
+ createVueBridgeNavigation
217
+ };
@@ -1,6 +1,6 @@
1
1
  import * as vue from 'vue';
2
- import { V as VueBridgeAppOptions } from './bridge-host-vue-BAxDKqGH.js';
3
- export { A as AppContext, B as BridgeApp } from './bridge-core-6uLJiMVa.js';
2
+ import { V as VueBridgeAppOptions } from './bridge-host-vue-Dd1kkwDb.js';
3
+ export { A as AppContext, a as BridgeApp } from './bridge-core-ejlY9ddN.js';
4
4
 
5
5
  /** Vue 宿主桥接工厂:返回 `{ appProps: P; sessionKey?: string | null }` 形态的包装组件 */
6
6
  declare const createVueBridgeApp: <P = Record<string, unknown>>(spec: string, options?: VueBridgeAppOptions) => vue.Component;
package/dist/bridge.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as react from 'react';
2
- import { R as ReactBridgeAppOptions } from './bridge-host-react-C55RmGll.js';
3
- export { B as BridgeErrorFallback } from './bridge-host-react-C55RmGll.js';
2
+ import { B as BridgeHostRouting } from './bridge-core-ejlY9ddN.js';
3
+ export { A as AppContext, a as BridgeApp } from './bridge-core-ejlY9ddN.js';
4
+ import { R as ReactBridgeAppOptions } from './bridge-host-react-Bua1TCVh.js';
5
+ export { B as BridgeErrorFallback } from './bridge-host-react-Bua1TCVh.js';
4
6
  import * as vue from 'vue';
5
- import { V as VueBridgeAppOptions } from './bridge-host-vue-BAxDKqGH.js';
6
- export { A as AppContext, B as BridgeApp } from './bridge-core-6uLJiMVa.js';
7
+ import { V as VueBridgeAppOptions } from './bridge-host-vue-Dd1kkwDb.js';
7
8
 
8
9
  /** Vue 宿主桥接工厂:返回 `{ appProps: P; sessionKey?: string | null }` 形态的包装组件 */
9
10
  declare const createVueBridgeApp: <P = Record<string, unknown>>(spec: string, options?: VueBridgeAppOptions) => vue.Component;
@@ -11,6 +12,7 @@ declare const createVueBridgeApp: <P = Record<string, unknown>>(spec: string, op
11
12
  declare const createReactBridgeApp: <P = Record<string, unknown>>(spec: string, options?: ReactBridgeAppOptions) => react.ComponentType<{
12
13
  appProps?: P;
13
14
  sessionKey?: string | null;
15
+ routing?: BridgeHostRouting;
14
16
  }>;
15
17
 
16
18
  export { ReactBridgeAppOptions, VueBridgeAppOptions, createReactBridgeApp, createVueBridgeApp };