@fulgurjs/federation 5.9.3 → 6.1.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 (104) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.en.md +57 -416
  3. package/README.md +56 -446
  4. package/dist/bridge-app-vue.d.cts +1 -1
  5. package/dist/bridge-app-vue.d.ts +1 -1
  6. package/dist/bridge-core.cjs +1 -1
  7. package/dist/bridge-core.js +2 -2
  8. package/dist/bridge-errors.cjs +1 -1
  9. package/dist/bridge-errors.js +1 -1
  10. package/dist/bridge-host-react.cjs +1 -1
  11. package/dist/bridge-host-react.js +3 -3
  12. package/dist/bridge-host-vue.cjs +1 -1
  13. package/dist/bridge-host-vue.js +3 -3
  14. package/dist/bridge-router-react.d.ts +22 -3
  15. package/dist/bridge-router-react.js +107 -9
  16. package/dist/bridge-router-vue.d.ts +24 -5
  17. package/dist/bridge-router-vue.js +4 -5
  18. package/dist/{chunk-YKIFZIBH.js → chunk-77JZ6U6V.js} +1 -1
  19. package/dist/{chunk-Q6V5O5Y6.js → chunk-ENT3BOTX.js} +1 -1
  20. package/dist/{chunk-WRJG2YG6.js → chunk-ZAYNS7YQ.js} +1 -1
  21. package/dist/cli.js +460 -109
  22. package/dist/index.cjs +25 -38
  23. package/dist/index.js +25 -38
  24. package/dist/react-adapter.d.cts +7 -0
  25. package/dist/react-adapter.d.ts +7 -0
  26. package/dist/react.d.ts +229 -101
  27. package/dist/react.js +4 -0
  28. package/dist/runtime-entry.d.ts +27 -174
  29. package/dist/runtime-entry.js +0 -1
  30. package/dist/runtime.js +19 -17
  31. package/dist/vue-adapter.cjs +13 -1
  32. package/dist/vue-adapter.d.cts +1 -0
  33. package/dist/vue-adapter.d.ts +1 -0
  34. package/dist/vue-adapter.js +14 -2
  35. package/dist/vue.d.ts +594 -0
  36. package/dist/vue.js +8 -0
  37. package/docs/en/migration.md +210 -0
  38. package/docs/en/reference/api.md +555 -0
  39. package/docs/en/reference/errors.md +86 -0
  40. package/docs/{P5-vite7-8 → maintainers/P5-vite7-8}/345/205/274/345/256/271/347/237/251/351/230/265.md +7 -9
  41. package/docs/{webpack-mf- → maintainers/webpack-mf-}/345/257/271/347/205/247/344/270/216/347/274/272/345/217/243.md +10 -10
  42. package/docs/maintainers//346/262/231/347/256/261/350/276/271/347/225/214/345/256/241/350/256/241.md +43 -0
  43. package/docs/zh/migration.md +210 -0
  44. package/docs/zh/reference/api.md +551 -0
  45. package/docs/zh/reference/errors.md +86 -0
  46. package/examples/templates/README.md +1 -1
  47. package/examples/templates/react-host-vue-remote/pnpm-lock.yaml +7 -7
  48. package/examples/templates/react-host-vue-remote/pnpm-workspace.yaml +1 -1
  49. package/examples/templates/react-host-vue-remote/react-host/README.md +2 -2
  50. package/examples/templates/react-host-vue-remote/react-host/package.json +1 -1
  51. package/examples/templates/react-host-vue-remote/react-host/src/main.tsx +2 -2
  52. package/examples/templates/react-host-vue-remote/scripts/dev.config.json +11 -2
  53. package/examples/templates/react-host-vue-remote/vue-remote/package.json +1 -1
  54. package/examples/templates/react-host-vue-remote/vue-remote/src/bridge.ts +1 -1
  55. package/examples/templates/react-react/host/package.json +1 -1
  56. package/examples/templates/react-react/pnpm-lock.yaml +7 -7
  57. package/examples/templates/react-react/pnpm-workspace.yaml +1 -1
  58. package/examples/templates/react-react/remote/package.json +1 -1
  59. package/examples/templates/react-react/scripts/dev.config.json +11 -2
  60. package/examples/templates/showcase/README.md +6 -6
  61. package/examples/templates/showcase/pnpm-lock.yaml +11 -11
  62. package/examples/templates/showcase/pnpm-workspace.yaml +1 -1
  63. package/examples/templates/showcase/react-host/package.json +1 -1
  64. package/examples/templates/showcase/react-host/src/pages/BridgeVuePage.tsx +1 -1
  65. package/examples/templates/showcase/react-host/src/routing.ts +1 -1
  66. package/examples/templates/showcase/react-remote/package.json +1 -1
  67. package/examples/templates/showcase/react-remote/src/bridge.tsx +1 -1
  68. package/examples/templates/showcase/scripts/dev.config.json +22 -4
  69. package/examples/templates/showcase/vue-host/package.json +1 -1
  70. package/examples/templates/showcase/vue-host/src/pages/BridgeReactPage.vue +1 -1
  71. package/examples/templates/showcase/vue-host/src/routing.ts +3 -13
  72. package/examples/templates/showcase/vue-remote/package.json +1 -1
  73. package/examples/templates/showcase/vue-remote/src/bridge.ts +2 -2
  74. package/examples/templates/vue-host-react-remote/pnpm-lock.yaml +7 -7
  75. package/examples/templates/vue-host-react-remote/pnpm-workspace.yaml +1 -1
  76. package/examples/templates/vue-host-react-remote/react-remote/package.json +1 -1
  77. package/examples/templates/vue-host-react-remote/scripts/dev.config.json +11 -2
  78. package/examples/templates/vue-host-react-remote/vue-host/README.md +2 -2
  79. package/examples/templates/vue-host-react-remote/vue-host/package.json +1 -1
  80. package/examples/templates/vue-host-react-remote/vue-host/src/App.vue +2 -2
  81. package/examples/templates/vue-vue/host/package.json +1 -1
  82. package/examples/templates/vue-vue/host/src/main.ts +28 -21
  83. package/examples/templates/vue-vue/host/src/pages/HomePage.vue +1 -1
  84. package/examples/templates/vue-vue/pnpm-lock.yaml +7 -7
  85. package/examples/templates/vue-vue/pnpm-workspace.yaml +1 -1
  86. package/examples/templates/vue-vue/remote/package.json +1 -1
  87. package/examples/templates/vue-vue/scripts/dev.config.json +11 -2
  88. package/package.json +24 -37
  89. package/dist/bridge-core-D37VanBl.d.ts +0 -99
  90. package/dist/bridge-host-react-CGymCzD2.d.ts +0 -35
  91. package/dist/bridge-host-vue-B3GHaQAC.d.ts +0 -32
  92. package/dist/bridge-react.d.ts +0 -14
  93. package/dist/bridge-react.js +0 -3
  94. package/dist/bridge-vue.d.ts +0 -8
  95. package/dist/bridge-vue.js +0 -3
  96. package/dist/bridge.d.ts +0 -18
  97. package/dist/bridge.js +0 -5
  98. package/dist/chunk-GLASM5EX.js +0 -1730
  99. package/dist/chunk-V6EASSCR.js +0 -249
  100. package/dist/chunk-X34EJB4H.js +0 -207
  101. package/docs/API.en.md +0 -339
  102. package/docs/API.md +0 -914
  103. package/docs//346/262/231/347/256/261/350/276/271/347/225/214/345/256/241/350/256/241.md +0 -45
  104. package/docs//350/277/201/347/247/273/346/214/207/345/215/227.md +0 -179
