@fulgurjs/federation 5.3.3 → 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.
- package/CHANGELOG.md +9 -0
- package/DESIGN.md +4 -0
- package/README.en.md +23 -3
- package/README.md +83 -1
- package/dist/bridge-app-react.cjs +14 -4
- package/dist/bridge-app-react.d.cts +16 -4
- package/dist/bridge-app-react.d.ts +16 -4
- package/dist/bridge-app-react.js +6 -4
- package/dist/bridge-app-vue.cjs +55 -9
- package/dist/bridge-app-vue.d.cts +25 -4
- package/dist/bridge-app-vue.d.ts +25 -4
- package/dist/bridge-app-vue.js +47 -9
- package/dist/bridge-core-BOWvVUxO.d.ts +91 -0
- package/dist/bridge-core.cjs +9 -1
- package/dist/bridge-core.d.cts +8 -1
- package/dist/bridge-core.d.ts +8 -1
- package/dist/bridge-core.js +2 -2
- package/dist/bridge-errors.cjs +55 -3
- package/dist/bridge-errors.d.cts +18 -2
- package/dist/bridge-errors.d.ts +18 -2
- package/dist/bridge-errors.js +11 -3
- package/dist/{bridge-host-react-C55RmGll.d.ts → bridge-host-react-CMVd-ym0.d.ts} +1 -1
- package/dist/bridge-host-react.cjs +324 -3
- package/dist/bridge-host-react.d.cts +2 -0
- package/dist/bridge-host-react.d.ts +2 -0
- package/dist/bridge-host-react.js +30 -4
- package/dist/{bridge-host-vue-BAxDKqGH.d.ts → bridge-host-vue-BuCGlLdS.d.ts} +1 -1
- package/dist/bridge-host-vue.cjs +335 -3
- package/dist/bridge-host-vue.js +42 -5
- package/dist/bridge-react.d.ts +5 -3
- package/dist/bridge-router-core-BxBx-reP.d.cts +44 -0
- package/dist/bridge-router-core-BxBx-reP.d.ts +44 -0
- package/dist/bridge-router-react.d.ts +96 -0
- package/dist/bridge-router-react.js +70 -0
- package/dist/bridge-router-vue.d.ts +75 -0
- package/dist/bridge-router-vue.js +73 -0
- package/dist/bridge-vue.d.ts +2 -2
- package/dist/bridge.d.ts +6 -4
- package/dist/{chunk-WD2ON7SN.js → chunk-BTJGYDHT.js} +303 -2
- package/dist/{chunk-GWJKABE7.js → chunk-GF6KOH67.js} +39 -4
- package/dist/{chunk-DH2QMM4Y.js → chunk-JQF7KYZF.js} +50 -2
- package/dist/{chunk-ZTRJLCOF.js → chunk-OXEAGRPY.js} +27 -3
- package/dist/{chunk-LE2G23E7.js → chunk-PRUYHYX2.js} +1 -1
- package/dist/chunk-WOTR2FUQ.js +266 -0
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/react.d.ts +51 -4
- package/dist/runtime-entry.d.ts +60 -4
- package/dist/runtime.js +1 -1
- package/examples/bridge/react-host/package-lock.json +105 -104
- package/examples/bridge/react-remote/package-lock.json +105 -104
- package/examples/bridge/vue-host/package-lock.json +105 -104
- package/examples/bridge/vue-remote/package-lock.json +105 -104
- package/examples/react/host/package-lock.json +1 -1
- package/examples/react/remote/package-lock.json +1 -1
- package/examples/vue/host/package-lock.json +1 -1
- package/examples/vue/remote/package-lock.json +1 -1
- package/package.json +28 -4
- package/dist/bridge-core-6uLJiMVa.d.ts +0 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.4.0
|
|
4
|
+
|
|
5
|
+
- **新增:跨框架桥接 URL 同步(`/bridge/router/*`)**——宿主 URL 表达子应用内部位置:首次深链直达(不闪默认页、不发错接口)、刷新/收藏/新窗口恢复、子应用 Link/RouterLink/router.push 与宿主菜单/前进后退全量同步、根重定向以 replace 规范化(不凭空制造历史)。架构:宿主 Router 是浏览器历史唯一写入方,子应用用受控 memory 路由(Vue `connectVueBridgeRouter` / React `createReactBridgeRouter`,Link/useNavigate/RouterLink 全兼容);独立路由通道承载位置与仲裁(请求编号/代次隔离/外部作废/取消恢复),path/search/hash 三段原样保留(重复键/编码/中文/片段不二次转换),同实例路径变化**不重挂 root、不重建 store、不重载远程**。
|
|
6
|
+
- **启用即校验,默认关闭**:`routing` prop(`{ basePath, navigation }`)显式开启;子应用 `defineBridgeApp(工厂, { routing: true })` 声明协议(工厂第二参数 `{ signal, routing }`,Vue 工厂支持 Promise 形态——初始 memory push 落定后必须再 `app.use(router)`,install 初始导航会覆盖深链位置,原型实证)。未声明协议而宿主启用 → `MFU-031`(不静默退回 memory);basePath 非法/前缀冲突 → `MFU-030`;越界导航/非法 go → `MFU-032`;重定向环 → `MFU-033`(附目标链)。
|
|
7
|
+
- **取消语义(真实框架行为)**:Vue Router 4 push/replace 落定 NavigationFailure 即真实取消(URL/历史/子应用位置保持确认态,不自动重试);React Router 仅支持 data router(`createBrowserRouter/createHashRouter`),`canNavigate` 与树内 `useBlocker` 同谓词——RR 的 navigate 被 blocker 拦截时静默返回 void(dist 源码核实),故端口预判取消零副作用。RR `location.pathname` 含 basename,端口新增 `basename` 选项剥部署前缀(§2.3 分层,生产子目录部署实测抓出)。
|
|
8
|
+
- **会话与生命周期**:换账号/登出作废旧通道(旧导航 cancelled、不写 URL、不复活子应用);unmount 后迟到通知失效;KeepAlive 离页暂停写入(不抢占 URL、不销毁共享端口);卸载失败容器封锁语义不变。前缀按路径段匹配并在页面级登记互斥。
|
|
9
|
+
- **包接线**:新增 `./bridge/router/vue`、`./bridge/router/react` 按需入口(vue-router ≥4.1 / react-router-dom ≥6.11 为可选 peer);默认 `/bridge`、`/runtime`、`/react` 零路由库导入(导入图门禁覆盖);两个路由入口各 ≤4096B gzip 门禁;错误码 44 → 48(三方一致)。宿主两入口 gzip:bridge-host-vue 3103B / bridge-host-react 2575B(仍 ≤4096B)。
|
|
10
|
+
- **质量门禁**:原型门禁 27/27(双向 + hash 变体,可控竞态断言);单测 566 → 589(路由内核/双端适配/宿主 routing 集成 23 例);e2e 新增 bridge-router 双向 dev 13 例 + 生产 U08(真实 nginx 回退深链刷新 + 资源 404 不被掩盖);全量 e2e dev 43 + prod 28 + 桥接六项目 33 全绿。**已知开发态边界**:workspace link(pnpm 双虚拟仓库)下宿主/子各持一份 react-router-dom 物理副本会断 Router context——fixtures 以 `resolve.alias` 统一副本(README 8.8/8.3 说明;registry 正式包消费无此问题)。
|
|
11
|
+
|
|
3
12
|
## 5.3.3
|
|
4
13
|
|
|
5
14
|
- **修复:loadShare × pinLoadedShare 并发窗口的单例双实例风险(F9)**——`loadShare` 设置 `entry.loaded` 后等待 `entry.get()` 期间,CJS 垫片的 `pinLoadedShare` 可同步写入本地实例;getter 完成后原实现直接覆盖 `entry.value`,同步 CJS 消费者与异步消费者各持一份实例。现在实行条目实例 first-wins(协商结果让位于先写入的 pin/fallback 实例,loadShare 返回条目当前 value)、条目级 in-flight get(并发 loadShare 共享同一次加载,getter 恰好调用一次)、getter 拒绝后回滚 `entry.loaded`(修复前死条目残留「已加载」标记会永久抢占单例的已加载优先)——失败窗口内已有 pin 时收敛到 pin 实例。8 个确定性回归(可控 Promise 门)覆盖挂起中 pin / pin 先行 / 并发去重 / 拒绝重试与状态恢复 / resolveShare 组合 / 多版本边界;单元 558 → 566。`runtime.js` gzip 9204B ≤ 9216B。
|
package/DESIGN.md
CHANGED
|
@@ -207,3 +207,7 @@ remote 样式改动立即生效;host 自身业务 HMR 不受影响。
|
|
|
207
207
|
- M2(dev 闭环)验收加:副本三 dev server 实测
|
|
208
208
|
- M3(shared 全语义)验收加:element-plus 双版本共存(bpm 2.9.1 vs lowcode 2.10.2)实测
|
|
209
209
|
- M6 验收加:NGINX 测试站点 prod 全量 e2e 绿 + 后台接口经反代连通
|
|
210
|
+
|
|
211
|
+
## 6.3 桥接 URL 同步(5.4.0)
|
|
212
|
+
|
|
213
|
+
宿主 Router 是浏览器历史的唯一写入方;子应用运行受控 memory 路由。两者以「路由通道」相连:通道由桥接宿主按挂载代次创建(一个同步实例一条),随 mount 第三参数交给子应用,随 unmount/换代销毁。通道职责:位置三段(path/search/hash)的原样换算(前缀按段匹配、页面级登记互斥)、子应用导航请求的串行化与编号、宿主裁决仲裁(committed/cancelled/superseded)、有界重定向检测。宿主端口(Vue `createVueBridgeNavigation` / React `createReactBridgeNavigation`)只做 Router→端口的语义换算:Vue 直接复用 push/replace 的 NavigationFailure 作为真实取消;React data router 用 canNavigate 预判(与 useBlocker 同谓词)+ 树内 blocker 保护其他来源。KeepAlive 用通道的 active 开关暂停/恢复写入而非销毁。路由能力全部住在按需入口(`/bridge/router/*`),默认入口零路由库导入。
|
package/README.en.md
CHANGED
|
@@ -261,7 +261,7 @@ Type generation supports string or array `extends` (later entries override earli
|
|
|
261
261
|
|
|
262
262
|
### 8.7 Cross-framework bridge — `/bridge` (sub-app-level Vue↔React, 5.3.0+)
|
|
263
263
|
|
|
264
|
-
**Scope**: whole-app mount/unmount embedding both ways — a Vue 3 host mounts a React 18/19 sub-app, and a React host mounts a Vue 3 sub-app. Component-level conversion,
|
|
264
|
+
**Scope**: whole-app mount/unmount embedding both ways — a Vue 3 host mounts a React 18/19 sub-app, and a React host mounts a Vue 3 sub-app. Component-level conversion, Angular, SSR/RSC, JS sandbox, CSS isolation are out of scope (§12). Sub-app internal route ↔ browser URL sync is available since 5.4.0 (§8.8).
|
|
265
265
|
|
|
266
266
|
#### Entries & import graph
|
|
267
267
|
|
|
@@ -337,6 +337,22 @@ const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', { getConte
|
|
|
337
337
|
- **Controlled `sessionKey`**: accepts `undefined` (no controlled validation) / `null` (logged out: unmount immediately, keep the container empty, stop loading) / non-empty string (login generation). Illegal values → `MFU-017`.
|
|
338
338
|
- **Multi-instance**: several same-spec instances coexist (per-el keying); `AppContext` is a page-level singleton — all controlled instances on a page must share the same session (`MFU-017` otherwise). React StrictMode double-effect is safe. Vue `<KeepAlive>` deactivation is **not** an unmount. Late results from invalidated generations are dropped by generation guards; a remote `onSession` must honor the existing `signal.aborted` contract.
|
|
339
339
|
|
|
340
|
+
### 8.8 Bridge URL sync — `/bridge/router/*` (sub-app internal routes ↔ browser URL, 5.4.0+)
|
|
341
|
+
|
|
342
|
+
The bridge defaults to memory routing: internal navigation does not touch the browser URL and refresh cannot restore the sub-app's internal page. URL sync makes the **host URL express the sub-app's internal location** — deep links, refresh, bookmarks, back/forward and host-menu navigation all agree. It is opt-in; **default is off** (5.3.x behavior and legacy contracts unchanged).
|
|
343
|
+
|
|
344
|
+
**Architecture**: the host router is the only writer of browser history; the sub-app uses a controlled memory router; both sides communicate over a dedicated routing channel (not appProps/Context); path/search/hash changes within an instance **do not remount the root, do not rebuild stores, do not reload the remote**.
|
|
345
|
+
|
|
346
|
+
**Host (Vue Router 4, history or hash mode)**: declare a suffix route (`/approval/:pathMatch(.*)*` — without it detail navigation unmounts the sub-app), add real guards (`beforeEach` rejecting → the channel receives `cancelled`, URL/history/sub-app position unchanged), then `createVueBridgeNavigation(router, { routerBase })` and pass `routing={{ basePath: '/approval', navigation }}` to the bridge component.
|
|
347
|
+
|
|
348
|
+
**Sub-app**: declare the protocol and wire a controlled router —
|
|
349
|
+
Vue: `defineBridgeApp(async (props, ctx) => { const router = createRouter({ history: createMemoryHistory(), routes }); await connectVueBridgeRouter(ctx.routing!, router).ready; ... app.use(router); return app }, { routing: true })` (await ready BEFORE `app.use(router)` — the install-time initial navigation would otherwise override the deep-link location).
|
|
350
|
+
React: `createReactBridgeRouter(ctx.routing!, routes).element` — `createMemoryRouter`-based; `Link`/`useNavigate` work unmodified.
|
|
351
|
+
|
|
352
|
+
**Host (React Router)**: data routers only (`createBrowserRouter`/`createHashRouter` + `RouterProvider`); `createReactBridgeNavigation(router, { basename, canNavigate })`. Pass the same predicate to `canNavigate` and to an in-tree `useBlocker` — sub-app-originated navigations are cancelled by the port pre-check (zero URL/history side effects), menu/POP sources are blocked by the real blocker. Declarative `BrowserRouter` has no cancellation semantics and is not supported. Requires react-router ≥ 6.11.
|
|
353
|
+
|
|
354
|
+
**Contract highlights**: `basePath` is a static absolute path from the host-router perspective (segment-matched; conflicting/overlapping prefixes rejected, `MFU-030`); location is compared and preserved as three raw strings (duplicate query keys, encoding, fragments survive without re-encoding); cancellation never auto-retries; session switch (`sessionKey→null`) invalidates the old channel — late navigations are rejected and never write the URL; KeepAlive-cached instances pause routing writes; enabling sync against a contract without `{ routing: true }` shows `MFU-031` instead of silently falling back to memory; escaping targets and illegal `go` arguments → `MFU-032`; redirect loops beyond 5 internal replaces → `MFU-033` with the chain attached. Router libraries are optional peers consumed only through the two opt-in entries (`/bridge/router/vue`, `/bridge/router/react`, each gated ≤ 4096B gzip); the default entries never load a router library. Not promised: SSR/RSC, cross-window, nested multi-level bridge routing proxies, TanStack Router and other libraries (extend via the `BridgeHostNavigation`/`BridgeChildRoute` ports).
|
|
355
|
+
|
|
340
356
|
## 9. Artifacts, endpoints & caching
|
|
341
357
|
|
|
342
358
|
| Artifact | Cache policy |
|
|
@@ -355,7 +371,7 @@ Lazy-loading measurement layers: ① nothing until first render of a remote comp
|
|
|
355
371
|
- `DEBUG=fulgurjs:*` — controlled pipeline diagnostics (off by default)
|
|
356
372
|
- Runtime diagnostics are emitted in Chinese by design (language policy); codes are stable identifiers listed below
|
|
357
373
|
|
|
358
|
-
## 11. Error codes (
|
|
374
|
+
## 11. Error codes (48)
|
|
359
375
|
|
|
360
376
|
| Segment | Code | Meaning |
|
|
361
377
|
|---|---|---|
|
|
@@ -401,13 +417,17 @@ Lazy-loading measurement layers: ① nothing until first render of a remote comp
|
|
|
401
417
|
| | `MFU-015` | bridge contract invalid (`./bridge` default export missing non-function mount/unmount; fix points to `defineBridgeApp`) |
|
|
402
418
|
| | `MFU-016` | bridge preparation or lifecycle failure (`details.phase` = getContext/mount/unmount; cause keeps the sub-app's original error) |
|
|
403
419
|
| | `MFU-017` | bridge session mismatch (controlled sessionKey vs AppContext / illegal value / page-level single-session conflict) |
|
|
420
|
+
| MFU | `MFU-030` | Bridge URL-sync config invalid / prefix conflict (illegal basePath: empty, root, query/hash/wildcard; overlapping active prefixes) |
|
|
421
|
+
| MFU | `MFU-031` | Bridge routing protocol missing / channel destroyed (sub-app not declared with `{ routing: true }`; disposed channel reused) |
|
|
422
|
+
| MFU | `MFU-032` | Bridge illegal navigation (target escaping its own prefix, illegal `go` argument, request on a dead channel) |
|
|
423
|
+
| MFU | `MFU-033` | Bridge routing sync failed (internal replace chain exceeds the bound of 5, chain attached; no silent fallback to memory) |
|
|
404
424
|
| CC | `CC-001` | AppContext required key missing (got/expected/example) |
|
|
405
425
|
| | `CC-002` | runtime singleton unavailable (standalone remote page) |
|
|
406
426
|
|
|
407
427
|
## 12. Boundaries (explicitly not supported)
|
|
408
428
|
|
|
409
429
|
- Support covers **browser-client** federation for Vue 3 and React 18–19. Not supported: SSR, React Server Components, Next.js full-stack, React Native, Node-side remote loading. **Cross-framework boundary (5.3.0+)**: sub-app-level embedding is supported (§8.7 `/bridge`); direct component-level Vue↔React rendering in one tree is not (that is the product of framework-conversion libraries). Pure single-framework projects keep zero cross-dependency
|
|
410
|
-
- **Bridge isolation boundary (declared honestly in §8.7)**: bridging isolates only the mount/unmount edge of the two component trees — no browser realm isolation. Remote global CSS, `body`/`html` styles, global variables, and DOM rendered outside the container via React Portal / Vue Teleport still affect the host; `unmount` cannot revoke CSS the browser already loaded. Sub-app internal errors do not bubble into host error boundaries (cross-root). Sub-
|
|
430
|
+
- **Bridge isolation boundary (declared honestly in §8.7)**: bridging isolates only the mount/unmount edge of the two component trees — no browser realm isolation. Remote global CSS, `body`/`html` styles, global variables, and DOM rendered outside the container via React Portal / Vue Teleport still affect the host; `unmount` cannot revoke CSS the browser already loaded. Sub-app internal errors do not bubble into host error boundaries (cross-root). Sub-app routing defaults to memory mode; explicit URL sync exists since 5.4.0 (§8.8) — when it is not enabled, refreshing does not restore the sub-app's internal path
|
|
411
431
|
- React side does not promise component keep-alive (`keepAliveNames` is Vue-only); re-opened pages still reuse downloaded modules
|
|
412
432
|
- Cross-origin Fast Refresh: remote React components update via the remote dev server's HMR push; after a cold start the first round often needs a host refresh — component-state retention across the federation boundary is not promised
|
|
413
433
|
- Not compatible with originjs `virtual:__federation__` legacy imports
|
package/README.md
CHANGED
|
@@ -614,7 +614,7 @@ CLI 解析同一份配置值;dev/prod 的 URL 选择规则与 `federation({ re
|
|
|
614
614
|
| `fulgurjs check-pages [--config <path>] [--site <URL>] [--manifest <r>=<路径\|URL>]... [--require-verified]` | 页面契约核对:宿主页面表(`hostPages` 具名导出)↔ 远程 manifest exposes。manifest 来源优先级 **`--manifest`(可多次、文件路径或 URL) > `--site`/消费方 prod 地址推导**(显式来源失败不回退、无本地 dist 兜底),输出每个 remote 的实际命中来源(防止旧本地 dist 冒充线上核对)。报告未知 remote、映射到未消费远程、缺失 expose、路由冲突(R1–R5);**确定性错误退出码 1**,远程不可达报「无法验证」,`--require-verified` 时无法验证也非零(CI 严格模式,避免 0 条核对显示通过)。`--json` 供 CI |
|
|
615
615
|
| `fulgurjs doctor --base <URL> --apps <a,b,c>` | 部署体检:remoteEntry/manifest/index.html 的 200/no-cache/JS 形态、CORS、chunk 抽样可达、版本 skew 预演。`--dev` 检查 dev 容器入口;`--json` 输出 JSON(CI 断言);`--chunk-sample N` 控制抽样数(默认 16)。**退出码:有 FAIL 即 1**,可直接做 CI 门禁 |
|
|
616
616
|
|
|
617
|
-
### 6. 错误码总表(
|
|
617
|
+
### 6. 错误码总表(48 个)
|
|
618
618
|
|
|
619
619
|
| 段 | 码 | 含义 |
|
|
620
620
|
|---|---|---|
|
|
@@ -660,6 +660,10 @@ CLI 解析同一份配置值;dev/prod 的 URL 选择规则与 `federation({ re
|
|
|
660
660
|
| | `MFU-015` | 桥接契约非法(`./bridge` 默认导出缺 mount/unmount 或非函数;修法指向 defineBridgeApp) |
|
|
661
661
|
| | `MFU-016` | 桥接准备或生命周期失败(`details.phase` 区分 getContext/mount/unmount;根因含子应用原始错误) |
|
|
662
662
|
| | `MFU-017` | 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾、非法值(空串/数字)、页面级单会话冲突) |
|
|
663
|
+
| | `MFU-030` | 桥接路由同步配置/前缀冲突(basePath 非法:空/根/带 query·hash·通配、同页重叠前缀登记) |
|
|
664
|
+
| | `MFU-031` | 桥接路由协议缺失/通道失效(子应用未以 `{ routing: true }` 声明协议、通道销毁后复用) |
|
|
665
|
+
| | `MFU-032` | 桥接非法导航(子应用导航目标越界自身前缀、`go` 参数非法、失效通道的请求被拒绝) |
|
|
666
|
+
| | `MFU-033` | 桥接路由同步失败(重定向链超出上限,附目标链;不静默回退 memory) |
|
|
663
667
|
| CC 跨应用上下文 | `CC-001` | AppContext 必需字段缺失(三段式:got/expected/example,修法指向宿主桥 `provideAppContext`) |
|
|
664
668
|
| | `CC-002` | 运行时单例不可用(独立直开远程页;修法 = 经宿主联邦加载,时序契约 bridge → 远程 setup → 页面模块) |
|
|
665
669
|
|
|
@@ -873,6 +877,84 @@ const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', {
|
|
|
873
877
|
- **DOM 所有权**:包装组件只创建并保持稳定的空挂载容器;pending/error 占位是它的兄弟节点,宿主重渲染不 patch 子应用 root 内部。React 宿主 StrictMode 双 effect(mount→cleanup→mount)安全。Vue `<KeepAlive>` 的 deactivate 不是卸载——缓存页中的子应用保有 root 与状态;需要离页即销毁就别缓存该页,登出流程应同时移除缓存的私有页面。
|
|
874
878
|
- **旧请求不冒充取消**:已进入 `loadRemote` 的工作不因桥接层作废而被取消——迟到的旧结果按代次丢弃(不 mount、不覆盖、无未处理拒绝);远程 `onSession` 必须遵守既有 `signal.aborted` 契约(异步等待后、写私有状态前检查信号)。
|
|
875
879
|
|
|
880
|
+
### 8.3 桥接 URL 同步 — `/bridge/router/*`(子应用内部路由 ↔ 宿主浏览器地址,5.4.0 起)
|
|
881
|
+
|
|
882
|
+
桥接默认 memory 路由:子应用内部跳转不改浏览器地址、刷新不能恢复子应用内部页面。URL 同步让**宿主 URL 表达子应用内部位置**——刷新直达、收藏分享、前进后退、宿主菜单跳转全部一致。显式开启,**默认关闭**(5.3.x 行为与老契约完全不变)。
|
|
883
|
+
|
|
884
|
+
**架构约定**:宿主 Router 是浏览器历史唯一写入方;子应用使用受控 memory 路由;两端经独立路由通道(不进 appProps/Context)传递位置;同实例内 path/search/hash 变化**不重挂 root、不重建 store、不重新加载远程**。
|
|
885
|
+
|
|
886
|
+
**① 宿主(Vue Router 4,history/hash 模式皆可)**:
|
|
887
|
+
|
|
888
|
+
```ts
|
|
889
|
+
// main.ts:宿主路由声明后缀匹配(缺它详情导航会卸载子应用!)
|
|
890
|
+
const router = createRouter({
|
|
891
|
+
history: createWebHistory(import.meta.env.BASE_URL), // hash 模式用 createWebHashHistory
|
|
892
|
+
routes: [
|
|
893
|
+
{ path: '/', component: Home },
|
|
894
|
+
{ path: '/approval/:pathMatch(.*)*', component: ApprovalBridgePage },
|
|
895
|
+
],
|
|
896
|
+
})
|
|
897
|
+
// 真实权限守卫:拒绝 → 通道收到 cancelled,URL/历史/子应用位置全部不变
|
|
898
|
+
router.beforeEach((to) => (to.path.startsWith('/approval/secret') ? false : undefined))
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
```vue
|
|
902
|
+
<!-- ApprovalBridgePage.vue:路由 prop = 独立控制通道 -->
|
|
903
|
+
<script setup lang="ts">
|
|
904
|
+
import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
|
|
905
|
+
import { createVueBridgeNavigation, type BridgeHostRouting } from '@fulgurjs/federation/bridge/router/vue'
|
|
906
|
+
const navigation = createVueBridgeNavigation(router) // routerBase 用于 createWebHistory(base) 场景
|
|
907
|
+
const routing: BridgeHostRouting = { basePath: '/approval', navigation }
|
|
908
|
+
const RemoteApp = createVueBridgeApp('remote/bridge-routed', { /* 同 8.2 */ })
|
|
909
|
+
</script>
|
|
910
|
+
<template>
|
|
911
|
+
<RemoteApp :session-key="sess" :routing="routing" :app-props="props" />
|
|
912
|
+
</template>
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
**② 子应用(声明协议 + 接线受控路由)**:
|
|
916
|
+
|
|
917
|
+
```ts
|
|
918
|
+
// bridge.ts(Vue 子应用):defineBridgeApp(工厂, { routing: true })——工厂第二参数 { signal, routing }
|
|
919
|
+
import { createRouter, createMemoryHistory, RouterView } from 'vue-router'
|
|
920
|
+
import { defineBridgeApp } from '@fulgurjs/federation/runtime'
|
|
921
|
+
import { connectVueBridgeRouter } from '@fulgurjs/federation/bridge/router/vue'
|
|
922
|
+
export default defineBridgeApp(async (props, ctx) => {
|
|
923
|
+
const router = createRouter({ history: createMemoryHistory(), routes: [
|
|
924
|
+
{ path: '/list', component: List },
|
|
925
|
+
{ path: '/detail/:id', component: Detail },
|
|
926
|
+
] })
|
|
927
|
+
await connectVueBridgeRouter(ctx.routing!, router).ready // 初始 push 落定后再 install(顺序不能反)
|
|
928
|
+
const app = createApp({ setup: () => () => h(RouterView) }, props)
|
|
929
|
+
app.use(router)
|
|
930
|
+
return app
|
|
931
|
+
}, { routing: true })
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
```tsx
|
|
935
|
+
// bridge.tsx(React 子应用):createReactBridgeRouter 返回 RouterProvider 元素
|
|
936
|
+
import { createReactBridgeRouter } from '@fulgurjs/federation/bridge/router/react'
|
|
937
|
+
export default defineBridgeApp((_props, ctx) =>
|
|
938
|
+
createReactBridgeRouter(ctx.routing!, [
|
|
939
|
+
{ path: '/list', element: <List /> },
|
|
940
|
+
{ path: '/detail/:id', element: <Detail /> },
|
|
941
|
+
]).element, { routing: true })
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
宿主端 React Router 仅支持 **data router 模式**(`createBrowserRouter` / `createHashRouter` + `RouterProvider`):`createReactBridgeNavigation(router, { basename, canNavigate })`。`canNavigate` 与树内 `useBlocker` 传同一谓词——子应用发起的导航被拒绝时由端口预判返回 cancelled(URL/历史零副作用),菜单/POP 等其他来源由 useBlocker 真实拦截。declarative 模式(BrowserRouter)无取消语义,不支持。React Router 要求 ≥ 6.11(createMemoryRouter)。
|
|
945
|
+
|
|
946
|
+
**③ 行为契约与边界**:
|
|
947
|
+
|
|
948
|
+
- **basePath**:宿主路由视角的静态绝对路径(拒绝空/根/带 query·hash·通配符,`MFU-030`);按路径段匹配(`/approval` 命中 `/approval/detail/1`,不命中 `/approval-old`);同页各同步实例前缀不得相同或重叠。`/approval` 对应子应用 `/`;根重定向由子应用路由定义、以 replace 规范化(不凭空多一条历史)。
|
|
949
|
+
- **Vite base 与路由分层**:部署在 `/erp/` 时 Vite base/宿主 Router base 是 `/erp/`,bridge basePath 仍是 `/approval`(Vue 传 `routerBase`、React 传 `basename`);适配器输出的逻辑路径不含部署前缀,不会拼出 `/erp/erp/...`。子目录部署 + hash 模式内层片段见 e2e fixtures(`fixtures/host-bridge-*/`,可运行参考实现)。
|
|
950
|
+
- **位置三段全等**:search/hash 原样保留(重复 query 键、编码、中文、片段不二次 decode/encode);仅参数变化也同步,且不重挂。
|
|
951
|
+
- **取消语义**:Vue Router 4 的 push/replace 落定 `NavigationFailure` 即真实取消;React Router data router 用 canNavigate 预判(与 useBlocker 同谓词)+ 树内 blocker。取消后 URL、历史、子应用位置保持最后确认状态,**绝不自动重试**(`router.push` 的函数返回不冒充提交成功)。
|
|
952
|
+
- **会话与生命周期**:换账号/登出(`sessionKey→null`)作废旧通道——旧通道的导航一律 cancelled、不写 URL、不复活子应用;unmount 后迟到通知失效(通道销毁,再订阅得 `MFU-031`)。KeepAlive 缓存离页的实例暂停路由写入(不抢占 URL、不销毁通道,激活重同步)。同一容器 unmount 抛错的持久封锁(BN09)不因路由绕过。
|
|
953
|
+
- **协议校验**:宿主启用 routing 而子应用未声明 `{ routing: true }`(契约 `routing: { protocol: 1 }`)→ `MFU-031` 占位,**不静默退回 memory 假装深链成功**。
|
|
954
|
+
- **非法导航与循环**:目标越界自身前缀(`../`、跨前缀)、非法 `go` 参数 → `MFU-032`;连续内部 replace 超过 5 次(重定向环)→ `MFU-033`(附目标链,不静默回入口)。
|
|
955
|
+
- **按需加载**:`/bridge`、`/runtime`、`/react` 默认入口不引入任何路由库;`/bridge/router/vue`、`/bridge/router/react` 为按需入口(`vue-router` / `react-router-dom` 为可选 peer,消费者自装)。两个入口各 ≤4096B gzip 门禁。
|
|
956
|
+
- **不承诺**:SSR/RSC、跨浏览器窗口、嵌套多级桥接子应用路由代理、TanStack Router 及其他路由库(可经 `BridgeHostNavigation`/`BridgeChildRoute` 端口自定义扩展)。
|
|
957
|
+
|
|
876
958
|
### 9. `AppContext` — 跨应用传值与方法引用(`@fulgurjs/federation/runtime`)
|
|
877
959
|
|
|
878
960
|
宿主向子应用传值、子应用向宿主反向注册方法,一律走这条一等公民通道(对标乾坤 `props`,但带类型与错误契约)——不再各自挂 `window.*` 裸口子。
|
|
@@ -54,7 +54,15 @@ var BridgeErrorCodes = {
|
|
|
54
54
|
/** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
|
|
55
55
|
BRIDGE_LIFECYCLE_FAILED: "MFU-016",
|
|
56
56
|
/** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
|
|
57
|
-
BRIDGE_SESSION_MISMATCH: "MFU-017"
|
|
57
|
+
BRIDGE_SESSION_MISMATCH: "MFU-017",
|
|
58
|
+
/** 路由同步配置/前缀冲突(basePath 非法、同页前缀冲突或重叠) */
|
|
59
|
+
ROUTING_CONFIG_INVALID: "MFU-030",
|
|
60
|
+
/** 路由协议缺失/通道失效(子应用未声明 routing 协议,或已销毁通道被再次使用) */
|
|
61
|
+
ROUTING_PROTOCOL_MISSING: "MFU-031",
|
|
62
|
+
/** 非法导航(目标越界前缀、非法 go 参数、失效实例的请求) */
|
|
63
|
+
ROUTING_NAVIGATION_INVALID: "MFU-032",
|
|
64
|
+
/** 路由准备/同步失败(重定向循环、子应用路由准备失败且已阻止错误页面显示) */
|
|
65
|
+
ROUTING_SYNC_FAILED: "MFU-033"
|
|
58
66
|
};
|
|
59
67
|
function bridgeLifecycleError(phase, spec, cause, extra) {
|
|
60
68
|
const raw = cause instanceof Error ? cause.message : String(cause ?? "unknown");
|
|
@@ -95,18 +103,20 @@ function CommitProbe({ children, onCommit }) {
|
|
|
95
103
|
}, []);
|
|
96
104
|
return children ?? null;
|
|
97
105
|
}
|
|
98
|
-
function defineBridgeApp(factory) {
|
|
106
|
+
function defineBridgeApp(factory, options = {}) {
|
|
99
107
|
return {
|
|
100
|
-
|
|
108
|
+
...options.routing ? { routing: { protocol: 1 } } : {},
|
|
109
|
+
mount(el, props, mountOptions) {
|
|
101
110
|
if (entriesByEl.has(el)) {
|
|
102
111
|
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", {
|
|
103
112
|
reason: "container-occupied"
|
|
104
113
|
});
|
|
105
114
|
}
|
|
106
115
|
const snapshot = { ...props ?? {} };
|
|
116
|
+
const ctx = { signal: mountOptions?.signal, routing: mountOptions?.routing };
|
|
107
117
|
let element;
|
|
108
118
|
try {
|
|
109
|
-
element = factory(snapshot);
|
|
119
|
+
element = factory(snapshot, ctx);
|
|
110
120
|
} catch (e) {
|
|
111
121
|
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
112
122
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
import { BridgeApp } from './bridge-core.cjs';
|
|
3
|
+
import { B as BridgeChildRoute } from './bridge-router-core-BxBx-reP.cjs';
|
|
3
4
|
import './context.cjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -21,8 +22,19 @@ import './context.cjs';
|
|
|
21
22
|
* 本文件零 Vue:/react 导入图隔离由 tests/runtime-entry-graph.test.ts 守护。
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
25
|
+
/** 工厂第二参数:挂载生命周期与路由通道(URL 同步;不混入业务 props) */
|
|
26
|
+
interface ReactBridgeAppContext {
|
|
27
|
+
/** 会话代次信号(登出/换代即 aborted;异步写回前必须检查) */
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
/** 路由通道(宿主启用 URL 同步时存在);配 createReactBridgeRouter 使用 */
|
|
30
|
+
routing?: BridgeChildRoute;
|
|
31
|
+
}
|
|
32
|
+
/** React 子应用工厂:接收挂载时 props 快照与生命周期上下文,返回 ReactElement */
|
|
33
|
+
type ReactBridgeAppFactory = (props: Record<string, unknown>, ctx?: ReactBridgeAppContext) => ReactElement;
|
|
34
|
+
interface DefineReactBridgeAppOptions {
|
|
35
|
+
/** 声明路由协议(URL 同步):契约写入 routing: { protocol: 1 }(缺失时宿主启用同步即 MFU-031) */
|
|
36
|
+
routing?: boolean;
|
|
37
|
+
}
|
|
26
38
|
/**
|
|
27
39
|
* 定义 React 子应用的桥接契约(远程 ./bridge 模块默认导出)。
|
|
28
40
|
*
|
|
@@ -32,6 +44,6 @@ type ReactBridgeAppFactory = (props: Record<string, unknown>) => ReactElement;
|
|
|
32
44
|
* export default defineBridgeApp((props) => <MemoryRouter><App {...props} /></MemoryRouter>)
|
|
33
45
|
* ```
|
|
34
46
|
*/
|
|
35
|
-
declare function defineBridgeApp(factory: ReactBridgeAppFactory): BridgeApp;
|
|
47
|
+
declare function defineBridgeApp(factory: ReactBridgeAppFactory, options?: DefineReactBridgeAppOptions): BridgeApp;
|
|
36
48
|
|
|
37
|
-
export { BridgeApp, type ReactBridgeAppFactory, defineBridgeApp };
|
|
49
|
+
export { BridgeApp, type DefineReactBridgeAppOptions, type ReactBridgeAppContext, type ReactBridgeAppFactory, defineBridgeApp };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
import { BridgeApp } from './bridge-core.js';
|
|
3
|
+
import { B as BridgeChildRoute } from './bridge-router-core-BxBx-reP.js';
|
|
3
4
|
import './context.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -21,8 +22,19 @@ import './context.js';
|
|
|
21
22
|
* 本文件零 Vue:/react 导入图隔离由 tests/runtime-entry-graph.test.ts 守护。
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
25
|
+
/** 工厂第二参数:挂载生命周期与路由通道(URL 同步;不混入业务 props) */
|
|
26
|
+
interface ReactBridgeAppContext {
|
|
27
|
+
/** 会话代次信号(登出/换代即 aborted;异步写回前必须检查) */
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
/** 路由通道(宿主启用 URL 同步时存在);配 createReactBridgeRouter 使用 */
|
|
30
|
+
routing?: BridgeChildRoute;
|
|
31
|
+
}
|
|
32
|
+
/** React 子应用工厂:接收挂载时 props 快照与生命周期上下文,返回 ReactElement */
|
|
33
|
+
type ReactBridgeAppFactory = (props: Record<string, unknown>, ctx?: ReactBridgeAppContext) => ReactElement;
|
|
34
|
+
interface DefineReactBridgeAppOptions {
|
|
35
|
+
/** 声明路由协议(URL 同步):契约写入 routing: { protocol: 1 }(缺失时宿主启用同步即 MFU-031) */
|
|
36
|
+
routing?: boolean;
|
|
37
|
+
}
|
|
26
38
|
/**
|
|
27
39
|
* 定义 React 子应用的桥接契约(远程 ./bridge 模块默认导出)。
|
|
28
40
|
*
|
|
@@ -32,6 +44,6 @@ type ReactBridgeAppFactory = (props: Record<string, unknown>) => ReactElement;
|
|
|
32
44
|
* export default defineBridgeApp((props) => <MemoryRouter><App {...props} /></MemoryRouter>)
|
|
33
45
|
* ```
|
|
34
46
|
*/
|
|
35
|
-
declare function defineBridgeApp(factory: ReactBridgeAppFactory): BridgeApp;
|
|
47
|
+
declare function defineBridgeApp(factory: ReactBridgeAppFactory, options?: DefineReactBridgeAppOptions): BridgeApp;
|
|
36
48
|
|
|
37
|
-
export { BridgeApp, type ReactBridgeAppFactory, defineBridgeApp };
|
|
49
|
+
export { BridgeApp, type DefineReactBridgeAppOptions, type ReactBridgeAppContext, type ReactBridgeAppFactory, defineBridgeApp };
|
package/dist/bridge-app-react.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
bridgeLifecycleError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JQF7KYZF.js";
|
|
4
4
|
import "./chunk-OF2JCXTZ.js";
|
|
5
5
|
|
|
6
6
|
// src/bridge-app-react.ts
|
|
@@ -26,18 +26,20 @@ function CommitProbe({ children, onCommit }) {
|
|
|
26
26
|
}, []);
|
|
27
27
|
return children ?? null;
|
|
28
28
|
}
|
|
29
|
-
function defineBridgeApp(factory) {
|
|
29
|
+
function defineBridgeApp(factory, options = {}) {
|
|
30
30
|
return {
|
|
31
|
-
|
|
31
|
+
...options.routing ? { routing: { protocol: 1 } } : {},
|
|
32
|
+
mount(el, props, mountOptions) {
|
|
32
33
|
if (entriesByEl.has(el)) {
|
|
33
34
|
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
35
|
reason: "container-occupied"
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
const snapshot = { ...props ?? {} };
|
|
39
|
+
const ctx = { signal: mountOptions?.signal, routing: mountOptions?.routing };
|
|
38
40
|
let element;
|
|
39
41
|
try {
|
|
40
|
-
element = factory(snapshot);
|
|
42
|
+
element = factory(snapshot, ctx);
|
|
41
43
|
} catch (e) {
|
|
42
44
|
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
43
45
|
}
|
package/dist/bridge-app-vue.cjs
CHANGED
|
@@ -43,7 +43,15 @@ var BridgeErrorCodes = {
|
|
|
43
43
|
/** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
|
|
44
44
|
BRIDGE_LIFECYCLE_FAILED: "MFU-016",
|
|
45
45
|
/** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
|
|
46
|
-
BRIDGE_SESSION_MISMATCH: "MFU-017"
|
|
46
|
+
BRIDGE_SESSION_MISMATCH: "MFU-017",
|
|
47
|
+
/** 路由同步配置/前缀冲突(basePath 非法、同页前缀冲突或重叠) */
|
|
48
|
+
ROUTING_CONFIG_INVALID: "MFU-030",
|
|
49
|
+
/** 路由协议缺失/通道失效(子应用未声明 routing 协议,或已销毁通道被再次使用) */
|
|
50
|
+
ROUTING_PROTOCOL_MISSING: "MFU-031",
|
|
51
|
+
/** 非法导航(目标越界前缀、非法 go 参数、失效实例的请求) */
|
|
52
|
+
ROUTING_NAVIGATION_INVALID: "MFU-032",
|
|
53
|
+
/** 路由准备/同步失败(重定向循环、子应用路由准备失败且已阻止错误页面显示) */
|
|
54
|
+
ROUTING_SYNC_FAILED: "MFU-033"
|
|
47
55
|
};
|
|
48
56
|
function bridgeLifecycleError(phase, spec, cause, extra) {
|
|
49
57
|
const raw = cause instanceof Error ? cause.message : String(cause ?? "unknown");
|
|
@@ -64,24 +72,61 @@ function bridgeLifecycleError(phase, spec, cause, extra) {
|
|
|
64
72
|
|
|
65
73
|
// src/bridge-app-vue.ts
|
|
66
74
|
var appsByEl = /* @__PURE__ */ new WeakMap();
|
|
67
|
-
|
|
75
|
+
var PENDING_MARKER = { __fulgurjsPending: true };
|
|
76
|
+
function entriesAborted(el) {
|
|
77
|
+
return appsByEl.get(el) !== PENDING_MARKER;
|
|
78
|
+
}
|
|
79
|
+
function defineBridgeApp(factory, options = {}) {
|
|
80
|
+
const attach = (el, app, mountOptions) => {
|
|
81
|
+
if (!app || typeof app.mount !== "function" || typeof app.unmount !== "function") {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`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`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
app.mount(el);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
try {
|
|
90
|
+
app.unmount();
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
94
|
+
}
|
|
95
|
+
appsByEl.set(el, app);
|
|
96
|
+
void mountOptions;
|
|
97
|
+
};
|
|
68
98
|
return {
|
|
69
|
-
|
|
99
|
+
...options.routing ? { routing: { protocol: 1 } } : {},
|
|
100
|
+
// 同步工厂保持同步语义(容器占用/工厂抛错同步抛,BN03/BN02 口径不变);
|
|
101
|
+
// Promise 工厂(URL 同步初始路由准备)走异步续体:挂起期间作废(signal aborted)
|
|
102
|
+
// 则结果丢弃不落挂(迟到初始化不得复活,BN06 同源语义)。
|
|
103
|
+
mount(el, props, mountOptions) {
|
|
70
104
|
if (appsByEl.has(el)) {
|
|
71
105
|
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
106
|
reason: "container-occupied"
|
|
73
107
|
});
|
|
74
108
|
}
|
|
75
109
|
const snapshot = { ...props ?? {} };
|
|
110
|
+
const ctx = { signal: mountOptions?.signal, routing: mountOptions?.routing };
|
|
76
111
|
let app;
|
|
77
112
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
const produced = factory(snapshot, ctx);
|
|
114
|
+
const thenable = produced.then;
|
|
115
|
+
if (typeof thenable === "function") {
|
|
116
|
+
appsByEl.set(el, PENDING_MARKER);
|
|
117
|
+
return produced.then(
|
|
118
|
+
(resolved) => {
|
|
119
|
+
if (entriesAborted(el)) return;
|
|
120
|
+
attach(el, resolved, mountOptions);
|
|
121
|
+
},
|
|
122
|
+
(e) => {
|
|
123
|
+
if (entriesAborted(el)) return;
|
|
124
|
+
appsByEl.delete(el);
|
|
125
|
+
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
126
|
+
}
|
|
82
127
|
);
|
|
83
128
|
}
|
|
84
|
-
app
|
|
129
|
+
app = produced;
|
|
85
130
|
} catch (e) {
|
|
86
131
|
if (app) {
|
|
87
132
|
try {
|
|
@@ -91,12 +136,13 @@ function defineBridgeApp(factory) {
|
|
|
91
136
|
}
|
|
92
137
|
throw bridgeLifecycleError("mount", "bridge", e instanceof Error ? e : String(e));
|
|
93
138
|
}
|
|
94
|
-
|
|
139
|
+
attach(el, app, mountOptions);
|
|
95
140
|
},
|
|
96
141
|
unmount(el) {
|
|
97
142
|
const app = appsByEl.get(el);
|
|
98
143
|
if (!app) return;
|
|
99
144
|
appsByEl.delete(el);
|
|
145
|
+
if (app === PENDING_MARKER) return;
|
|
100
146
|
try {
|
|
101
147
|
app.unmount();
|
|
102
148
|
} catch (e) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { App } from 'vue';
|
|
2
2
|
import { BridgeApp } from './bridge-core.cjs';
|
|
3
|
+
import { B as BridgeChildRoute } from './bridge-router-core-BxBx-reP.cjs';
|
|
3
4
|
import './context.cjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -18,8 +19,28 @@ import './context.cjs';
|
|
|
18
19
|
* 本文件零 React:/runtime 导入图隔离由 tests/runtime-entry-graph.test.ts 守护。
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
22
|
+
/** 工厂第二参数:挂载生命周期与路由通道(URL 同步;不混入业务 props) */
|
|
23
|
+
interface VueBridgeAppContext {
|
|
24
|
+
/** 会话代次信号(登出/换代即 aborted;异步写回前必须检查) */
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
/** 路由通道(宿主启用 URL 同步时存在);配 connectVueBridgeRouter 使用 */
|
|
27
|
+
routing?: BridgeChildRoute;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Vue 子应用工厂:接收挂载时 props 快照与生命周期上下文,返回装配完成的 VueApp。
|
|
31
|
+
* 允许返回 Promise<VueApp>(URL 同步子应用的初始 memory 路由准备是异步的——
|
|
32
|
+
* 必须等初始 push 落定再 app.use(router),否则 install 的初始导航会覆盖深链位置;
|
|
33
|
+
* 见 /bridge/router/vue 的 connectVueBridgeRouter 与任务书 §1 原型结论 1)。
|
|
34
|
+
* Promise 被作废(signal aborted / 容器已换代)时结果丢弃,不落挂。
|
|
35
|
+
*/
|
|
36
|
+
type VueBridgeAppFactory = (props: Record<string, unknown>, ctx?: VueBridgeAppContext) => App | Promise<App>;
|
|
37
|
+
interface DefineVueBridgeAppOptions {
|
|
38
|
+
/**
|
|
39
|
+
* 声明路由协议(URL 同步):契约写入 routing: { protocol: 1 },宿主启用 routing 时
|
|
40
|
+
* 校验;未声明而宿主启用同步 → MFU-031(不静默退回 memory 假装深链成功)。
|
|
41
|
+
*/
|
|
42
|
+
routing?: boolean;
|
|
43
|
+
}
|
|
23
44
|
/**
|
|
24
45
|
* 定义 Vue 子应用的桥接契约(远程 ./bridge 模块默认导出)。
|
|
25
46
|
*
|
|
@@ -29,6 +50,6 @@ type VueBridgeAppFactory = (props: Record<string, unknown>) => App;
|
|
|
29
50
|
* export default defineBridgeApp((props) => { const app = createApp(App, props); app.use(router); return app })
|
|
30
51
|
* ```
|
|
31
52
|
*/
|
|
32
|
-
declare function defineBridgeApp(factory: VueBridgeAppFactory): BridgeApp;
|
|
53
|
+
declare function defineBridgeApp(factory: VueBridgeAppFactory, options?: DefineVueBridgeAppOptions): BridgeApp;
|
|
33
54
|
|
|
34
|
-
export { BridgeApp, type VueBridgeAppFactory, defineBridgeApp };
|
|
55
|
+
export { BridgeApp, type DefineVueBridgeAppOptions, type VueBridgeAppContext, type VueBridgeAppFactory, defineBridgeApp };
|
package/dist/bridge-app-vue.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { App } from 'vue';
|
|
2
2
|
import { BridgeApp } from './bridge-core.js';
|
|
3
|
+
import { B as BridgeChildRoute } from './bridge-router-core-BxBx-reP.js';
|
|
3
4
|
import './context.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -18,8 +19,28 @@ import './context.js';
|
|
|
18
19
|
* 本文件零 React:/runtime 导入图隔离由 tests/runtime-entry-graph.test.ts 守护。
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
22
|
+
/** 工厂第二参数:挂载生命周期与路由通道(URL 同步;不混入业务 props) */
|
|
23
|
+
interface VueBridgeAppContext {
|
|
24
|
+
/** 会话代次信号(登出/换代即 aborted;异步写回前必须检查) */
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
/** 路由通道(宿主启用 URL 同步时存在);配 connectVueBridgeRouter 使用 */
|
|
27
|
+
routing?: BridgeChildRoute;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Vue 子应用工厂:接收挂载时 props 快照与生命周期上下文,返回装配完成的 VueApp。
|
|
31
|
+
* 允许返回 Promise<VueApp>(URL 同步子应用的初始 memory 路由准备是异步的——
|
|
32
|
+
* 必须等初始 push 落定再 app.use(router),否则 install 的初始导航会覆盖深链位置;
|
|
33
|
+
* 见 /bridge/router/vue 的 connectVueBridgeRouter 与任务书 §1 原型结论 1)。
|
|
34
|
+
* Promise 被作废(signal aborted / 容器已换代)时结果丢弃,不落挂。
|
|
35
|
+
*/
|
|
36
|
+
type VueBridgeAppFactory = (props: Record<string, unknown>, ctx?: VueBridgeAppContext) => App | Promise<App>;
|
|
37
|
+
interface DefineVueBridgeAppOptions {
|
|
38
|
+
/**
|
|
39
|
+
* 声明路由协议(URL 同步):契约写入 routing: { protocol: 1 },宿主启用 routing 时
|
|
40
|
+
* 校验;未声明而宿主启用同步 → MFU-031(不静默退回 memory 假装深链成功)。
|
|
41
|
+
*/
|
|
42
|
+
routing?: boolean;
|
|
43
|
+
}
|
|
23
44
|
/**
|
|
24
45
|
* 定义 Vue 子应用的桥接契约(远程 ./bridge 模块默认导出)。
|
|
25
46
|
*
|
|
@@ -29,6 +50,6 @@ type VueBridgeAppFactory = (props: Record<string, unknown>) => App;
|
|
|
29
50
|
* export default defineBridgeApp((props) => { const app = createApp(App, props); app.use(router); return app })
|
|
30
51
|
* ```
|
|
31
52
|
*/
|
|
32
|
-
declare function defineBridgeApp(factory: VueBridgeAppFactory): BridgeApp;
|
|
53
|
+
declare function defineBridgeApp(factory: VueBridgeAppFactory, options?: DefineVueBridgeAppOptions): BridgeApp;
|
|
33
54
|
|
|
34
|
-
export { BridgeApp, type VueBridgeAppFactory, defineBridgeApp };
|
|
55
|
+
export { BridgeApp, type DefineVueBridgeAppOptions, type VueBridgeAppContext, type VueBridgeAppFactory, defineBridgeApp };
|