@fulgurjs/federation 5.3.2 → 5.4.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/DESIGN.md +4 -0
  3. package/README.en.md +26 -4
  4. package/README.md +85 -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-BOWvVUxO.d.ts +91 -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 +55 -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-CMVd-ym0.d.ts} +1 -1
  23. package/dist/bridge-host-react.cjs +324 -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-BuCGlLdS.d.ts} +1 -1
  28. package/dist/bridge-host-vue.cjs +335 -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-BxBx-reP.d.cts +44 -0
  32. package/dist/bridge-router-core-BxBx-reP.d.ts +44 -0
  33. package/dist/bridge-router-react.d.ts +96 -0
  34. package/dist/bridge-router-react.js +70 -0
  35. package/dist/bridge-router-vue.d.ts +75 -0
  36. package/dist/bridge-router-vue.js +73 -0
  37. package/dist/bridge-vue.d.ts +2 -2
  38. package/dist/bridge.d.ts +6 -4
  39. package/dist/{chunk-TOT3P7AK.js → chunk-BTJGYDHT.js} +412 -27
  40. package/dist/{chunk-MMXKQN2H.js → chunk-GF6KOH67.js} +39 -4
  41. package/dist/{chunk-DH2QMM4Y.js → chunk-JQF7KYZF.js} +50 -2
  42. package/dist/{chunk-X4F2BHNM.js → chunk-OXEAGRPY.js} +27 -3
  43. package/dist/{chunk-LE2G23E7.js → chunk-PRUYHYX2.js} +1 -1
  44. package/dist/chunk-WOTR2FUQ.js +266 -0
  45. package/dist/index.cjs +136 -15
  46. package/dist/index.js +136 -15
  47. package/dist/react.d.ts +56 -4
  48. package/dist/runtime-entry.d.ts +65 -4
  49. package/dist/runtime.js +18 -18
  50. package/examples/bridge/react-host/package-lock.json +107 -106
  51. package/examples/bridge/react-host/package.json +1 -1
  52. package/examples/bridge/react-remote/package-lock.json +107 -106
  53. package/examples/bridge/react-remote/package.json +1 -1
  54. package/examples/bridge/vue-host/package-lock.json +107 -106
  55. package/examples/bridge/vue-host/package.json +1 -1
  56. package/examples/bridge/vue-remote/package-lock.json +107 -106
  57. package/examples/bridge/vue-remote/package.json +1 -1
  58. package/examples/react/host/package-lock.json +3 -3
  59. package/examples/react/host/package.json +1 -1
  60. package/examples/react/remote/package-lock.json +3 -3
  61. package/examples/react/remote/package.json +1 -1
  62. package/examples/vue/host/package-lock.json +3 -3
  63. package/examples/vue/host/package.json +1 -1
  64. package/examples/vue/remote/package-lock.json +3 -3
  65. package/examples/vue/remote/package.json +1 -1
  66. package/package.json +28 -4
  67. package/dist/bridge-core-6uLJiMVa.d.ts +0 -42