package/docs/API.md DELETED
@@ -1,914 +0,0 @@
1
- # API 手册(中文)
2
-
3
- > 对应 5.9.0。配置默认值、公开入口与类型以仓库源码及发布包声明核对;版本变更见 [CHANGELOG](../CHANGELOG.md)。
4
- > 桥接是“把子应用挂到宿主提供的 DOM 容器”;作用域是“共享依赖的分组”。以下执行规则用于避免误用,不要求入门时全部阅读。
5
-
6
- [插件配置](#plugin-options) · [运行时](#runtime) · [错误码](#error-codes) · [桥接](#bridge) · [URL 同步](#url-sync)
7
-
8
- ## API 参考
9
-
10
- 本手册按功能列出配置、参数和执行规则。先读 [使用指南](../README.md),需要查询某个 API 时再回到这里。下文的长代码块包含集成片段;完整可运行工程见 [examples](../examples/README.md) 与 [Demo](https://github.com/chenmingye/fulgurjs-federation/blob/master/examples/demos/README.md)。应用代码只导入公开入口,不依赖 `/internal/*`。
11
-
12
- <a id="plugin-options"></a>
13
-
14
- ### 1. `federation(options)` — Vite 插件(宿主/远程同一份 API)
15
-
16
- ```ts
17
- import { federation, type FederationOptions } from '@fulgurjs/federation'
18
- ```
19
-
20
- 插件选项类型为 `FederationOptions`(下表即其字段全集)。
21
-
22
- #### 全部选项
23
-
24
- | 选项 | 类型 | 默认 | 说明 |
25
- |---|---|---|---|
26
- | `name` | `string` **必填** | — | 容器名。同页面宿主/远程必须唯一;须匹配 `/^[a-zA-Z][\w.-]*$/` |
27
- | `filename` | `string` | `'fulgurjs-remoteEntry.js'` | prod 容器入口文件名(固定文件名便于引用与部署规则落位;入口内容每次构建变,**必须 no-cache**,长缓存只给带内容哈希的 chunk) |
28
- | `exposes` | `Record<string, string \| { import: string; name?: string }>` | — | 对外暴露模块:键 `'./X'`,值源文件路径;`name` 为稳定 chunk 文件名。键不得占用内部保留键 `./__fulgurjs_setup__`(CFG-012) |
29
- | `setup` | `string` | —(无初始化行为) | **可选远程初始化入口**:相对应用根的 TS/JS 模块路径。默认导出 `setup(context)` 应用级执行一次(容器首次被加载业务模块前);可选具名导出 `onSession(context)` 按宿主 `sessionKey` 去重执行。其余导出不作为生命周期入口。执行时序/去重/失败重试/错误码见 §10 |
30
- | `remotes` | `Record<string, string \| RemoteEntryConfig \| (() => Promise<any>)>` | — | 消费的远程,三种形态见下表 |
31
- | `shared` | `string[] \| Record<string, string \| SharedHint>` | — | 共享依赖;字符串简写 = requiredVersion(缺省从本应用 package.json 推断) |
32
- | `shareScope` | `string` | `'default'` | 默认共享作用域名 |
33
- | `runtime` | `string \| false` | 内置运行时 | 自定义运行时模块路径;`false` 禁用内置运行时 |
34
- | `runtimeChunk` | `boolean \| 'single'` | — | 运行时是否拆独立 chunk |
35
- | `manifest` | `boolean \| Record<string, unknown>` | `true` | 设 false 关闭,其余值开启;对象形态不提供额外字段配置。prod 构建生成 `fulgurjs-manifest.json`(preloadRemote 依赖它) |
36
- | `runtimePlugins` | `string[]` | `[]` | 运行时插件模块路径列表(写法见「运行时插件」) |
37
- | `dts` | `boolean \| { dir?: string; mode?: 'source' \| 'shim' }` | `true` | dev 下拉取远程 manifest 生成类型声明——宿主写 `import X from 'remote-a/X'` 获得类型。**产物写入 `src/fulgurjs/types/`(联邦产物集中一个文件夹;无 src 布局回退 `.fulgurjs/types`)**,src 布局项目 tsconfig 零配置即生效;`{ dir }` 自定义位置;`mode: 'source'`(默认)跨工程源码直连(补全/跳转直达远程源码,VSCode 打开生成物可能显示工程外文件诊断);`mode: 'shim'` 宽松占位(不引用源文件,IDE 全程干净,无源码级补全——见 §9.1.5)。**注意**:两种 mode 都要读取 remote 本机源码来枚举导出名(shim 亦然),manifest 的 `fsRoot`/`src` 经过路径边界校验(相对路径、无 `..`、realpath 不得越出 fsRoot),但 `dts` 不是不可信 manifest 的安全边界——只对可信来源开启 |
38
- | `devSharedSelf` | `boolean` | 提供 `exposes`(或 `setup`)的应用 `true`;纯宿主(只消费)`false`;显式配置永远优先 | dev 下自身源码(含依赖)是否参与 shared 协商改写。双向联邦(既 expose 又消费 remote)默认即 `true`——无需再背诵显式配置(4.1.0 起按角色推断,默认值规则;此前默认 false 曾是已知错误配置的来源)。build 下该路径的协商门面自动隔离进插件专属 chunk(`fulgurjs-runtime` + `fulgurjs-shared-<key>`);共享包本体(含其静态闭包)自动隔离进 `fulgurjs-provider-<key>` 组(5.8.0 起,优先于用户 `manualChunks` 分组——防「消费方组 →静态→ TLA 门面 →fallback 动态→ 消费方组」自等待环与跨 chunk TDZ),**业务自身的 `manualChunks` 分组规则可原样保留**;不能保证用户任意模块图无循环 |
39
- | `devCorsOrigins` | `string[] \| '*'` | `'*'`(现状兼容) | dev 跨源访问策略:插件端点(`/@fulgurjs-entry.js`、`/@fulgurjs-manifest.json`)与 `server.cors` 使用同一来源。缺省或 `'*'` 全放开(非 loopback host 时提醒 DEV-011);数组按 Origin 反射 allowlist(未命中省略头)。用户显式配置的 `server.cors` 永远优先。开/关/自定义三态示例见下方 |
40
- | `devFsRoot` | `boolean` | `true`(现状兼容) | dev manifest 是否携带 `fsRoot`(remote 根目录本机绝对路径,宿主 dts 类型直连用)。`false` 不写入(本机路径不外发),宿主 dts 降级 any 桩并提示;该字段永不进入 prod manifest。非 loopback host 下默认值会提醒 DEV-012 |
41
-
42
- #### remotes 的三种形态
43
-
44
- ```ts
45
- remotes: {
46
- // ① 字符串单地址:dev 自动拼 /@fulgurjs-entry.js,prod 自动拼 filename
47
- 'remote-a': 'http://localhost:5101',
48
- // ② '自报名@url':重命名语义(仅字符串形式支持;对象形式不支持 name@,配置期即报 CFG-007)
49
- 'checkout': 'shop@http://localhost:5102',
50
- // ③ 对象:dev/prod 显式拆分 + 容错参数(全部可选)
51
- 'remote-b': {
52
- dev: 'http://localhost:5103/remote-b',
53
- prod: '/remote-b',
54
- shareScope: 'default',
55
- timeout: 15000, // 加载超时 ms(有限正数,配置期校验 CFG-009)
56
- retries: 2, // 失败重试次数
57
- fallback: ['http://backup/remote-b'], // 备用 remoteEntry,依次尝试
58
- breaker: { threshold: 5, resetMs: 30000 }, // 连续失败熔断
59
- },
60
- // ④ 函数:promise-based remote(构建时地址未知;等价 webpack "promise new Promise",
61
- // 需在运行时配合 registerRemote 注册,见下文运行时 API)
62
- 'remote-c': () => fetch('/api/remote-url').then(r => r.text()),
63
- }
64
- ```
65
-
66
- #### devCorsOrigins / devFsRoot 三态示例
67
-
68
- ```ts
69
- // ① 开(默认/现状):全放开——跨 dev-server 协作开箱即用;非 loopback host 时提醒 DEV-011/012
70
- federation({ name: 'remote-a', exposes: { './Button': './src/Button.vue' } })
71
-
72
- // ② 显式全开:同 ①,但不再提醒(声明"我知情")
73
- federation({ name: 'remote-a', exposes: { './Button': './src/Button.vue' }, devCorsOrigins: '*' })
74
-
75
- // ③ 自定义 allowlist:仅列出的宿主来源可跨源访问联邦端点与源码模块
76
- federation({
77
- name: 'remote-a',
78
- exposes: { './Button': './src/Button.vue' },
79
- devCorsOrigins: ['http://localhost:5100', 'https://team.example.com'],
80
- devFsRoot: false, // 同时不把本机绝对路径写进 dev manifest(宿主 dts 降级 any 桩并提示)
81
- })
82
- ```
83
-
84
- 行为边界:`devCorsOrigins` 只作用于 dev(build 产物不受影响);用户显式配置的 `server.cors` 永远优先于插件注入的 cors 选项;端点对未命中来源只是省略 `Access-Control-Allow-Origin` 响应头(同源请求不受任何影响)。`devFsRoot: false` 只影响 dev manifest 的 `fsRoot` 字段(该字段永不进入 prod manifest)。
85
-
86
- #### shared 的完整选项(SharedHint)
87
-
88
- ```ts
89
- shared: {
90
- vue: {
91
- singleton: true, // 全页单实例(vue/pinia/vue-router 强烈建议 true)
92
- requiredVersion: '^3.4.0', // semver 全语法;false = 接受任意;缺省从 package.json 推断
93
- strictVersion: false, // 缺省:有本地副本且非 singleton → true(不满足即抛 MFU-003)
94
- shareKey: 'vue', // 共享作用域里的键(导入名与共享名不同时用)
95
- shareScope: 'default', // 该项的共享作用域
96
- eager: false, // true = 本地副本打进初始 chunk(同步可用)
97
- import: 'vue', // 本地副本模块;false = 纯消费不提供(与 eager 互斥,CFG-008)
98
- version: '3.4.21', // 显式提供版本(缺省读本机安装版本)
99
- },
100
- // 字符串简写:等价 { requiredVersion: '^4.4.5' }
101
- 'vue-router': '^4.4.5',
102
- // 数组形式:shared: ['vue', 'pinia']
103
- }
104
- ```
105
-
106
- 版本裁决语义对齐 webpack:满足 requiredVersion 的最高版本胜出;已加载版本永不替换;singleton 收敛到唯一实例;strictVersion 冲突抛 MFU-003。`MFU-010` 仅在最终选中的单例版本不满足某个消费方的 `requiredVersion` 时告警。提示会列出候选版本、实际提供方、影响及修法;同一版本组合只提示一次。多个候选版本本身不是错误,例如 `^2.1.7` 包含 `2.3.1`,不能仅凭两个版本号不同就判定不兼容。
107
-
108
- <a id="runtime"></a>
109
-
110
- ### 2. 运行时 API — `@fulgurjs/federation/runtime`
111
-
112
- **任何文件都直接静态导入**——宿主页面、exposes 目标文件(远程页面)都一样,插件自动保证同一页面只有一个运行时实例(远程页面里的导入会被自动改写为惰性单例委托):
113
-
114
- ```ts
115
- // 宿主页面、远程页面,写法完全一致
116
- import { loadRemote } from '@fulgurjs/federation/runtime'
117
- ```
118
-
119
- > 仍可绕过代理直取全局单例(等价,调试用):`(globalThis as any).__FULGURJS_RUNTIME__`。
120
-
121
- #### 函数总表
122
-
123
- > 下表全部函数与 `definePages` / `remoteSchema` / `provideAppContext` 等 context 函数 / `remoteComponent` / `createHostPages` 都从 Vue 入口 `@fulgurjs/federation/runtime` 导入(见 §2);旧入口已删除。**React 浏览器应用请使用 `@fulgurjs/federation/react`**(通用函数同名提供 + §8.1 的 React 适配 API;不含本表的 Vue 专属项 `remoteComponent` Vue 形态 / `createHostPages` / `keepAliveNames`)。
124
-
125
- > **TS 提示**:`@fulgurjs/federation/runtime` 的类型随包发布,由包的 `exports` 和 `typesVersions` 直接解析;不需要 `client` 类型垫片。dev 启动时插件仅在类型目录(默认 `src/fulgurjs/types/`)生成远程 exposes 的类型声明。
126
-
127
- | 函数 | 签名 | 说明 |
128
- |---|---|---|
129
- | `loadRemote` | `(spec: string, opts?) => Promise<模块命名空间>` | 加载远程模块。`spec = '远程名/./Expose键'`(`./` 可省)。远程配置了 `setup` 时,该函数是初始化生命周期的**统一触发入口**(容器 init 后、返回模块前执行 setup/onSession,见 §10);`loadRemote('remote')` 只取容器不执行初始化。opts 见下 |
130
- | `loadShare` | `(name: string, opts?) => Promise<命名空间>` | 共享模块协商(最高版本胜出/已加载优先/singleton 收敛)。opts:`{ requiredVersion?, singleton?, strictVersion?, shareKey?, shareScope?, fallback? }` |
131
- | `preloadRemote` | `(spec: string, opts?: { mode?: 'preload' \| 'prefetch' }) => Promise<void>` | `remote/Expose` 只预载该 expose 的 chunk + CSS;仅传 remote 名则预载全部 exposes。`preload` 等待 CSS load/error,`prefetch` 低优先级并立即返回 |
132
- | `getContainer` | `(name: string) => Promise<容器>` | 取远程容器(触发加载 + init),容器协议 `{ name, init, get }`;**不执行 setup/onSession**。直接访问 `container.get()` 同样不保证执行初始化——需要生命周期的加载一律走 `loadRemote` |
133
- | `registerRemote` / `registerRemotes` | `(config \| list) => void` | 运行时注册远程(promise remote / 动态地址)。`RemoteConfig`:`{ name, entry, promise?, shareScope?, timeout?, retries?, fallback?, breaker? }`(旧类型名 RemoteInput 保留为弃用别名)。参数校验:`timeout` 有限正数、`retries` 0..10 整数、`breaker.threshold/resetMs` 有限正数——非法值**注册当场抛错**(配置文件路径在配置期即报 CFG-009);重复注册时 entry/timeout/retries/breaker 参数按最新配置刷新,熔断计数状态保留。`timeout` 语义:超时只代表"调用方不再等待",浏览器不会取消已发出的动态 import——后续调用复用同一 in-flight 记录,不会重复初始化同一容器 |
134
- | `registerShare` | `(scope, name, version, get, opts?) => void` | 手工注册共享模块(一般由 init 模块自动完成) |
135
- | `initSharing` | `(scopeName?) => ShareScopeMap` | 初始化共享作用域(一般由 init 模块自动完成) |
136
- | `registerPlugins` | `(plugins: RuntimePlugin[]) => void` | 注册运行时插件(见下) |
137
- | `getRuntime` | `() => FgRuntime` | 取运行时单例本体(与 `__FULGURJS_RUNTIME__` 同一实例) |
138
- | `version` | `string` | 运行时/插件版本(跨源副本一致性诊断用) |
139
- | `unwrapDefault` | `(ns: any) => any` | ESM/CJS default interop 工具 |
140
-
141
-
142
- #### 其他已导出的通用函数
143
-
144
- Vue 从 `/runtime`、React 从 `/react` 导入以下函数。除 `validatePages` 外,这些主要用于高级诊断或自定义加载,不是普通接入的必做步骤。
145
-
146
- | 名称 | 签名 / 值 | 说明 |
147
- |---|---|---|
148
- | `getLoadedShare` | `(name: string, opts?: LoadShareOptions) => any` | 同步读取已就绪实例,不下载模块;未命中返回 undefined,严格版本冲突仍可抛错。有 resolveShare 策略时仅复用已裁决结果 |
149
- | `pinLoadedShare` | `(name, opts: LoadShareOptions, localVersion: string, instance: unknown) => void` | 登记已存在的本地实例;保留版本、首次实例和并发保护,不强制覆盖别人的实例。普通接入交给插件处理 |
150
- | `parseSpec` | `(spec: string) => { remote: string; module: string }` | 拆解远程模块名称;模块部分统一为 `./X`,仅有远程名时 module 为空 |
151
- | `shareScopeMap` | `ShareScopeMap` | 共享依赖注册表;查看诊断可以,普通业务不要直接修改 |
152
- | `clearSessionState` | `() => void` | 作废远程 onSession 信号及去重状态,不删除账号上下文本身;退出通常调用 `clearAppContext`,它会同时清理这些状态 |
153
- | `validatePages` | `(pages: PageRouteLike[], options?: PagesOptions) => PageViolation[]` | 返回页面表的 R1–R5 问题列表,不因为违例抛错;通常由 definePages/createHostPages 调用 |
154
-
155
- #### loadRemote 选项
156
-
157
- ```ts
158
- const Panel = await loadRemote('shop/Panel', {
159
- shareScope: 'default', // 覆盖远程声明的 shareScope
160
- retries: 3, // 单次调用覆盖 remote.retries
161
- fallbackModule: () => import('./PanelFallback.vue'),
162
- // 失败时返回 fallback 模块;错误事件/console 仍显式发出(不是静默兜底);不传则抛错
163
- })
164
- ```
165
-
166
- #### 运行时插件
167
-
168
- (`runtimePlugins: ['./src/fulgurjsPlugin.ts']`)
169
-
170
- > hook 错误契约:`beforeLoadRemote` / `afterLoadRemote` 是**观测 hook**——自身抛错只告警、不改写加载结果;`resolveShare` 是**决策 hook**——显式抛错向调用方传播(绝不静默回退到另一份共享依赖)。
171
- >
172
- > **resolveShare 与消费路径(5.7.1 起)**:配置了 `runtimePlugins` 的 HTML 入口在执行应用前完成共享裁决与加载;远程容器也会在执行 expose 前完成异步裁决。同步门面复用同一消费条件的决策与实例,异步 hook 可以选择低版本或原表之外的条目,不会被本地副本覆盖。应用与 provider 之间保留动态导入边界,Vite 8 的消费方门面仍无 TLA,避免把协商等待传入消费方循环依赖。
173
- >
174
- > **入口边界**:没有 HTML 入口的 library/自定义入口,或应用运行后才调用 `registerPlugins` 更改策略,需要先 `await loadShare(name, opts)`,再动态导入新的消费者;已经求值的静态绑定无法追溯改写。未准备的同步消费者遇到异步 hook 仍给出 `MFU-004`(`details.syncUnsupported: true`),并接管其迟到拒绝,避免额外 `unhandledrejection`。`strictVersion` 冲突给出 MFU-003;本地接管的实例按真实版本登记,不能借用另一版本槽位绕过检查。需要同时使用 React 18/19 时,为整组 React、renderer 及其消费方设置独立 `shareScope`,通过桥接传普通 props/回调,不跨 renderer 传 ReactElement 或 Context。可运行示例见 [React 版本隔离与恢复](https://github.com/chenmingye/fulgurjs-federation/blob/master/examples/demos/react-versions/README.md)。
175
-
176
- ```ts
177
- import type { RuntimePlugin } from '@fulgurjs/federation/runtime'
178
-
179
- export default {
180
- name: 'my-plugin',
181
- init(hooks) {
182
- hooks.resolveShare = async ({ shareKey, shareScope, requiredVersion, picked, available }) => {
183
- // 覆写共享版本裁决:返回 ShareEntry 即生效
184
- }
185
- hooks.beforeLoadRemote = ({ remote, module }) => {}
186
- hooks.afterLoadRemote = ({ remote, module, module_ns }) => {}
187
- hooks.onRemoteError = ({ remote, error }) => {} // error.code ∈ 错误码总表
188
- },
189
- } satisfies RuntimePlugin
190
- ```
191
-
192
- #### 调试面(无需配置,始终存在)
193
-
194
- | 出口 | 内容 |
195
- |---|---|
196
- | `window.__FULGURJS_SCOPE__` | share scope 实时协商结果(键 → 版本 → `{ get, from, loaded }`) |
197
- | `window.__FULGURJS_INFO__` | `{ remotes: { [名]: { entry, status, lastLoadMs, error, setup } }, errors: [] }`——`setup` ∈ none/pending/ready/failed |
198
- | `window.__FULGURJS_APP_CONFIG__` | W4 全局配置镜像 |
199
- | `window` 事件 `fulgurjs:error` | `CustomEvent<{ remote, error }>`,所有远程加载/共享错误都会发出 |
200
-
201
- <a id="pages"></a>
202
-
203
- ### 3. `definePages` — 宿主页面路由表(`@fulgurjs/federation/runtime`)
204
-
205
- 宿主把「URL 路径 → 远程 exposes 键」的映射表交给它校验,带参路由的静默冲突在启动期报错而不是运行时加载错组件:
206
-
207
- ```ts
208
- import { definePages } from '@fulgurjs/federation/runtime'
209
- import { remoteSchema } from '@fulgurjs/federation/runtime' // dev 自动生成;build 恒为空(诚实降级)
210
-
211
- export const PAGES = definePages(
212
- [
213
- { route: '/remote-a/home', name: 'RemoteAHome', title: '首页' },
214
- // 带参路由:缺省推导 spec = 去首段 + 剥 :参 段;与其它条目冲突时 ERROR,
215
- // 指向独立 expose 用 spec 显式覆盖
216
- { route: '/remote-a/detail/:id', name: 'RemoteADetail', spec: 'pages/remote-a/detail', title: '详情' },
217
- ],
218
- {
219
- deriveSpec: (route) => 'pages/' + route.replace(/^\//, '').split('/').filter(s => !s.startsWith(':')).join('/'),
220
- remotes: { '/remote-a/': 'remote-a' }, // 路由前缀 → 远程名
221
- schema: remoteSchema, // { [remoteName]: { exposes: string[], exists?: boolean } }
222
- strict: true, // ERROR 默认 throw;false 降级 console.error
223
- },
224
- )
225
- ```
226
-
227
- 校验规则:
228
-
229
- | 规则 | 级别 | 内容 |
230
- |---|---|---|
231
- | R1 | ERROR | 带参路由(无显式 spec)的推导 spec 与其它条目收敛相同——会静默加载错误组件 |
232
- | R2 | WARN | 多条目有效 spec 完全相同(刻意的菜单别名可忽略) |
233
- | R3 | ERROR | spec 不在该 remote 的 exposes 清单中(dev 有 schema 时校验;远程不可达诚实跳过) |
234
- | R4 | ERROR | 静态路由被更靠前的带参路由遮蔽(先到先得)/路由完全重复 |
235
- | R5 | WARN | name 重复(vue-router 命名跳转歧义) |
236
-
237
- `validatePages(pages, options)` 为独立导出:返回违例清单不抛错,便于自测。
238
-
239
- 同子路径的类型:`PageRouteLike`(路由条目形状)、`PagesOptions`(校验选项,含 `deriveSpec` / `remotes` / `schema` / `strict`)、`PageViolation`(`validatePages` 的返回条目,含 `level` 与说明)、`RemoteSchemaEntry`(`schema` 里每个远程的条目形状)。
240
-
241
- ### 4. `fulgurjs.config.ts` — 每项目一份的联邦配置(默认形态)
242
-
243
- ```ts
244
- // my-app/fulgurjs.config.ts —— 默认导出直接可传给 federation();无 root/apps[]/角色壳
245
- import type { FederationOptions } from '@fulgurjs/federation'
246
-
247
- export default {
248
- name: 'my-app', // 联邦容器名(必填)
249
- exposes: { './pages/home': './src/views/Home.vue' },
250
- remotes: { 'remote-a': { dev: 'http://localhost:5174/remote-a', prod: '/remote-a' } },
251
- setup: './src/fulgurjs/setup.ts', // 可选:远程初始化入口(§10)
252
- shared: { vue: { singleton: true } },
253
- devSharedSelf: true, // 可选:显式覆盖;缺省按角色推断(提供 exposes/setup → true)
254
- } satisfies FederationOptions
255
-
256
- // ── 以下具名导出仅供 CLI explain/check-pages 读取,不是 federation() 的参数 ──
257
- // 宿主应用:页面表与运行时 createHostPages 消费同一份数据模块(唯一手工维护位置)
258
- // import { pages, remotePrefixes, deriveSpec } from './src/fulgurjs/host/pages.data'
259
- // export const hostPages = { pages, remotePrefixes, deriveSpec }
260
- ```
261
-
262
- CLI 内部加载器(`loadAppConfig`)以**原配置文件为解析基准** esbuild-bundle 读取:支持项目内
263
- 相对导入的纯 TS/JS 数据模块(extensionless 可)、Node ≥ 18、CJS/ESM 双形态;缺失文件、无
264
- `name`、字段形状不对、expose/setup 指向项目外或不存在文件等均三段式报错。运行时(Vite)与
265
- CLI 解析同一份配置值;dev/prod 的 URL 选择规则与 `federation({ remotes })` 一致(§1)。
266
-
267
- **已删除(5.0.0)**:4.1.0 聚合配置入口 `@fulgurjs/federation/config`(`defineRepoConfig` /
268
- `loadRepoConfig` / `federationOptionsForApp` 及 `RepoConfig` 等聚合类型)不再发布——导入该子路径
269
- 会得到 exports 解析错误;CLI 读到旧形状(`root + apps[]`)会输出「拆分到各项目根」的中文迁移
270
- 指引。`PageEntry` 仍是现行类型(宿主页面表记录,随单项目契约从主入口类型面使用)。
271
-
272
-
273
- ### 5. CLI 命令参考
274
-
275
- | 命令 | 说明 |
276
- |---|---|
277
- | `fulgurjs create [--list]` | **完整工程创建向导(新项目入口)**:从已安装 npm 包内复制一个完整模板工程(workspace + 子应用 + 锁文件 + 启动脚本)并默认执行 `pnpm install --frozen-lockfile`。交互模式(TTY)选择场景;非交互用 `fulgurjs create <模板名> [--dir <目标目录>] [--no-install] [--force] [--json]`。模板:`vue-vue` / `react-react` / `vue-host-react-remote` / `react-host-vue-remote` / `showcase`。目标目录非空默认拒绝(保护已有文件);`--force` 复用目录:只补缺失文件,同名冲突逐项列出并保留你的版本,绝不改写/删除已有内容;目标路径是文件、复制中途失败、安装失败均非零退出,已生成工程保留供排查;`--json` 时 stdout 仅输出结果 JSON,进度与安装日志走 stderr;复制后校验关键文件齐全。不做应用名称/端口改写(改端口四处清单见模板 README) |
278
- | `fulgurjs init` | 在当前目录生成**单项目** `fulgurjs.config.ts` 起步模板(默认导出 = `federation()` 选项 + 可选 `hostPages` 具名导出示例);`--template <path>` 指定**输出文件路径**(不是模板编号);已存在拒绝覆盖,`--force` 强制。init **只生成配置起步模板**,面向已有项目,不生成完整工程(新项目用 `create`)、不生成桥/路由/启动器/NGINX 文件 |
279
- | `fulgurjs init --config <path>` | 校验配置(CFG 三段式报错)+ 输出 `federation(fulgurjsConfig)` 接入块与通用核对清单(纯打印)。旧聚合形状报中文迁移错误 |
280
- | `fulgurjs explain [--config <path>] [--json]` | 配置解释器(纯本地、无网络、不读 token/环境秘密):应用角色(**按实际 federation 选项判定**——配 `remotes` 即消费、配 `exposes`/`setup` 即提供,两者均有=双角色,如双向联邦的 BPM)、有效 remotes、公开 exposes、内部 setup、shared、页面 spec 映射与数据来源、`devSharedSelf` 最终值及来源、加载链;另附**桥接完备性 WARN**(exposes 含 `./bridge` 的子应用:本框架键须 `singleton: true`,React 子应用需 react+react-dom 双键;shared 同时含 vue 与 react 的桥接宿主:三键全 `singleton: true`——启发式提示,不产生错误码)。`--json` 供 CI。传 `--app`(5.0.0 已删除的聚合选择器)报中文迁移错误 |
281
- | `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 |
282
- | `fulgurjs doctor --base <URL> --apps <a,b,c>` | 部署体检(`--apps` 为**站点根下的部署子目录**,如远程部署在 `/remote-a/` 就写 `remote-a`,不是容器名):检查 `<base>/<app>/` 下的 remoteEntry/manifest/index.html 的 200/no-cache/JS 形态、CORS、chunk 抽样可达、版本 skew 预演。`--dev` 检查 dev 容器入口;`--json` 输出 JSON(CI 断言);`--chunk-sample N` 控制抽样数(默认 16)。**退出码:有 FAIL 即 1**,可直接做 CI 门禁 |
283
-
284
- <a id="error-codes"></a>
285
-
286
- ### 6. 错误码总表(48 个)
287
-
288
- | 段 | 码 | 含义 |
289
- |---|---|---|
290
- | CFG 配置期 | `CFG-001` | name 缺失或非法 |
291
- | | `CFG-002` | exposes 配置形状错误 |
292
- | | `CFG-003` | remotes 配置形状错误 / 键含非法字符 |
293
- | | `CFG-004` | shared 配置形状错误 |
294
- | | `CFG-005` | remotes 键与 shared 键同名冲突 |
295
- | | `CFG-006` | 孤岛配置(既不提供也不消费) |
296
- | | `CFG-007` | remotes 对象形式误用 name@ 前缀(整串当 URL 拼接) |
297
- | | `CFG-008` | shared 非法组合(eager+import:false / shareKey 重复声明) |
298
- | | `CFG-009` | remotes 运行参数非法(timeout/retries/breaker 非有限正数/超上限) |
299
- | | `CFG-010` | devCorsOrigins 形态非法(须为 "*" 或 http(s) 来源数组) |
300
- | | `CFG-011` | 已删除的 webpack 兼容/无效选项(remoteType/library/automaticAsyncBoundary/dataPrefetch/usedExports/ignoreUnusedSharedExports——传入任何值报错并给出迁移写法) |
301
- | | `CFG-012` | setup 配置非法(路径为空/非字符串,或 exposes 占用内部保留键 `./__fulgurjs_setup__`) |
302
- | DEV 开发期 | `DEV-001` | remote dev server 不可达(manifest 拉取失败) |
303
- | | `DEV-002` | remote dev manifest 为空或格式不识别 |
304
- | | `DEV-004` | 已知 UMD-only 依赖不在 optimizeDeps.include(预构建内联本地 vue 风险) |
305
- | | `DEV-005` | remotes dev URL 端口无监听 |
306
- | | `DEV-006` | 宿主/远程插件版本不一致 |
307
- | | `DEV-009` | 门面/虚拟模块 404(.vite 缓存漂移,需清缓存重启) |
308
- | | `DEV-010` | dev 冷启动预构建窗口提示(首轮 30~60s 瞬态,非故障) |
309
- | | `DEV-011` | 非 loopback host + 通配 dev CORS(暴露面扩大提醒) |
310
- | | `DEV-012` | 非 loopback host + dev manifest 携带 fsRoot(本机路径外发提醒) |
311
- | BLD 构建期 | `BLD-001` | expose 源文件解析失败 |
312
- | | `BLD-002` | 构建目标低于 es2022(TLA 需要) |
313
- | | `BLD-003` | expose 目标组件含必填 props(文档化核对项) |
314
- | | `BLD-006` | output 数组形态下无法自动注入协商门面 chunk 隔离(需手工加分支) |
315
- | MFU 运行时 | `MFU-001` | 远程容器/模块加载失败(网络/超时/重试耗尽/熔断) |
316
- | | `MFU-002` | remoteEntry 自报名与配置名不一致 |
317
- | | `MFU-003` | strictVersion 版本不满足 |
318
- | | `MFU-004` | 共享模块缺失且无本地 fallback |
319
- | | `MFU-005` | 同一容器用不同 share scope 重复 init |
320
- | | `MFU-006` | 请求的模块未被该远程 exposes |
321
- | | `MFU-007` | 预加载失败(不阻断业务) |
322
- | | `MFU-008` | 未知远程 |
323
- | | `MFU-009` | 加载到的模块没有任何导出 |
324
- | | `MFU-010` | 选中的共享单例版本不满足消费方要求;显示版本、提供方、影响和修法,同一组合只告警一次 |
325
- | | `MFU-011` | setup 生命周期入口导出形态非法(默认导出/具名 onSession 不是函数;报实际类型/预期签名/修法) |
326
- | | `MFU-012` | setup/onSession 执行抛错(该次 loadRemote 拒绝;仅清失败阶段缓存,可直接重试,已成功的阶段不重复) |
327
- | | `MFU-013` | 远程声明 onSession 但宿主 AppContext 缺 sessionKey(登录代次;禁止用 token 充当) |
328
- | | `MFU-014` | setup/onSession 同步段内递归 loadRemote 同一远程(自等待死锁防线) |
329
- | | `MFU-015` | 桥接契约非法(`./bridge` 默认导出缺 mount/unmount 或非函数;修法指向 defineBridgeApp) |
330
- | | `MFU-016` | 桥接准备或生命周期失败(`details.phase` 区分 getContext/mount/unmount;根因含子应用原始错误) |
331
- | | `MFU-017` | 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾、非法值(空串/数字)、页面级单会话冲突) |
332
- | | `MFU-030` | 桥接路由同步配置/前缀冲突(basePath 非法:空/根/带 query·hash·通配、同页重叠前缀登记) |
333
- | | `MFU-031` | 桥接路由协议缺失/通道失效(子应用未以 `{ routing: true }` 声明协议、通道销毁后复用) |
334
- | | `MFU-032` | 桥接非法导航(子应用导航目标越界自身前缀、`go` 参数非法、失效通道的请求被拒绝) |
335
- | | `MFU-033` | 桥接路由准备/同步失败(重定向超限或导航异常,附目标链/cause;不静默回退 memory) |
336
- | CC 跨应用上下文 | `CC-001` | AppContext 必需字段缺失(三段式:got/expected/example,修法指向宿主桥 `provideAppContext`) |
337
- | | `CC-002` | 运行时单例不可用(独立直开远程页;修法 = 经宿主联邦加载,时序契约 bridge → 远程 setup → 页面模块) |
338
-
339
- 错误排查三段式文案见「配置出错?报错看得懂」一节(下文);`fulgurjs doctor` 可提前把部署面的 MFU-001 类问题拦在上线前。
340
-
341
- ### 7. 产物与端点约定
342
-
343
- | 环境 | 路径 | 说明 |
344
- |---|---|---|
345
- | dev | `/<base>/@fulgurjs-entry.js` | 远程容器入口(插件中间件直出,自包含) |
346
- | dev | `/<base>/@fulgurjs-manifest.json` | dev manifest(宿主 dts / preloadRemote 消费) |
347
- | prod | `/<base>/fulgurjs-remoteEntry.js` | 固定文件名容器入口(内容每次构建变——**必须 no-cache**) |
348
- | prod | `/<base>/fulgurjs-manifest.json` | expose chunk/CSS 清单(preloadRemote 消费,**no-cache**) |
349
-
350
- NGINX no-cache 规则(remoteEntry/manifest/index.html)与深链回退是联邦部署通用知识,按下方规则自行落位(`init` 核对清单第 8 条同步提示)。
351
-
352
- ### 8. `remoteComponent` — Vue 远程组件直渲染(`@fulgurjs/federation/runtime`)
353
-
354
- ```ts
355
- import { remoteComponent } from '@fulgurjs/federation/runtime'
356
-
357
- const FederatedBusinessForm = remoteComponent('demo-host/FormRouterPage')
358
- const FederatedAmisForm = remoteComponent('demo-host/AmisFormRouterPage', {
359
- loadingComponent: MyLoading, // 可选:加载期组件
360
- errorComponent: MyError, // 可选:失败期组件(收到 error prop)
361
- retries: 2, // 可选:透传 loadRemote 单次调用级重试覆盖
362
- })
363
- ```
364
-
365
- | 选项 | 类型 | 默认 | 说明 |
366
- |---|---|---|---|
367
- | `loadingComponent` | `Component` | — | 加载期间展示 |
368
- | `errorComponent` | `Component` | 内置错误占位 | 加载失败展示(Vue 会传入 `error` prop)。自定义时完全接管展示,插件不再注入恢复按钮;默认占位自带「重试加载 / 刷新页面重试」 |
369
- | `retries` | `number` | 远程注册值(默认 2) | 透传 `loadRemote` |
370
- | `delay` | `number` | `200` | 切到 loadingComponent 前的等待(ms) |
371
- | `timeout` | `number` | — | 超时进错误态(ms);不设由 runtime 容器超时兜底 |
372
-
373
- 语义与边界:
374
-
375
- - 内部 = `defineAsyncComponent({ loader: () => loadRemote(spec, opts).then(m => m.default ?? m) })`,返回标准 Vue 异步组件,`props`(如 `form-params`)在使用处直接透传;
376
- - **无任何兜底/降级**(H3 零兜底):加载失败显式进错误态;不传 `errorComponent` 时渲染内置占位(错误码 + 根因 + 修法 + **重试加载 / 刷新页面重试**),`window` 的 `fulgurjs:error` 事件由 runtime 层照常发出;
377
- - 模块去重沿用 `loadRemote` 内部 Promise 缓存——同 spec 多组件实例只加载一次容器模块;
378
- - `vue` 为**可选 peerDependency**(`peerDependenciesMeta.optional`):只使用包根(Vite 插件)时无需安装;应用使用 `/runtime` 时需要安装 Vue,因为该入口导出 `remoteComponent`。内部 `runtime.js` 仍不导入 Vue,体积零增量;
379
- - 运行时实例经 `globalThis.__FULGURJS_RUNTIME__` 页面级单例复用,与 `@fulgurjs/federation/runtime` 的导入殊途同归,无需额外接线。
380
-
381
- ### 8.1 React 适配 API — `@fulgurjs/federation/react`
382
-
383
- React 浏览器应用唯一导入点:同时导出通用运行时 API(`loadRemote`/`preloadRemote`/`provideAppContext`/`definePages`/`remoteSchema` 等,与 `/runtime` 的通用面一致)与下列 React 适配 API。**不包含** Vue 的 `remoteComponent` 选项形态、`createHostPages`、`keepAliveNames`。
384
-
385
- #### `remoteComponent<Props>(spec, options?)`
386
-
387
- 返回可渲染的 React 组件类型(`Props` 约束 JSX 使用;类型参数是编译期合同,不是运行时校验)。工厂与页面表声明**零加载副作用**;首次渲染才 `loadRemote`(经容器协商与可选 setup/onSession),内部自带 pending 占位、错误占位与错误边界——最简用法无需手写 Suspense/`React.lazy`。**不用 `React.lazy`**:lazy 实例缓存失败的 Promise,仅重置错误边界无法恢复;本实现的 retry 会重建加载尝试(已成功的模块经运行时缓存不会重复下载)。
388
-
389
- | 选项 | 类型与默认 | 语义 |
390
- |---|---|---|
391
- | `fallback` | `ReactNode`,默认 `null` | 本次加载 pending 时的占位(区别于失败占位) |
392
- | `error` | `ReactNode` 或 `(error, retry) => ReactNode`,默认内置中文占位 | 加载失败或子树渲染错误的展示;渲染函数收到真实错误与可用的重试 |
393
- | `retries` | `number`,沿用 `loadRemote` 默认(2) | 透传重试次数(0–10 整数,非法值工厂调用期抛错) |
394
- | `timeout` | `number`(ms),默认不设适配层超时 | 本次组件加载等待上限;超时只结束本次等待,**不取消**已发出的共享请求;迟到的成功/失败不覆盖终态、不产生未处理 rejection |
395
-
396
- - 组件导出校验:默认导出(或模块本身)必须是函数组件 / class / `memo` / `forwardRef` 等合法组件类型;字符串、数字、空命名空间显式报错(不渲染空白成功页)
397
- - `ref` 透传:`forwardRef` 导出可正确接收 ref(React 18/19 实测);普通函数组件传 ref 遵循 React 标准行为
398
- - 渲染期异常由内置边界捕获并与网络/导出错误**分开记录与展示**(文案区分「加载失败」与「渲染出错」);ErrorBoundary 不捕获事件处理器与任意异步回调异常——这两类错误遵循 React 自身语义
399
- - 内置默认错误占位包含:错误码(FgError 的 `code`,无码渲染错误显示 `UNKNOWN`)、真实根因 message、可执行修法,以及两个恢复操作——**「重试加载」**(同页重建加载链)与**「刷新页面重试」**(仅用户点击才整页刷新,保留当前地址;用于浏览器已缓存模块失败的场景,见下条边界)。渲染阶段错误只提供「重试加载」(错误抛自远程代码本身,刷新无法修复)
400
- - 失败恢复真实穿透浏览器 ESM 失败缓存:运行时对入口 URL 与容器 expose loader 均在失败后的重试上变更 URL(`fulgurjs_retry=N`),服务恢复后点击重试可真实重新拉取(不是只在 mock 下可恢复)。并发加载同一模块失败后重试只推进一个代次(不会因并发失败产生多个重试 URL 导致模块实例分裂);已成功模块的重复访问零重复网络请求
401
- - **已知边界**:expose 的**静态依赖** chunk(expose chunk 内 `import` 的普通 chunk)失败后,同页重试不可恢复——浏览器 module map 缓存了该依赖 URL 的失败,重试换 URL 的 expose chunk 重新拉取后其静态 import 仍命中缓存失败。恢复需整页刷新——默认占位的**「刷新页面重试」**就是这条路径的用户操作(用户点击触发,保留当前地址,永不自动刷新);插件控制的动态加载边界可通过新 URL 重试,但不保证任意动态依赖都能恢复。插件不做全站依赖图递归改写来穿透该限制。此限制对应当前原生 ESM 加载路径,不能概括为 webpack MF 的共同限制(见 [对照说明](webpack-mf-对照与缺口.md#三使用限制与-webpack-的区别))
402
-
403
- #### `useLoadRemote<Module>(spec, options?)`
404
-
405
- ```ts
406
- const { data, error, loading, reload } = useLoadRemote<Utils>('remote-react/utils')
407
- ```
408
-
409
- - 返回 `{ data: Module | undefined, error: unknown, loading: boolean, reload: () => Promise<void> }`;`error` 无错误时恒为 `undefined`
410
- - `options`:`shareScope` / `retries` / `fallbackModule`(透传 `loadRemote`;配置 `fallbackModule` 是显式声明的行为——失败返回兜底值而非写 error)
411
- - 按字段比较依赖(调用方每次 render 新建 options 对象不会无限重载);spec/选项变化时清理旧数据进入新请求
412
- - 每轮 effect 与 `reload` 有独立代次:快速 A→B、慢请求晚返回、连续 reload、卸载后返回、StrictMode 双 effect 都只允许最新有效请求写状态;不宣称重复 effect 从未发生(运行时缓存去重网络与生命周期)
413
- - `reload` 开始时清空旧 data/error 并设 loading=true;当前尝试成功后写 data,失败后仅写 error,均结束 loading。卸载会作废未完成的 effect/reload,卸载后调用已保存的 reload 不发起请求。已成功缓存的模块不会重新下载;`Promise<void>` 正常结束(按钮 `onClick` 调用不产生未处理拒绝)
414
- - `AppContext` 不是 React 状态订阅:宿主读到新的非空 `sessionKey` 时由**宿主自身状态/路由**触发重新渲染(`createHostPages` 的组件缓存会在新登录代次自动重建,触发新代次 `onSession`)
415
-
416
- #### `RemoteErrorBoundary`
417
-
418
- 独立页面级兜底边界。props:`children`、`fallback`(节点或 `({ error, reset }) => ReactNode`)、`onError(error, info)`、`resetKeys`(任一变化重置边界状态,受控重试常用形态 `resetKeys={[retryEpoch]}`)。`reset` 只重置边界状态;子树若持有失败缓存(如外部 `React.lazy`)还需由调用方重建加载尝试——插件自带 `remoteComponent` 的重试已完成两者。内置 `remoteComponent` 的错误边界已消费自身错误,外层 `RemoteErrorBoundary` 看不到内层已处理的异常;想改某个远程组件的占位请用该组件自己的 `error` 选项。
419
-
420
- #### `createReactHostPages(options)`
421
-
422
- 与 Vue 侧共用同一份页面表数据与 `definePages` R1–R5 校验(5.1.0 起纯解析提取为共用内核);返回 `{ pages, resolve(path), component(spec) }`——`component(spec)` 返回 React 组件类型,路由层用 JSX / `createElement` 渲染即可(不提供 `.element()` 同义入口)。
423
-
424
- - `options` 数据项:`pages / remotePrefixes / deriveSpec / schema / strict / base`(语义与 Vue 完全一致);React 展示项:`fallback / error / retries / timeout`(语义与 `remoteComponent` 一致)+ `beforeLoad`(每次实际加载尝试前执行,供宿主刷新 context;页面表创建时不执行)
425
- - `resolve` 保持 base 剥离、最长前缀、参数解码(坏 `%` 序列只让该次匹配失败)、query/hash、无匹配返回 `null`
426
- - 组件缓存按 spec 与登录代次复用;**仅新的非空 `sessionKey` 到来时重建**(登出变 `undefined` 不重建——与 Vue 侧同语义);换账号后重新加载触发新代次 `onSession`
427
- - React 侧不提供 `keepAliveNames`(不承诺组件保活);路由不是插件的运行时依赖——示例用 React Router 7(`path` 在路由表声明、`element` 渲染 `component(spec)` 产物;带参数路由经 `useParams`/`useSearchParams` 传给远程页面 props)
428
- - 跨框架共享 Context:宿主与远程消费方经**同一 expose 实例**拿到同一 Context 对象(如远程 `expose './theme-context'` 导出 `createContext` 实例,宿主 `useLoadRemote` 取得后作 Provider,远程组件 `useContext` 读到宿主值);插件不自动桥接任意 React Context——必须显式共享该对象
429
-
430
- #### React 的 dev 类型
431
-
432
- `@fulgurjs/federation/react` 的 `.tsx`/`.ts` expose 与 Vue 共用同一套 dev 类型生成(目录、`dts:false`、`dts.dir`、setup 过滤、`devFsRoot:false` 降级全部一致),并新增**双轨**形态:零配置时生成可解析的宽松声明(导出为 `any`);在宿主**应用 TS 上下文**(`tsconfig.json` 本身、其 `extends` 链,或其 `references` 指向且 include 覆盖应用源码/类型输出目录的子项目配置;独立的 `tsconfig.test.json`、只含 vite.config 的 `tsconfig.node.json` 等无关上下文不参与判定)配置一段 `"paths": { "<remote>/*": ["<types目录>/<remote>.d/*"] }` 后,同形态导入即解析到转发模块获得**源码级类型**(props/函数签名精确,错误 props/参数编译失败)——应用上下文配置了 paths 的远程会自动跳过同名宽松声明避免遮蔽,启用说明见生成目录内 `_paths.d.ts`。
433
-
434
- 类型生成支持字符串或数组 `extends`(后项覆盖前项)、指向目录的 `references`,并按声明文件目录解析继承路径。`baseUrl` 与 `paths` 独立继承。多个实际应用上下文的远程 `paths` 接管不一致时,会保留默认宽松声明并给出中文提示;需要精确类型时请统一这些应用配置。生命周期错误 `MFU-012` 的 `cause` 保留 setup/onSession 抛出的原始异常。
435
-
436
- <a id="bridge"></a>
437
-
438
- ### 8.2 跨框架桥接 API — `/bridge`(子应用级 Vue↔React 互嵌,5.3.0 起)
439
-
440
- **产品范围**:整站挂载/卸载的双向嵌入——Vue 3 宿主嵌 React 18/19 子应用、React 18/19 宿主嵌 Vue 3 子应用。子应用内部路由与宿主 URL 同步已支持,按 §8.3 显式开启;默认关闭。组件级互转、Angular、SSR/RSC、JS 沙箱、CSS 隔离不在支持面(见本文末尾「边界」)。
441
-
442
- #### 入口与导入图
443
-
444
- ```text
445
- 构建期 @fulgurjs/federation -> 插件(不变)
446
- Vue 子应用 @fulgurjs/federation/runtime -> defineBridgeApp(零 React)
447
- React 子应用 @fulgurjs/federation/react -> defineBridgeApp(零 Vue;react-dom/client 实际 mount 时才加载)
448
- 桥接宿主 @fulgurjs/federation/bridge/vue -> createVueBridgeApp(推荐:Vue 宿主,零 React)
449
- @fulgurjs/federation/bridge/react -> createReactBridgeApp(推荐:React 宿主,零 Vue)
450
- @fulgurjs/federation/bridge -> 聚合入口(兼容保留;dev 原生 ESM 会同时执行两个宿主适配器)
451
- ```
452
-
453
- **推荐用法是分离入口**:只用 `createVueBridgeApp` 的宿主页在 dev 首屏与生产产物中都不执行 React 宿主适配器,反之亦然(e2e 断言请求图)。聚合 `/bridge` 在生产可摇树、在 dev 无摇树保证——文档与示例默认分离入口。
454
-
455
- **双框架安装合同(必须)**:桥接宿主同时安装 `vue` + `react` + `react-dom`,shared 三键全部 `singleton: true`:
456
-
457
- ```ts
458
- // 桥接宿主 fulgurjs.config.ts
459
- shared: {
460
- vue: { singleton: true },
461
- react: { singleton: true },
462
- 'react-dom': { singleton: true },
463
- }
464
- ```
465
-
466
- 子应用只装并共享自己的框架(Vue 子应用:`vue`;React 子应用:`react` + `react-dom`)。纯 Vue / 纯 React 项目的零对方依赖承诺不受影响。共享子路径(`react/jsx-runtime`、`react/jsx-dev-runtime`、`react-dom/client`)由 shared 机制协商单实例;宿主侧另需为 `react`、`react-dom` 配置 shared(子路径协商依赖父键)。缺 singleton 的真实症状(Invalid hook call、双实例)见 §6 错误码表 `MFU-010` 与避坑指南——插件按协商机制如实运行,不拦截配置违例。
467
-
468
- #### 子应用侧:`defineBridgeApp`(`/runtime` 与 `/react` 同名双导出)
469
-
470
- 远程 expose `./bridge` 的模块**默认导出**契约对象;插件校验 `mount`/`unmount` 均为函数,否则 `MFU-015`:
471
-
472
- ```ts
473
- // Vue 子应用 src/bridge.ts —— fulgurjs.config exposes: { './bridge': './src/bridge.ts' }
474
- import { createApp } from 'vue'
475
- import { createMemoryHistory, createRouter } from 'vue-router'
476
- import { defineBridgeApp } from '@fulgurjs/federation/runtime'
477
- import App from './App.vue'
478
-
479
- export default defineBridgeApp((props) => {
480
- const app = createApp(App, props)
481
- app.use(createRouter({ history: createMemoryHistory(), routes }))
482
- return app // 返回装配完整的 VueApp;mount/unmount 由契约负责
483
- })
484
- ```
485
-
486
- ```tsx
487
- // React 子应用 src/bridge.tsx
488
- import { MemoryRouter } from 'react-router-dom'
489
- import { defineBridgeApp } from '@fulgurjs/federation/react'
490
-
491
- export default defineBridgeApp((props) => (
492
- <MemoryRouter><App {...props} /></MemoryRouter>
493
- ))
494
- ```
495
-
496
- 契约语义(`BridgeApp` 接口,双方入口共享同一类型定义):
497
-
498
- - `mount(el, props?): void | Promise<void>`——返回 `void` 表示首次根提交已同步完成(Vue 同步 mount);返回 Promise 时宿主保持 pending 直到首次根提交后完成(React 由契约内建提交探针兑现,`root.render()` 返回**不**算成功)。首次提交前的失败必须抛错/拒绝(宿主转 `MFU-016`,`details.phase: 'mount'`)并清理已创建的 app/root。
499
- - `unmount(el): void`——同步使该容器代次失效并清理;未知容器为 no-op。pending 时卸载立即作废本轮代次,迟到的成功/失败不得复活 DOM、改写宿主状态或产生未处理拒绝。unmount 抛错由宿主捕获报 `MFU-016`(`phase: 'unmount'`),该容器清理状态不确定,插件会**持久封锁该容器**:同页「重试加载」与换会话都不会在此容器重新挂载(默认占位随之移除「重试加载」按钮),只能整页刷新恢复;残留资源(事件订阅/定时器/全局副作用)请如实排查。
500
- - 契约实例**按容器 el 分键**:同一契约多处挂载互不干扰;同一容器未卸载再次 mount 拒绝(`MFU-016`,容器已被占用)且不覆盖原实例。
501
- - 首次根提交后的子应用内部错误由**子应用自己的错误边界**负责——宿主 ErrorBoundary/errorCaptured 捕不到跨 root 的渲染错误,插件不冒充兜底(跨组件树的规则,不承诺「宿主兜底子应用一切错误」)。
502
-
503
- #### 宿主侧工厂(`/bridge/vue` 与 `/bridge/react`)
504
-
505
- ```ts
506
- // Vue 宿主
507
- import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
508
- import { getLatestHostContext } from './host-context' // 宿主自有的同步纯 getter
509
-
510
- const RemoteReactApp = createVueBridgeApp('bridge-react-remote/bridge', {
511
- retries: 1,
512
- getContext: () => getLatestHostContext(),
513
- })
514
- // 模板:<RemoteReactApp :session-key="loginKey" :app-props="{ userId, onReady }" />
515
- ```
516
-
517
- ```tsx
518
- // React 宿主
519
- import { createReactBridgeApp } from '@fulgurjs/federation/bridge/react'
520
- const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', {
521
- getContext: () => getLatestHostContext(),
522
- })
523
- // JSX:<RemoteVueApp sessionKey={loginKey} appProps={{ userId, onReady }} />
524
- ```
525
-
526
- | 项 | `createVueBridgeApp`(Vue 宿主) | `createReactBridgeApp`(React 宿主) |
527
- |---|---|---|
528
- | 工厂选项 | `loadingComponent?` `errorComponent?`(收到 `error` prop,完全接管) `retries?`(0–10 整数) `timeout?`(正有限 ms) `getContext?` | `fallback?`(pending 占位) `error?`(节点或 `(error, retry) => ReactNode`) `retries?` `timeout?` `getContext?` |
529
- | 返回组件 props | `appProps: P`(业务数据)+ `sessionKey?: string \| null`(控制参数,不混入业务 props) | 同左,`ComponentType<{ appProps: P; sessionKey?: string \| null }>` |
530
- | 泛型 | `createVueBridgeApp<P>(spec, options?)`,P 只约束 `appProps` | 同左 |
531
- | spec | 完整 `<remote>/<expose>`,与 `remoteComponent` 同一解析规则;无 remotePrefixes/schema/deriveSpec | 同左 |
532
- | 默认错误占位 | 中文诊断(错误码+根因+修法)+「重试加载 / 刷新页面重试」 | 同左 |
533
-
534
- - **`appProps` 快照语义**:挂载时浅拷贝顶层字段传入,嵌套对象/响应式 store/函数保留原引用;之后的顶层替换**不追踪、不重渲染子应用**,需要重置用 `:key`/key 重建。宿主新闭包不会自动传给子应用——实时读取宿主状态请传稳定回调(内部读 ref/store)或主动重挂。跨 root 不继承宿主 provide/inject、Pinia、React Context 或路由——需要的数据经 `appProps`、AppContext、共享实例或子应用自装。
535
- - **`getContext`**:无副作用的**同步** getter,在首次、重试及换会话的实际加载前调用;返回快照对象(拒绝 Promise/thenable 与非对象——`MFU-016`,`phase: 'getContext'`)。桥接层先校验快照 `sessionKey` 与受控值一致(不一致 `MFU-017`,且不写全局),**校验通过后由桥接层调用 `provideAppContext`**——getter 本身不写全局。未提供 getter 时校验现有 `AppContext.sessionKey` 必须与受控值一致。换代时桥接层先 `clearAppContext()` 清旧账号独有字段再写新快照,保证零旧账号残留。
536
- - **`sessionKey` 受控语义**:只接受 `undefined`(不启用受控会话)/`null`(登出态:立即卸载、保持空容器、不再 loadRemote)/非空字符串(登录代次)。空字符串、数字等非法值按 `MFU-017` 拒绝挂载。
537
-
538
- | 触发 | 行为 |
539
- |---|---|
540
- | 首次渲染,`sessionKey` 非空字符串 | getContext(若提供)→ 校验快照/现有 context → 桥接层 provideAppContext → loadRemote → 契约校验 → `contract.mount(el, appProps 快照)`;远程 onSession 用同一代次 |
541
- | 首次渲染,`sessionKey` 省略 | 不启用受控校验;仍可提供快照或复用现有 AppContext;远程声明 onSession 时按 runtime 既有规则(无 sessionKey → `MFU-013`) |
542
- | `sessionKey` A→B | 推荐宿主先置 null 等卸载、`clearAppContext()` 后再更新;直接 A→B 时包装组件先作废并卸载 A、确认完成后才写 B 的 context 并挂载 |
543
- | `sessionKey` → `null` | 立即作废旧加载并卸载;保持空容器不再请求;宿主随后 `clearAppContext()` 并移除/禁用缓存的私有页面 |
544
- | 同会话重渲染 / 只换 `appProps` 引用 | 不重挂、不重复 loadRemote;业务数据仍是上次挂载快照 |
545
- | 点错误占位「重试加载」 | 同页重建尝试(成功模块走运行时缓存;失败入口按现有机制换 URL 重取) |
546
-
547
- - **多实例与页面级单会话**:同页多个同 spec 实例并存合法(契约按 el 分键);`AppContext` 是页面级单例——同页所有受控桥接实例必须同一会话,后挂实例与活跃实例代次不一致按 `MFU-017` 拒绝(不让两实例互相覆盖身份)。不承诺同页同时承载两个账号。
548
- - **DOM 所有权**:包装组件只创建并保持稳定的空挂载容器;pending/error 占位是它的兄弟节点,宿主重渲染不 patch 子应用 root 内部。React 宿主 StrictMode 双 effect(mount→cleanup→mount)安全。Vue `<KeepAlive>` 的 deactivate 不是卸载——缓存页中的子应用保有 root 与状态;需要离页即销毁就别缓存该页,登出流程应同时移除缓存的私有页面。
549
- - **旧请求不冒充取消**:已进入 `loadRemote` 的工作不因桥接层作废而被取消——迟到的旧结果按代次丢弃(不 mount、不覆盖、无未处理拒绝);远程 `onSession` 必须遵守既有 `signal.aborted` 契约(异步等待后、写私有状态前检查信号)。
550
-
551
- <a id="url-sync"></a>
552
-
553
- ### 8.3 桥接 URL 同步 — `/bridge/router/*`(子应用内部路由 ↔ 宿主浏览器地址,5.4.0 起)
554
-
555
- 桥接默认 memory 路由:子应用内部跳转不改浏览器地址、刷新不能恢复子应用内部页面。URL 同步让**宿主 URL 表达子应用内部位置**——刷新直达、收藏分享、前进后退、宿主菜单跳转全部一致。显式开启,**默认关闭**(5.3.x 行为与老契约完全不变)。
556
-
557
- **架构约定**:宿主 Router 是浏览器历史唯一写入方;子应用使用受控 memory 路由;两端经独立路由通道(不进 appProps/Context)传递位置;同实例内 path/search/hash 变化**不重挂 root、不重建 store、不重新加载远程**。
558
-
559
- **① 宿主(Vue Router 4,history/hash 模式皆可)**:
560
-
561
- ```ts
562
- // main.ts:宿主路由声明后缀匹配(缺它详情导航会卸载子应用!)
563
- const router = createRouter({
564
- history: createWebHistory(import.meta.env.BASE_URL), // hash 模式用 createWebHashHistory
565
- routes: [
566
- { path: '/', component: Home },
567
- { path: '/approval/:pathMatch(.*)*', component: ApprovalBridgePage },
568
- ],
569
- })
570
- // 真实权限守卫:拒绝 → 通道收到 cancelled,URL/历史/子应用位置全部不变
571
- router.beforeEach((to) => (to.path.startsWith('/approval/secret') ? false : undefined))
572
- ```
573
-
574
- ```vue
575
- <!-- ApprovalBridgePage.vue:路由 prop = 独立控制通道 -->
576
- <script setup lang="ts">
577
- import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
578
- import { createVueBridgeNavigation, type BridgeHostRouting } from '@fulgurjs/federation/bridge/router/vue'
579
- const navigation = createVueBridgeNavigation(router) // Vue Router fullPath 已是逻辑路径,无需传部署 base
580
- const routing: BridgeHostRouting = { basePath: '/approval', navigation }
581
- const RemoteApp = createVueBridgeApp('remote/bridge-routed', { /* 同 8.2 */ })
582
- </script>
583
- <template>
584
- <RemoteApp :session-key="sess" :routing="routing" :app-props="props" />
585
- </template>
586
- ```
587
-
588
- **② 子应用(声明协议 + 接线受控路由)**:
589
-
590
- ```ts
591
- // bridge.ts(Vue 子应用):defineBridgeApp(工厂, { routing: true })——工厂第二参数 { signal, routing }
592
- import { createRouter, createMemoryHistory, RouterView } from 'vue-router'
593
- import { defineBridgeApp } from '@fulgurjs/federation/runtime'
594
- import { connectVueBridgeRouter } from '@fulgurjs/federation/bridge/router/vue'
595
- export default defineBridgeApp(async (props, ctx) => {
596
- if (!ctx?.routing) throw new Error('本契约需要宿主启用 URL 同步')
597
- const router = createRouter({ history: createMemoryHistory(), routes: [
598
- { path: '/list', component: List },
599
- { path: '/detail/:id', component: Detail },
600
- ] })
601
- await connectVueBridgeRouter(ctx.routing!, router, { signal: ctx.signal }).ready // 初始 push 落定后再 install(顺序不能反)
602
- const app = createApp({ setup: () => () => h(RouterView) }, props)
603
- app.use(router)
604
- return app
605
- }, { routing: true })
606
- ```
607
-
608
- ```tsx
609
- // bridge.tsx(React 子应用):createReactBridgeRouter 返回 RouterProvider 元素
610
- import { createReactBridgeRouter } from '@fulgurjs/federation/bridge/router/react'
611
- import { defineBridgeApp } from '@fulgurjs/federation/react'
612
- export default defineBridgeApp((_props, ctx) => {
613
- if (!ctx?.routing) throw new Error('本契约需要宿主启用 URL 同步')
614
- return createReactBridgeRouter(ctx.routing, [
615
- { path: '/list', element: <List /> },
616
- { path: '/detail/:id', element: <Detail /> },
617
- ], { signal: ctx.signal }).element
618
- }, { routing: true })
619
- ```
620
-
621
- 宿主端 React Router 仅支持 **data router 模式**(`createBrowserRouter` / `createHashRouter` + `RouterProvider`):`createReactBridgeNavigation(router, { basename, canNavigate })`。`canNavigate` 可选,仅作提前拒绝;端口观察真实 `useBlocker` 状态,等待 `reset()` 返回 cancelled、`proceed()` 后实际位置提交返回 committed,不能只凭 navigate 的 Promise 落定判成功。declarative 模式(BrowserRouter)无取消语义,不支持。React Router 要求 ≥ 6.11(createMemoryRouter)。
622
-
623
- **③ 行为契约与边界**:
624
-
625
- - **basePath**:宿主路由视角的静态绝对路径(拒绝空/根/带 query·hash·通配符,`MFU-030`);按路径段匹配(`/approval` 命中 `/approval/detail/1`,不命中 `/approval-old`);同页各同步实例前缀不得相同或重叠。`/approval` 对应子应用 `/`;根重定向由子应用路由定义、以 replace 规范化(不凭空多一条历史)。
626
- - **Vite base 与路由分层**:部署在 `/erp/` 时 Vite base/宿主 Router base 是 `/erp/`,bridge basePath 仍是 `/approval`(Vue Router 已自动剥离 history base,React 端口传 `basename`);适配器输出的逻辑路径不含部署前缀,不会拼出 `/erp/erp/...`。子目录部署 + hash 模式内层片段见 e2e fixtures(`fixtures/host-bridge-*/`,可运行参考实现)。
627
- - **位置三段全等**:search/hash 原样保留(重复 query 键、编码、中文、片段不二次 decode/encode);仅参数变化也同步,且不重挂。
628
- - **取消语义**:Vue Router 4 的 push/replace 落定 `NavigationFailure` 即真实取消;React Router data router 等待真实 blocker 的取消/放行,`canNavigate` 仅为可选提前拒绝。取消后 URL、历史、子应用位置保持最后确认状态,**绝不自动重试**(`router.push` 的函数返回不冒充提交成功)。
629
- - **导航与错误**:子应用 push/replace 保留原动作,go/back/forward 委托宿主历史;连续请求串行落定,外部导航作废旧的在飞与排队请求。守卫/加载器/端口执行异常拒绝 Promise(MFU-033,保留 cause),不会伪装 cancelled。两端子路由接线的第三参数 `{ signal?: AbortSignal }` 默认为空;推荐传 `ctx.signal` 自动 dispose,未传时由子应用显式调用连接的 `dispose()`。自定义 `BridgeHostNavigation.navigate(target, action, { signal })` 应在异步提交前复核可选 signal,已 aborted 时禁止迟到写入。
630
- - **会话与生命周期**:换账号/登出(`sessionKey→null`)作废旧通道——旧通道的导航一律 cancelled、不写 URL、不复活子应用;unmount 后迟到通知失效(通道销毁,再订阅得 `MFU-031`)。KeepAlive 缓存离页的实例暂停路由写入(不抢占 URL、不销毁通道,激活重同步)。同一容器 unmount 抛错的持久封锁(BN09)不因路由绕过。
631
- - **协议校验**:宿主启用 routing 而子应用未声明 `{ routing: true }`(契约 `routing: { protocol: 1 }`)→ `MFU-031` 占位,**不静默退回 memory 假装深链成功**。
632
- - **非法导航与循环**:目标越界自身前缀(`../`、跨前缀)、非法 `go` 参数 → `MFU-032`;连续内部 replace 超过 5 次(重定向环)→ `MFU-033`(附目标链,不静默回入口)。
633
- - **按需加载**:`/bridge`、`/runtime`、`/react` 默认入口不引入任何路由库;`/bridge/router/vue`、`/bridge/router/react` 为按需入口(`vue-router` / `react-router-dom` 为可选 peer,消费者自装)。两个入口各 ≤4096B gzip 门禁。
634
- - **不承诺**:SSR/RSC、跨浏览器窗口、嵌套多级桥接子应用路由代理、TanStack Router 及其他路由库(可经 `BridgeHostNavigation`/`BridgeChildRoute` 端口自定义扩展)。
635
-
636
- <a id="context"></a>
637
-
638
- ### 9. `AppContext` — 跨应用传值与方法引用(`@fulgurjs/federation/runtime`)
639
-
640
- 宿主向子应用传值、子应用向宿主反向注册方法,一律走这条一等公民通道(对标乾坤 `props`,但带类型与错误契约)——不再各自挂 `window.*` 裸口子。
641
-
642
- ```ts
643
- // —— 宿主桥(host/src/fulgurjs/host/bridge.ts):登录态同步(可多次调用,幂等 merge) ——
644
- import { provideAppContext, clearAppContext } from '@fulgurjs/federation/runtime'
645
-
646
- provideAppContext({
647
- user, // 宿主登录用户原始形态(当时快照)
648
- getToken, // 取最新 token(拉取式防过期)
649
- store: piniaInstance, // 宿主 pinia:子应用 useUserStore(ctx.store) 共享响应式状态
650
- hostApp: app, // 宿主 Vue App 实例:全局组件/指令注册目标
651
- locale, // EP locale 等 UI 配置
652
- sessionKey: 's-101-1730…', // 非敏感登录代次 ID:每次成功登录/重登生成新值;
653
- // token 刷新但会话未变时沿用。远程 onSession 按它去重,
654
- // 缺失时声明了 onSession 的远程报 MFU-013。不是 token、不做授权凭证
655
- events: { main: mainEvents }, // 事件/方法池:宿主提供 main;子应用反向注册 bpm.* / lowcode.*
656
- // 只传有真实消费的键。项目自定义键经扩展位按需自行提供(如 baseUrl: '/demo')
657
- })
658
-
659
- // 退出登录时清理:删 context + 作废远程会话信号/onSession 去重状态
660
- // (不重置远程模块缓存、共享模块图与应用级 setup 注册)
661
- clearAppContext()
662
-
663
- // —— 远程 setup/onSession:显式校验消费 ——
664
- import { requireAppContext } from '@fulgurjs/federation/runtime'
665
-
666
- const { store, user, hostApp } = requireAppContext('store', 'user', 'hostApp')
667
- // 缺任一键 → [fulgurjs:CC-001] 三段式抛错(got / expected / example 指向宿主桥);
668
- // 页面无运行时单例(独立直开远程页)→ [fulgurjs:CC-002] 显式,修法 = 经宿主联邦加载。
669
-
670
- // —— 远程页面读点 ——
671
- import { getAppContext } from '@fulgurjs/federation/runtime'
672
- const dict = getAppContext().events?.main?.getDictItems?.('sex')
673
-
674
- // —— 子应用反向注册方法给宿主(页面 onUnmounted 时记得摘除,见迁移指南「页面卸载清理清单」)——
675
- getAppContext().events!.bpm = { formEvent, formSubmitEvent }
676
- ```
677
-
678
- 标准字段表:
679
-
680
- | 字段 | 类型 | 语义 | 写方 |
681
- |---|---|---|---|
682
- | `user` | `Record<string, any>` | 宿主登录用户原始形态(提供时快照) | 宿主桥(只读约定;登录态变化时重新 provide 覆盖) |
683
- | `getToken` | `() => string \| undefined` | **取最新 token**(拉取式调用,永不过期;context 不提供一次性 token 快照字段) | 宿主桥(只读约定) |
684
- | `store` | `unknown`(运行时为宿主 pinia) | 子应用挂载/读取宿主共享响应式状态 | 宿主桥(只读约定) |
685
- | `hostApp` | Vue App 实例(同 realm 直引用) | 全局组件/指令注册目标 | 宿主桥(只读约定) |
686
- | `locale` | `unknown` | EP locale 等 UI 配置 | 宿主桥(只读约定) |
687
- | `sessionKey` | `string` | 非敏感登录代次 ID:每次成功登录/重登生成新值;token 刷新但会话未变时沿用。远程 onSession 按它去重(同一代次只执行一次,换代自动重跑);退出 `clearAppContext` 后必须重跑。生成责任在宿主登录流程;**不得用真实 token 充当**,也不作为授权凭证 | 宿主桥(登录后) |
688
- | `events` | `Record<string, any>` | 事件/方法池:`events.main.*` 宿主提供、`events.bpm.*` / `events.lowcode.*` 子应用反向注册 | 宿主桥建池,子应用挂载 |
689
- | (扩展位) | `[key: string]: unknown` | 项目自定义键(`formUrl` / `baseUrl` 等按需自行提供,模板默认不传) | 宿主桥;远程 boot 只增不改宿主键 |
690
-
691
- 变更语义与时序契约:
692
-
693
- - `provide` = 顶层 merge(后写覆盖,幂等可多次);约定「宿主先写标准字段,远程只增不改宿主键」;嵌套对象(如 `events`)是**引用共享**,子应用挂属性即时可见(同 realm 直引用);
694
- - 时序契约:**bridge(provide)→ 远程 setup/onSession(require)→ 页面模块返回**——违反即在初始化处显式失败(CC-001 / MFU-013),不静默;
695
- - 数据语义 = **传输层快照 + 函数引用,非响应式**(与乾坤 props 同语义)。"实时"由两条正规通道承担:① `getToken()` / `events.main.*` 函数引用每次调用执行宿主最新闭包;② `context.store` 把宿主 pinia 递给子应用(共享响应式实例)。**同页换账号(退出→B 登录→再打开远程页)不依赖页面刷新**:宿主重新 provide 最新 context + `sessionKey`,远程 `onSession` 检测到新代次自动重跑,同步用户/权限/字典等会话状态(见 §10)。context 本体不做 Vue reactive(runtime 框架无关 + gzip 红线 + 跨包 proxy 双份陷阱);"中途变更需通知"的场景:等真实需求出现再设计(当前无此场景,不预留空 API)。
696
-
697
- 方法引用两条通道:
698
-
699
- | 通道 | 语义 | 适用 |
700
- |---|---|---|
701
- | **context 携带函数引用** | 同步直调(bridge 先于一切页面加载) | 高频热路径(`getToken` / `getDictItems` / `getFileAccessHttpUrl`)、子应用反向注册(`formEvent`) |
702
- | **exposes 方法模块** | `exposes: { './api': './src/fulgurjs/exposes/api.ts' }` → `const { xxx } = await loadRemote('remote/api')` | 低频/重逻辑跨应用调用;任意 expose 任意消费;dts 类型直连自动覆盖 |
703
-
704
- 方法模块规范:`src/fulgurjs/exposes/` 下的 `api.ts` 导出纯函数/服务对象(不挂 Vue 组件);依赖宿主单例的函数(如 defHttp 走 shared)直接写,联邦协商保证同模块图。
705
-
706
- 端到端示例(提供方 admin,消费方任意应用):
707
-
708
- ```ts
709
- // ① 提供方 vite.config.ts:exposes 加一条
710
- exposes: { './api': './src/fulgurjs/exposes/api.ts' }
711
-
712
- // ② 提供方 src/fulgurjs/exposes/api.ts:导出纯函数
713
- import { defHttp } from '/@/utils/http/axios'
714
- export function getDictItems(dictCode: string) {
715
- return defHttp.get({ url: '/sys/dict/getDictItems/' + dictCode }, {})
716
- }
717
-
718
- // ③ 消费方(任意页面,类型直连自动覆盖):
719
- const { getDictItems } = await loadRemote('demo-host/api')
720
- const res = await getDictItems('sex')
721
- ```
722
-
723
- 存储说明:context 的存储本体即全局镜像对象 `window.__FULGURJS_APP_CONFIG__`(页面级单例,跨 bundle 副本共享同一份;调试面板可直接查看)。
724
-
725
- ### 9.1 可选的项目接入示例:保活、加载提示、预载和诊断页
726
-
727
- 本节记录项目侧的组合用法。常量、页面及诊断面板需要接入方自己实现,不是安装插件后自动生成的公开 API,也不是每个项目都必须配置的内容。
728
-
729
- 这些能力全部是**项目侧**能力(手工集成的项目按下述接入点自行落位;`fulgurjs init` 只生成配置起步模板,不生成这些项目文件),插件 runtime.js 零参与。配置面总览:
730
-
731
- | 能力 | 配置项 | 类型 | 默认值 | 配置位置 |
732
- |---|---|---|---|---|
733
- | 页面保活 | `keepAlive` | `boolean` | `false` | 页面路由表条目(`src/fulgurjs/host/pages.ts`) |
734
- | 页面加载骨架屏 | —(内置,无配置项) | — | 见下方内置参数 | `src/fulgurjs/host/pages.ts` 页面工厂 |
735
- | 空闲预载 | `PREFETCH_REMOTES` | `string[]` | `[]`(关闭整远程预载,按需加载;详见 §9.1.3) | `src/fulgurjs/host/bridge.ts` 顶部常量 |
736
- | 联邦诊断面板 | —(内置页面) | — | 常驻 | 路由 `/fulgurjs-demo` |
737
-
738
- #### 9.1.1 页面保活 — `keepAlive`
739
-
740
- 页面级布尔开关:开启后该页面切换到其他标签页时**组件实例不销毁**(deactivate),切回时表单输入、筛选条件、滚动位置原样恢复。
741
-
742
- | 属性 | 类型 | 默认值 | 说明 |
743
- |---|---|---|---|
744
- | `keepAlive` | `boolean` | `false` | `true` = 该页面纳入 LayoutContent 联邦分支 `<keep-alive>` 的 include 白名单 |
745
-
746
- ```ts
747
- // src/fulgurjs/host/pages.ts — 页面路由表条目
748
-
749
- // 开启保活(显式)
750
- { route: '/flowable/bpm/task/todo', name: 'BpmTodoTask', title: '待办任务', keepAlive: true }
751
-
752
- // 关闭保活:不写该字段,或显式 false(二者等价,默认即关)
753
- { route: '/flowable/bpm/manager/form', name: 'BpmForm', title: '流程表单', keepAlive: false }
754
- ```
755
-
756
- 行为与边界:
757
-
758
- - 缓存上限 `max: 8`(Vue 原生 LRU,超出后最久未访问的页面实例被销毁);
759
- - 缓存键 = 页面 spec 清洗名(`Fulgurjs_<remote>_<expose键>`),同一路由不同参数(fullPath 不同)各占一个缓存条目;
760
- - **默认全关的原因**:vxe-table、表单设计器等重型组件的缓存内存成本高,按页面逐个显式开启;
761
- - 开启页面的组件若注册了 `window` 级监听/定时器/context 反向注册,须遵循迁移指南「三D 页面卸载清理清单」(保活页只在真正被 LRU 淘汰时才 unmount)。
762
-
763
- #### 9.1.2 页面加载骨架屏 — 内置 `loadingComponent`(无配置项)
764
-
765
- 页面组件工厂的 `defineAsyncComponent` 内置了加载期占位:联邦页面 chunk 下载/模块执行期间,内容区显示 4 条渐变动画骨架条而非白屏。**本能力无配置项**,内置参数如下:
766
-
767
- | 内置参数 | 值 | 说明 |
768
- |---|---|---|
769
- | `loadingComponent` | `FulgurjsSkeleton`(4 条渐变动画条) | 工厂内置组件,位于 `src/fulgurjs/host/pages.ts` |
770
- | `delay` | `200`(毫秒) | 超过 200ms 未加载完成才显示骨架——快速加载时不闪烁 |
771
- | `errorComponent` | 内置错误占位 | 加载失败显示 spec + 完整错误(错误码 + 根因 + 修法) |
772
-
773
- 如需自定义加载占位(如品牌 logo 动画),不经页面工厂,改用 `remoteComponent(spec, { loadingComponent })`(见 §8)。
774
-
775
- #### 9.1.3 空闲预载 — `PREFETCH_REMOTES`(默认关闭)
776
-
777
- **先分清四层(重要,勿把「路由声明多」当成「首屏会执行所有页面代码」)**:
778
-
779
- | 层 | 机制 | 时机 | 网络成本 |
780
- |---|---|---|---|
781
- | ① 路由表声明 | `pages.data.ts` 26 条记录只是**数据映射**,不导入任何远程代码 | 构建期 | 零 |
782
- | ② 页面真实加载 | `createHostPages` 对每页 `defineAsyncComponent` 包装,**渲染时**才 `loadRemote(spec)` | 用户打开该页 | 该页 chunk + CSS(首次该远程还有入口/共享依赖/setup) |
783
- | ③ 单页预取 | `preloadRemote('remote-a/pages/remote-a/home', { mode: 'prefetch' })` | 项目主动调用 | 该 expose 的 chunk + CSS(**只下载不执行**) |
784
- | ④ 整远程预取 | `preloadRemote('remote-a', { mode: 'prefetch' })` | 项目显式开启 | manifest 全部 expose 的 chunk + CSS(**只下载不执行**) |
785
-
786
- 预取是**下载**(`modulepreload`/`stylesheet` 链接,`fetchPriority=low` 只是降低优先级、不等于不下载),**不等于执行页面代码**——`container.get()`、组件实例化、`setup/onSession` 都只由真实页面的 `loadRemote` 触发。已加载模块有 Promise 缓存:重复打开同页复用模块,换账号重做会话初始化但不重新下载 JS。多个 expose 共享同一 chunk 是正常打包结果。
787
-
788
- 宿主桥默认**关闭整远程预载**(`PREFETCH_REMOTES = []`):首次进入联邦页只下载该页所需资源,dashboard 不因登录而提前下载两个远程的全部页面文件。某项目的用户路径确有明确的「下一步页面」时,可低优先级预取一两个明确指定的 spec;把 `PREFETCH_REMOTES` 写成远程名列表则是**显式选择**整远程预热(下载完整 expose 清单)。
789
-
790
- > 插件配置面(`FederationOptions`)**没有** `host.prefetch` 字段——4.1.0 前文档曾声称该配置存在,属错误描述,已订正。预载名单就是宿主桥里的常量,改名单只改这一个地方。
791
-
792
- ```ts
793
- // src/fulgurjs/host/bridge.ts 顶部常量
794
- const PREFETCH_REMOTES: string[] = [] // 默认:关闭整远程预载(按需加载)
795
-
796
- // 只预取下一步很可能打开的明确页面(低优先级,只下载不执行)
797
- // idle(() => preloadRemote('mes-bpm/pages/bpm/task/todo', { mode: 'prefetch' }))
798
-
799
- // 显式预热整个远程(下载完整 expose 清单;确有真实使用路径再开启)
800
- // const PREFETCH_REMOTES: string[] = ['mes-bpm', 'mes-lowcode']
801
- ```
802
-
803
- 行为与边界:
804
-
805
- - 预载失败**不阻断业务**:runtime 按 MFU-007 语义发出 `window` 的 `fulgurjs:error` 事件并 console 警告(诊断面板⑤可查历史);
806
- - 预载注入 `<link rel="modulepreload">` 与 `<link rel="stylesheet">`,不执行模块——首次打开页面时才真正初始化容器;`preload` 等待样式 load/error,`prefetch` 低优先级后台加载;
807
- - 触发时机:宿主桥每次页面加载同步执行(幂等),实际预取发生在浏览器空闲回调中。
808
-
809
- #### 9.1.4 联邦诊断面板(免登录页,无配置项)
810
-
811
- 演示页升级为运行时诊断面板(源自乾坤 v3 inspector 概念的轻量化),访问路由 `meta.ignoreAuth` 的 `/fulgurjs-demo`(prod 为 `/main/fulgurjs-demo`),六块信息实时读取运行时注册表:
812
-
813
- | 块 | 内容 |
814
- |---|---|
815
- | ① 方法模块调用演示 | 按钮实调 `loadRemote('demo-host/api')` → `getDictItems('sex')` 并显示结果(方法引用通道②的活样例) |
816
- | ② remotes 状态 | 各 remote 的 entry / 加载状态(idle/loading/loaded/failed)/ 容器加载耗时 |
817
- | ③ shared 协商 | 共享键 → version ← 提供方(多版本并存可见) |
818
- | ④ context 快照 | AppContext 每个键的值形态(函数引用 / 对象 / 字符串,含 events 池) |
819
- | ⑤ fulgurjs:error 历史日志 | window 事件累积(时间戳 + remote + 错误消息),本轮会话零错误显示"无错误" |
820
- | ⑥ 远程资源加载耗时 | performance resource 中 fulgurjs / remoteEntry / chunk 相关条目与耗时 |
821
-
822
- #### 9.1.5 IDE 说明(`src/fulgurjs/` 目录的红波浪线)
823
-
824
- - `types/` 下的 `*.d.ts` 是**插件每次 dev 自动生成**的类型直连声明(勿手改):内部 `export * from '../../../demo-app-xxx/src/***.vue'` 指向**兄弟工程的源码**。命令行 `vue-tsc --noEmit`(走本应用 tsconfig,skipLibCheck 生效)为 **0 错误**;但 **VSCode/Volar 在打开这些 d.ts 时**可能把工程外 .vue 用推断项目(inferred project,无 tsconfig 上下文)展开检查,显示大片"找不到模块 '@/...'"——**仅编辑器显示问题,不影响命令行检查与构建**,不打开 `types/` 生成物即无感。
825
- - 升级插件版本后若 `import ... from '@fulgurjs/federation/runtime'` 报 ts(2307):是 IDE 的 TS 服务缓存了旧包——`Restart TS Server`(⌘⇧P)或重开窗口即可。3.0.0 起旧子路径(`@fulgurjs/federation/{context,pages,vue}`)已从包 exports 删除,按迁移映射改为 `@fulgurjs/federation/runtime`。
826
- - **根治红波浪线**:`federation({ dts: { mode: 'shim' } })` —— 生成物不再引用跨工程源文件(宽松占位形态),IDE 全程干净;取舍是失去"跳转直达远程源码"的补全能力(默认 `source` 不变,按项目偏好选择)。
827
-
828
- ### 10. `createHostPages` 与 `setup`/`onSession` — 宿主页面适配器与远程初始化(`@fulgurjs/federation/runtime`)
829
-
830
- #### 10.1 `createHostPages(options)` — 宿主页面适配器
831
-
832
- ```ts
833
- import { createHostPages } from '@fulgurjs/federation/runtime'
834
-
835
- export const hostPages = createHostPages({
836
- pages: [/* PageRouteLike[]:宿主路由与布局共用的唯一页面来源 */],
837
- remotePrefixes: { '/remote-a/': 'remote-a' }, // 必填;最长前缀匹配
838
- deriveSpec: (route) => `pages/${...}`, // 可选;缺省 = 去首段 + 剥 :参数 段
839
- schema: remoteSchema, // 可选;dev 探针结果(R3 校验),build 为空表诚实降级
840
- strict: true, // 可选;ERROR 级校验失败默认 throw
841
- base: '/main', // 可选;resolve 时剥离的站点 base 前缀
842
- beforeLoad: () => { /* 每次页面模块实际加载前执行(同步/异步);宿主在此提供最新 context */ },
843
- loadingComponent: MySkeleton, // 可选;缺省无骨架
844
- errorComponent: MyError, // 可选;缺省 = 内置三段式错误占位
845
- delay: 200, // 可选;骨架屏延迟 ms
846
- })
847
- ```
848
-
849
- | 返回成员 | 说明 |
850
- |---|---|
851
- | `pages` | 原页面记录(不经改写,宿主路由注册直接用) |
852
- | `resolve(path)` | `{ page, remote, spec, params } \| null`——兼容 base 前缀与深链;参数解码失败只让该次匹配失败(console 提示),不抛错 |
853
- | `component(spec)` | 异步页面组件(`<remote>/<exposes键>` 完整 spec);同 spec 复用。**会话感知**:AppContext.sessionKey 变化(换账号/重登/退出)后自动重建组件——下一次渲染重新走 `beforeLoad → loadRemote`,触发新代次的 `onSession`(模块本体经 loadRemote 缓存复用,不重复下载)。无 sessionKey 的用法缓存永不失效 |
854
- | `keepAliveNames` | `keepAlive: true` 页面的组件 name(与实际被 KeepAlive 缓存的包装组件一致,直接绑 `<keep-alive :include>`) |
855
-
856
- 行为契约:
857
-
858
- - 页面表经 `definePages` 校验(R1 剥参冲突 / R2 重复 spec / R3 spec 存在性 / R4 遮蔽与重复 / R5 重名),行为与独立使用完全一致;
859
- - `remotePrefixes` **最长前缀优先**(不再是"先到先得");页面路由无匹配前缀在 `createHostPages` 创建期即报错(fail fast);
860
- - 组件是**本地包装组件**(稳定 name 供 KeepAlive include 匹配),不修改远程模块导出的组件对象(它可能是跨页面共享的模块实例);attrs/slots 全量透传;
861
- - 加载顺序:`beforeLoad` → 远程可选 `setup`/`onSession`(loadRemote 生命周期)→ 页面模块;失败进 `errorComponent` 错误态(显式错误码/根因/修法),不静默回退。
862
-
863
- #### 10.2 `federation({ setup })` — 远程初始化生命周期
864
-
865
- ```ts
866
- // 远程 vite.config.ts
867
- federation({ name: 'remote-a', exposes: { ... }, setup: './src/fulgurjs/setup.ts' })
868
- ```
869
-
870
- ```ts
871
- // remote-a/src/fulgurjs/setup.ts —— 只有下面两个函数名有自动生命周期语义
872
- import type { RemoteSetupContext, RemoteSetupModule } from '@fulgurjs/federation/runtime'
873
-
874
- export default async function setup(context: RemoteSetupContext) {
875
- // 应用级一次性注册:全局组件/指令、全局样式、locale 注入、宿主 app 上的插件安装
876
- // context.appContext = 调用时的 AppContext 快照;context.signal = 生命周期信号
877
- }
878
- export async function onSession(context: RemoteSetupContext) {
879
- // 会话级同步:当前用户、权限、字典、token 相关缓存
880
- const data = await fetchUserDicts()
881
- if (context.signal.aborted) return // ← await 之后写状态前必须检查:期间可能已换账号/退出
882
- writeToStores(data)
883
- }
884
- ```
885
-
886
- `RemoteSetupContext`:`{ appContext: Readonly<AppContext>, sessionKey?: string, signal: AbortSignal }`。`appContext` 是**调用时**从页面级 AppContext 取得的快照(不保存永不更新的旧引用);`signal` 在登录代次变化或退出清理时失效。
887
-
888
- 固定契约:
889
-
890
- | 维度 | 语义 |
891
- |---|---|
892
- | 触发入口 | `loadRemote('remote/模块')` 是**统一入口**(`remoteComponent` 与宿主页面适配器同源)。`loadRemote('remote')`、`getContainer()`、`preloadRemote()`、直调 `container.get()` 都**不执行**初始化 |
893
- | 时序 | 取得容器 → `init(shareScope)`(共享作用域收养)→ **setup** → **onSession** → 返回业务模块。setup 模块自身的导入在该阶段完成共享协商 |
894
- | 执行次数(setup) | 每容器一次;并发调用共享同一 Promise;成功后不重复。`preloadRemote` 只预取资源不执行 |
895
- | 执行次数(onSession) | 按非敏感 `sessionKey` 去重:同一登录代次一次;新代次先作废旧 `signal`,再按远程**串行**衔接旧调用与新调用(防两账号异步写入交错);`clearAppContext()` 失效去重状态,下次登录必须重跑。应用级 setup 不因退出/换代重复执行 |
896
- | sessionKey | 宿主登录流程每次成功登录/重登生成新代次(非敏感 ID,禁止用 token);token 刷新但会话未变时沿用。**有 onSession 却缺 sessionKey → MFU-013**,不凭用户对象引用猜测身份;无 onSession 的远程无需 sessionKey |
897
- | 导出校验 | 必须默认导出函数;具名 `onSession` 可选且必须是函数;其他导出不作为入口。违反 → MFU-011(报实际类型/预期签名/修法) |
898
- | 失败与重试 | setup/onSession 抛错 → 该次 `loadRemote` 拒绝(MFU-012);**只清失败阶段的缓存**(setup 失败重试从 setup 开始;onSession 失败只重跑会话段),已成功的阶段不重复。`fallbackModule` 不掩盖初始化失败 |
899
- | 自递归 | setup/onSession 同步段内 `loadRemote(同 remote/…)` → MFU-014(该调用会等待自身形成死锁)。异步段内的同远程递归无法精确归因,表现为挂起——不要在初始化内加载同远程模块 |
900
- | dev/prod 一致 | dev 容器(中间件直出)与 prod 容器(构建产物)携带同一 setup 元数据(容器上的 `__fulgurjsSetup` 字段 + manifest 的 `setup` 字段);内部 expose 键 `./__fulgurjs_setup__` 不出现在 dts 类型与公开文档 exposes 清单中 |
901
- | 错误码 | MFU-011 导出非法 / MFU-012 执行失败 / MFU-013 缺 sessionKey / MFU-014 自递归;全部带 remote 名、模块路径/阶段、实际结果、预期与修法,不记录 token |
902
-
903
- 「`exposes` 一个普通 TS 启动模块 + 宿主手动 `loadRemote` 并调用」只是普通 expose + `loadRemote` 的通用用法,不是插件 API,也无 `setup`/`onSession` 的应用级一次、会话级去重、失败重试语义——初始化一律改用 `federation({ setup })`(见[迁移指南](../README.md#文档))。
904
-
905
-
906
-
907
-
908
- ## 开发类型与远程源码
909
-
910
- ### 远程源码不可访问时的开发类型
911
-
912
- 远程设置 `devFsRoot: false`,或远程源码目录在宿主机器上不可访问时,插件根据开发 manifest 为每个公开暴露模块生成 `any` 声明。默认导入、具名导入和副作用导入均可解析,但没有源码补全、类型约束或源码跳转;内部 setup 生命周期入口不生成声明。生成目录遵循 `dts.dir`;默认有 `src` 时为 `src/fulgurjs/types`,否则为 `.fulgurjs/types`。确保项目 tsconfig 包含该目录。恢复源码直连后重启宿主开发服务即可重新生成精确映射;`dts: false` 会完全关闭生成。
913
-
914
- 开发类型生成与运行时使用同一份 `remotes.dev` 地址:绝对 URL、`//host:port/path` 和同源相对路径均支持。相对地址以宿主 Vite 开发服务的 origin 解析;显式 `server.origin` 优先,其次实际本地服务地址。