@@ -0,0 +1,96 @@
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'): Promise<BridgeNavigationResult>;
22
+ go(delta: number): void;
23
+ }
24
+ /** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
25
+ interface BridgeChildRoute {
26
+ getLocation(): BridgeLocation;
27
+ subscribe(listener: (location: BridgeLocation) => void): () => void;
28
+ navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
29
+ go(delta: number): void;
30
+ }
31
+
32
+ /**
33
+ * React Router 路由适配(/bridge/router/react 按需入口;URL 同步任务书 §3.1/§3.3)。
34
+ *
35
+ * 实际支持模式(原型实证,任务书 §1 结论 2):
36
+ * - 宿主:data router(createBrowserRouter / createHashRouter + RouterProvider)。
37
+ * router.navigate 被 blocker 拦截时静默返回 void(dist 源码核实)——取消用
38
+ * 「与 useBlocker 同一取消策略预判」:canNavigate(target) 返回 false 即 cancelled,
39
+ * URL/历史零副作用;菜单/POP 等其他来源仍由树内 useBlocker 真实拦截。
40
+ * - 子应用:createMemoryRouter(RR 6.11+)受控路由——Link/useNavigate 全兼容,
41
+ * 通道广播与本地导航双向桥接,取消时乐观回滚。
42
+ * - declarative 模式(BrowserRouter)无 blocker/提交语义,不支持 URL 同步(文档声明)。
43
+ */
44
+
45
+ /** 宿主取消策略(与树内 useBlocker 共用同一谓词,单一事实源) */
46
+ type ReactBridgeCancelPolicy = (next: BridgeLocation) => boolean;
47
+ /** data router 的最小结构面(避免耦合 RR 内部类型版本) */
48
+ interface ReactDataRouterLike {
49
+ state: {
50
+ location: {
51
+ pathname: string;
52
+ search: string;
53
+ hash: string;
54
+ };
55
+ };
56
+ subscribe(cb: () => void): () => void;
57
+ navigate(to: string | number, opts?: {
58
+ replace?: boolean;
59
+ }): void | Promise<void>;
60
+ }
61
+ /**
62
+ * 宿主侧:把 RR data router 包装为宿主导航端口。
63
+ *
64
+ * ```tsx
65
+ * const router = createBrowserRouter(routes, { basename })
66
+ * const navigation = createReactBridgeNavigation(router, {
67
+ * basename, // = createBrowserRouter 的 basename(含它;RR location.pathname 不剥 basename)
68
+ * canNavigate: (next) => !next.pathname.startsWith('/approval/secret'), // 与 useBlocker 同一谓词
69
+ * })
70
+ * ```
71
+ */
72
+ declare function createReactBridgeNavigation(router: ReactDataRouterLike, options?: {
73
+ canNavigate?: ReactBridgeCancelPolicy;
74
+ basename?: string;
75
+ }): BridgeHostNavigation;
76
+ interface ReactBridgeRouterConnection {
77
+ /** RouterProvider 元素:子应用在自己的 root 内渲染 */
78
+ element: ReactElement;
79
+ /** 清理本接线(通道订阅与本地监听;通道本身由宿主生命周期销毁) */
80
+ dispose(): void;
81
+ }
82
+ /**
83
+ * 子应用侧:把子应用自己的 routes 接到通道,返回 RouterProvider 元素。
84
+ *
85
+ * ```tsx
86
+ * export default defineBridgeApp((props, ctx) => {
87
+ * const conn = createReactBridgeRouter(ctx.routing, routes)
88
+ * const root = createRoot(el)
89
+ * root.render(conn.element)
90
+ * return { mount: () => {}, unmount: () => { conn.dispose(); root.unmount() } }
91
+ * }, { routing: true })
92
+ * ```
93
+ */
94
+ declare function createReactBridgeRouter(routing: BridgeChildRoute, routes: RouteObject[]): ReactBridgeRouterConnection;
95
+
96
+ export { type ReactBridgeCancelPolicy, type ReactBridgeRouterConnection, type ReactDataRouterLike, createReactBridgeNavigation, createReactBridgeRouter };
@@ -0,0 +1,70 @@
1
+ // src/bridge-router-react.tsx
2
+ import { createElement } from "react";
3
+ import { RouterProvider, createMemoryRouter } from "react-router-dom";
4
+ function createReactBridgeNavigation(router, options = {}) {
5
+ const routerBase = options.basename && options.basename !== "/" ? options.basename.replace(/\/$/, "") : "";
6
+ const toLogic = () => {
7
+ const l = router.state.location;
8
+ const pathname = routerBase && l.pathname.startsWith(routerBase) ? l.pathname.slice(routerBase.length) || "/" : l.pathname;
9
+ return { pathname, search: l.search, hash: l.hash };
10
+ };
11
+ return {
12
+ getLocation: toLogic,
13
+ subscribe(listener) {
14
+ return router.subscribe(() => listener(toLogic()));
15
+ },
16
+ async navigate(target, action) {
17
+ if (options.canNavigate && !options.canNavigate(target)) {
18
+ return { status: "cancelled", location: toLogic() };
19
+ }
20
+ await router.navigate((routerBase && routerBase !== "/" ? routerBase : "") + target.pathname + target.search + target.hash, { replace: action === "replace" });
21
+ return { status: "committed", location: toLogic() };
22
+ },
23
+ go(delta) {
24
+ void router.navigate(delta);
25
+ }
26
+ };
27
+ }
28
+ function createReactBridgeRouter(routing, routes) {
29
+ const init = routing.getLocation();
30
+ const router = createMemoryRouter(routes, {
31
+ initialEntries: [init.pathname + init.search + init.hash]
32
+ });
33
+ let syncing = false;
34
+ let disposed = false;
35
+ const unsub = routing.subscribe((loc) => {
36
+ if (disposed) return;
37
+ const next = loc.pathname + loc.search + loc.hash;
38
+ const cur = router.state.location;
39
+ if (next !== cur.pathname + cur.search + cur.hash) void router.navigate(next, { replace: true });
40
+ });
41
+ const unwatch = router.subscribe(() => {
42
+ if (disposed || syncing) return;
43
+ const cur = router.state.location;
44
+ const chan = routing.getLocation();
45
+ if (cur.pathname + cur.search + cur.hash === chan.pathname + chan.search + chan.hash) return;
46
+ syncing = true;
47
+ void routing.navigate({ pathname: cur.pathname, search: cur.search, hash: cur.hash }, "push").then((result) => {
48
+ syncing = false;
49
+ if (result.status === "cancelled" && !disposed) {
50
+ const c = result.location;
51
+ const next = c.pathname + c.search + c.hash;
52
+ const cur2 = router.state.location;
53
+ if (next !== cur2.pathname + cur2.search + cur2.hash) void router.navigate(next, { replace: true });
54
+ }
55
+ });
56
+ });
57
+ return {
58
+ element: createElement(RouterProvider, { router }),
59
+ dispose() {
60
+ if (disposed) return;
61
+ disposed = true;
62
+ unwatch();
63
+ unsub();
64
+ }
65
+ };
66
+ }
67
+ export {
68
+ createReactBridgeNavigation,
69
+ createReactBridgeRouter
70
+ };
@@ -0,0 +1,75 @@
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'): Promise<BridgeNavigationResult>;
21
+ go(delta: number): void;
22
+ }
23
+ /** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
24
+ interface BridgeChildRoute {
25
+ getLocation(): BridgeLocation;
26
+ subscribe(listener: (location: BridgeLocation) => void): () => void;
27
+ navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
28
+ go(delta: number): void;
29
+ }
30
+
31
+ /**
32
+ * Vue Router 4 路由适配(/bridge/router/vue 按需入口;URL 同步任务书 §3.1/§3.3)。
33
+ *
34
+ * 宿主侧:createVueBridgeNavigation(router) 把 Vue Router 实例包装为宿主导航端口——
35
+ * push/replace 落定 NavigationFailure | undefined,failure 即真实取消(守卫拒绝),
36
+ * 不以「函数返回」冒充提交成功。
37
+ *
38
+ * 子应用侧:connectVueBridgeRouter(routing, router) 把子应用自己的 memory router
39
+ * 接到通道上——RouterLink/router.push 等真实入口经 afterEach 桥为通道请求(宿主仲裁,
40
+ * 取消时回滚最后确认位置);通道广播 replace 应用到本地 router。
41
+ * 初始位置已由通道就绪;`ready` 落定前 afterEach 不发起请求(原型实证:install 的
42
+ * 初始导航竞态,见任务书 §1 原型结论 1)。
43
+ *
44
+ * 本入口按需导入 vue-router(peerDependenciesMeta.optional);默认 /bridge、/runtime、
45
+ * /react 不加载本模块。
46
+ */
47
+
48
+ /** Vue Router base(createWebHistory/createWebHashHistory 的 base 参数);逻辑路径 = 实际路径去 base */
49
+ interface VueBridgeNavigationOptions {
50
+ routerBase?: string;
51
+ }
52
+ /** 宿主侧:把 Vue Router 实例包装为宿主导航端口(宿主 Router 是唯一历史写入方) */
53
+ declare function createVueBridgeNavigation(router: Router, options?: VueBridgeNavigationOptions): BridgeHostNavigation;
54
+ interface VueBridgeRouterConnection {
55
+ /** 初始位置就绪(含初始 push 与首次广播应用);ready 前后的清理都必须可用 */
56
+ ready: Promise<void>;
57
+ /** 清理本接线(通道订阅与本地监听;通道本身由宿主生命周期销毁) */
58
+ dispose(): void;
59
+ }
60
+ /**
61
+ * 子应用侧:把子应用自己的 memory router 接到通道。
62
+ *
63
+ * ```ts
64
+ * export default defineBridgeApp((props, ctx) => {
65
+ * const router = createRouter({ history: createMemoryHistory(), routes })
66
+ * const conn = connectVueBridgeRouter(ctx.routing, router)
67
+ * const app = createApp(App, props)
68
+ * app.use(router)
69
+ * return app
70
+ * }, { routing: true })
71
+ * ```
72
+ */
73
+ declare function connectVueBridgeRouter(routing: BridgeChildRoute, router: Router): VueBridgeRouterConnection;
74
+
75
+ export { type VueBridgeNavigationOptions, type VueBridgeRouterConnection, connectVueBridgeRouter, createVueBridgeNavigation };
@@ -0,0 +1,73 @@
1
+ // src/bridge-router-vue.ts
2
+ function createVueBridgeNavigation(router, options = {}) {
3
+ const routerBase = options.routerBase && options.routerBase !== "/" ? options.routerBase.replace(/\/$/, "") : "";
4
+ const toLogic = () => {
5
+ const raw = new URL(router.currentRoute.value.fullPath || "/", "http://f.invalid");
6
+ const pathname = routerBase && raw.pathname.startsWith(routerBase) ? raw.pathname.slice(routerBase.length) || "/" : raw.pathname;
7
+ return { pathname, search: raw.search, hash: raw.hash };
8
+ };
9
+ return {
10
+ getLocation: toLogic,
11
+ subscribe(listener) {
12
+ return router.afterEach(() => listener(toLogic()));
13
+ },
14
+ async navigate(target, action) {
15
+ const to = target.pathname + target.search + target.hash;
16
+ try {
17
+ const failure = action === "replace" ? await router.replace(to) : await router.push(to);
18
+ if (failure) return { status: "cancelled", location: toLogic() };
19
+ return { status: "committed", location: toLogic() };
20
+ } catch {
21
+ return { status: "cancelled", location: toLogic() };
22
+ }
23
+ },
24
+ go(delta) {
25
+ router.go(delta);
26
+ }
27
+ };
28
+ }
29
+ function connectVueBridgeRouter(routing, router) {
30
+ let syncing = false;
31
+ let disposed = false;
32
+ let started = false;
33
+ const unsub = routing.subscribe((loc) => {
34
+ if (disposed) return;
35
+ const next = loc.pathname + loc.search + loc.hash;
36
+ if (router.currentRoute.value.fullPath !== next) void router.replace(next);
37
+ });
38
+ const stopWatch = router.afterEach((to) => {
39
+ if (disposed || syncing || !started) return;
40
+ const chan = routing.getLocation();
41
+ const toFull = to.fullPath;
42
+ if (toFull === chan.pathname + chan.search + chan.hash) return;
43
+ const raw = new URL(toFull, "http://f.invalid");
44
+ syncing = true;
45
+ void routing.navigate({ pathname: raw.pathname, search: raw.search, hash: raw.hash }, "push").then((result) => {
46
+ syncing = false;
47
+ if (result.status === "cancelled" && !disposed) {
48
+ const c = result.location;
49
+ const cur = c.pathname + c.search + c.hash;
50
+ if (router.currentRoute.value.fullPath !== cur) void router.replace(cur);
51
+ }
52
+ });
53
+ });
54
+ const init = routing.getLocation();
55
+ const ready = router.push(init.pathname + init.search + init.hash).then(() => {
56
+ started = true;
57
+ }).catch(() => {
58
+ started = true;
59
+ });
60
+ return {
61
+ ready,
62
+ dispose() {
63
+ if (disposed) return;
64
+ disposed = true;
65
+ stopWatch();
66
+ unsub();
67
+ }
68
+ };
69
+ }
70
+ export {
71
+ connectVueBridgeRouter,
72
+ createVueBridgeNavigation
73
+ };
@@ -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-BuCGlLdS.js';
3
+ export { A as AppContext, a as BridgeApp } from './bridge-core-BOWvVUxO.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-BOWvVUxO.js';
3
+ export { A as AppContext, a as BridgeApp } from './bridge-core-BOWvVUxO.js';
4
+ import { R as ReactBridgeAppOptions } from './bridge-host-react-CMVd-ym0.js';
5
+ export { B as BridgeErrorFallback } from './bridge-host-react-CMVd-ym0.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-BuCGlLdS.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 };