@fulgurjs/federation 5.7.1 → 5.8.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 +3 -0
- package/README.en.md +314 -367
- package/README.md +264 -1186
- package/dist/index.cjs +72 -11
- package/dist/index.js +72 -11
- package/dist/runtime.js +1 -1
- package/docs/API.en.md +328 -0
- package/docs/API.md +913 -0
- package/docs/webpack-mf-/345/257/271/347/205/247/344/270/216/347/274/272/345/217/243.md +64 -37
- package/examples/bridge/react-host/package-lock.json +4 -4
- package/examples/bridge/react-host/package.json +1 -1
- package/examples/bridge/react-remote/package-lock.json +4 -4
- package/examples/bridge/react-remote/package.json +1 -1
- package/examples/bridge/vue-host/package-lock.json +4 -4
- package/examples/bridge/vue-host/package.json +1 -1
- package/examples/bridge/vue-remote/package-lock.json +4 -4
- package/examples/bridge/vue-remote/package.json +1 -1
- package/examples/react/host/package-lock.json +4 -4
- package/examples/react/host/package.json +1 -1
- package/examples/react/remote/package-lock.json +4 -4
- package/examples/react/remote/package.json +1 -1
- package/examples/vue/host/package-lock.json +4 -4
- package/examples/vue/host/package.json +1 -1
- package/examples/vue/remote/package-lock.json +4 -4
- package/examples/vue/remote/package.json +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,1376 +1,454 @@
|
|
|
1
1
|
# @fulgurjs/federation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
> **fulgurjs** — 拉丁语「闪电 · 辉光」。
|
|
6
|
-
> 一个把 Vite 模块联邦做到开箱即用的插件:**一套配置,dev / prod 双引擎,语义对齐 Webpack Module Federation**,Vue 3 与 React 18/19 双生态浏览器端支持。
|
|
7
|
-
|
|
8
|
-
  
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 为什么是它
|
|
13
|
-
|
|
14
|
-
| | webpack MF | 其他 vite MF 方案 | **@fulgurjs/federation** |
|
|
15
|
-
|---|---|---|---|
|
|
16
|
-
| dev 体验 | 需要独立构建 | 通常要手工 bootstrap | ✅ 双 dev-server 直连,零手工异步边界 |
|
|
17
|
-
| prod 产物 | ✅ | 常缺失或降级 | ✅ 构建期改写,稳定 remoteEntry 文件名 + manifest |
|
|
18
|
-
| 语义完整度 | 100% | 残缺(版本协商/单例/容错经常缺失) | ✅ 逐条对齐 webpack 语义并有 e2e 验收 |
|
|
19
|
-
| **UMD / CJS-only 依赖** | 需自行处理 | **普遍不可用** | ✅ 自动支持(预构建外部化 + 构建期 require 垫片) |
|
|
20
|
-
| 远程加载失败 | 裸错误,需手写重试 | 普遍缺失 | ✅ 重试/熔断/超时内置 + `fallbackModule` 显式降级 |
|
|
21
|
-
| 运行时体积 | ~40KB+ | 不等 | **gzip < 5KB** |
|
|
22
|
-
| 配置出错时 | 难排查 | 报错晦涩 | 三段式报错:`got / expected / example` |
|
|
23
|
-
|
|
24
|
-
**真实工程验证**:某企业级 mes 系统(admin 宿主 + bpm/lowcode 两个子应用,21+6 页)已全量迁移,三个应用各自维护项目根目录的 `fulgurjs.config.ts`、Vite 一处 `federation(fulgurjsConfig)` 接入。历史版本验收曾出现"23/23 页面有字即全过"的口径偏差(4.2.1 复核订正:参数页需用有效业务数据进入、错误页不得算通过);最新一轮以 26 条页面记录 + 27 个菜单入口的逐项业务断言为准,结论见对应版本验收报告与 `docs/` 下证据文件(见[迁移指南](#文档))。
|
|
25
|
-
|
|
26
|
-
## 特性
|
|
27
|
-
|
|
28
|
-
- **exposes / remotes / shared 全语义**:`name@url` 语法、键重命名、promise-based remote、semver 全语法 requiredVersion、版本协商(最高版本胜出)、singleton / strictVersion、已加载版本永不替换、多版本共存、shareKey 重定向、多 shareScope
|
|
29
|
-
- **UMD / CJS-only 依赖开箱即用**:element-plus、avue 等只有 UMD/CJS 产物的依赖直接进 `optimizeDeps.include` 即可——dev 期插件自动把预构建产物内的 shared 键改道协商门面;build 期自动把 CJS `require(<shared>)` 重定向到垫片,双运行时免疫
|
|
30
|
-
- **自动异步边界**:top-level await 自动注入(es2022+),无需 webpack 式手工 `import('./bootstrap')`
|
|
31
|
-
- **稳定产物**:remoteEntry 固定文件名便于稳定引用(入口内容每次构建变,**必须 no-cache**——只有带内容哈希的 chunk 才可长缓存);`fulgurjs-manifest.json` 资源清单;expose 独立 chunk
|
|
32
|
-
- **容错(对齐 webpack MF 2.0 errorLoadRemote)**:加载重试 / 熔断 / 超时内置;`loadRemote(spec, { retries, fallbackModule })` 单次调用级覆盖——失败时返回 fallback 模块,错误事件仍显式发出(**绝不静默兜底**,不传则照旧抛错)。组件级默认错误占位提供用户恢复操作:**重试加载**(同页重试,失败后换 URL 穿透浏览器失败缓存)与**刷新页面重试**(用户点击才整页刷新,覆盖浏览器失败缓存无法同页穿透的静态子依赖场景)
|
|
33
|
-
- **增强能力**:dts 类型直连(dev 补全直达 remote 源码)、`preloadRemote()` manifest 驱动精确预载、runtimePlugins 钩子
|
|
34
|
-
- **HMR 全链路**:remote 改动 → host 页面热更,L1 组件热替换 / L2 状态保留 / L3 错误覆盖与恢复
|
|
35
|
-
- **零报错纪律**:配置问题启动瞬间三段式报错;联邦失败显式抛错(错误码 + 可执行修复建议),**无任何静默兜底路径**
|
|
36
|
-
- **CLI(主包内置 bin)**:`fulgurjs init`——**单项目** `fulgurjs.config.ts` 起步模板与校验(默认导出直接是 `federation()` 选项;输出 `federation(fulgurjsConfig)` 接入块与核对清单,**不改写任何项目文件**);`fulgurjs explain`——本应用有效联邦形态与加载链解释器(纯本地,按实际选项判角色);`fulgurjs check-pages`——宿主页面表 ↔ 远程 manifest exposes 契约核对(`--manifest`/`--site` 指定来源并如实报告;CI 可嵌,确定性错误与 `--require-verified` 均非零退出);`fulgurjs doctor`——部署面体检(remoteEntry/manifest/HTML 缓存头与形态、CORS、chunk 抽样可达、版本 skew 预演、`--dev` 端口探测)
|
|
37
|
-
- **远程初始化生命周期(可选)**:`federation({ setup })` 显式声明初始化入口——默认导出 `setup(context)` 应用级执行一次、可选具名导出 `onSession(context)` 按宿主 `sessionKey` 去重执行(换账号/重登自动重跑,退出 `clearAppContext` 清理会话状态);失败显式报错可重试(`MFU-011~014`),`preloadRemote`/`getContainer` 无副作用。不配置 `setup` 时零行为零体积
|
|
38
|
-
- **宿主页面适配器(可选)**:`createHostPages({ pages, remotePrefixes, ... })`——一份页面表供宿主路由与布局共用;URL 解析(含 base 剥离)、最长前缀远程归属、`definePages` R1–R5 校验、异步组件缓存(会话切换自动重建)、骨架屏/错误占位、保活名称内置
|
|
39
|
-
- **跨应用传值与方法引用**:`@fulgurjs/federation/runtime` 导出 `provideAppContext` / `getAppContext` / `requireAppContext` / `clearAppContext`(缺键 `CC-001` 三段式、独立直开远程页 `CC-002` 显式)。宿主桥写入页面级单例(user/getToken/store/hostApp/locale/sessionKey/events 标准字段 + 项目扩展位),远程 setup/onSession 显式校验消费;方法引用两条通道 = context 携带函数引用(热路径直调)+ exposes 方法模块 `loadRemote('remote/api')`(低频重逻辑)。数据语义 = 传输层快照 + 函数引用,非响应式(与乾坤 props 同语义;"实时"靠函数引用拉取 / 宿主 pinia 共享承担,同页换账号由 onSession 会话同步承担,不依赖页面刷新)
|
|
40
|
-
- **Vue 直渲染**:`remoteComponent('remote/X')`(`@fulgurjs/federation/runtime` 导出)——`defineAsyncComponent + loadRemote` 的标准封装,加载失败显式错误占位(错误码+根因+修法+**重试加载/刷新页面重试**),runtime.js 零框架依赖零体积增量
|
|
41
|
-
- **React 完整支持(浏览器端)**:`@fulgurjs/federation/react` 独立入口——`remoteComponent`(含 Suspense 占位/错误占位/**重试加载+刷新页面重试**,不用 React.lazy 的失败缓存陷阱)、`useLoadRemote`(代次守卫的模块 hook)、`RemoteErrorBoundary`(页面级兜底)、`createReactHostPages`(与 Vue 同源页面表与 R1–R5 校验);共享 `react`/`react-dom` singleton 协商,Hooks/StrictMode/Context 跨端同实例(dev 预构建外部化 + prod CJS 垫片自动处理 `react/jsx-runtime`、`react-dom/client` 子路径);纯 React 项目零 Vue 依赖、纯 Vue 项目零 React 依赖
|
|
42
|
-
- **跨框架桥接(子应用级,5.3.0 起)**:Vue 3 宿主嵌入 React 18/19 子应用、React 宿主嵌入 Vue 子应用——子应用以 `defineBridgeApp` 导出 `mount/unmount` 契约,宿主用 `createVueBridgeApp` / `createReactBridgeApp`(`/bridge` 入口,推荐 `/bridge/vue`、`/bridge/react` 分离入口)像普通组件一样挂载;受控 `sessionKey` 会话代次、`appProps` 快照 + 函数引用、首次根提交语义、加载/挂载失败占位与恢复、同页多实例与 StrictMode 安全内置;双框架 shared singleton 配方强制(纯项目零对方依赖不受影响)
|
|
43
|
-
- **CSP 友好**:原生 ESM 加载路径全程无 `eval` / `new Function`,可在严格 CSP(无 `unsafe-eval`)下运行
|
|
44
|
-
- **全链路错误码体系(44 码)**:CFG/DEV/BLD/MFU/CC 五段 + 手册 §6 码表防漂移校验
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md)
|
|
45
4
|
|
|
46
|
-
|
|
5
|
+
**让一个 Vite 应用使用另一个应用提供的组件、页面或函数。**
|
|
47
6
|
|
|
48
|
-
|
|
49
|
-
pnpm add -D @fulgurjs/federation
|
|
50
|
-
```
|
|
7
|
+
例如:主系统加载独立部署的审批页面,Vue 页面中嵌入一个 React 子应用,或者多个应用共用同一套工具函数。提供方和使用方可以放在不同仓库,各自构建和部署。
|
|
51
8
|
|
|
52
|
-
|
|
9
|
+
本文是使用指南,示例按 **5.7.1** 编写。完整参数、默认值和执行规则在 [API 手册](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md)。
|
|
53
10
|
|
|
54
|
-
|
|
11
|
+
## 先看你要做什么
|
|
55
12
|
|
|
56
|
-
|
|
13
|
+
| 你的需求 | 使用方法 | 示例 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Vue 加载另一个应用的 Vue 组件 | `remoteComponent` | [Vue 示例](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/vue) |
|
|
16
|
+
| React 加载另一个应用的 React 组件 | `/react` 的 `remoteComponent` | [React 示例](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/react) |
|
|
17
|
+
| 加载远程 JS/TS 函数 | `loadRemote`;React 也可用 `useLoadRemote` | 见下方快速开始 |
|
|
18
|
+
| 一批宿主路由对应远程页面 | Vue 用 `createHostPages`;React 用 `createReactHostPages` | [页面接入示例](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/pages-cli) |
|
|
19
|
+
| Vue 嵌 React,或 React 嵌 Vue | 子应用桥接:`defineBridgeApp` + 宿主桥接组件 | [双向嵌套示例](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/bridge) |
|
|
20
|
+
| 刷新后仍打开子应用的详情页 | 在桥接上开启 URL 同步 | [路由同步示例](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/bridge-router) |
|
|
21
|
+
| 远程页面需要用户、token 或初始化 | `AppContext` + 可选 `setup`/`onSession` | 见下方业务初始化 |
|
|
22
|
+
| 同页使用 React 18 和 React 19 | 将两组依赖和使用方放入不同 `shareScope` | [版本隔离示例](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/react-versions) |
|
|
57
23
|
|
|
58
|
-
|
|
24
|
+
这些功能按需组合。**加载一个普通组件,不需要先配置桥接、页面表或登录初始化。**
|
|
59
25
|
|
|
60
|
-
|
|
26
|
+
## 几个名字是什么意思
|
|
61
27
|
|
|
62
|
-
|
|
63
|
-
|
|
28
|
+
| 名字 | 用普通话解释 |
|
|
29
|
+
|---|---|
|
|
30
|
+
| 宿主(host) | 显示远程内容的应用,例如主系统 |
|
|
31
|
+
| 远程(remote) | 提供组件、页面或函数的应用,例如审批系统 |
|
|
32
|
+
| `exposes` | 远程允许其他应用加载哪些文件 |
|
|
33
|
+
| `remotes` | 宿主要连接哪些远程、它们的地址是什么 |
|
|
34
|
+
| `shared` | 哪些依赖参与共享,例如 Vue、React |
|
|
35
|
+
| `singleton` | 同一个共享分组内只采用一个依赖实例;不保证所有库都能跨大版本兼容 |
|
|
36
|
+
| `shareScope` | 共享依赖的分组。不同组可以使用不同版本 |
|
|
37
|
+
| 桥接 | 为子应用准备一个 DOM 容器,让它在里面自行渲染和卸载 |
|
|
38
|
+
| URL 同步 | 把子应用的内部路径写进宿主地址,支持刷新、分享和前进后退 |
|
|
39
|
+
|
|
40
|
+
同一个应用可以既提供模块又使用其他应用的模块,不需要固定为单一角色。
|
|
64
41
|
|
|
65
|
-
|
|
66
|
-
name: 'remote-react',
|
|
67
|
-
exposes: {
|
|
68
|
-
'./Button': './src/Button.tsx',
|
|
69
|
-
'./utils': './src/utils.ts',
|
|
70
|
-
'./pages/home': './src/pages/Home.tsx',
|
|
71
|
-
},
|
|
72
|
-
shared: {
|
|
73
|
-
react: { singleton: true },
|
|
74
|
-
'react-dom': { singleton: true },
|
|
75
|
-
},
|
|
76
|
-
} satisfies FederationOptions
|
|
77
|
-
```
|
|
42
|
+
## 安装
|
|
78
43
|
|
|
79
|
-
|
|
80
|
-
// vite.config.ts —— React 插件照常在前,联邦只加一行
|
|
81
|
-
import { defineConfig } from 'vite'
|
|
82
|
-
import react from '@vitejs/plugin-react'
|
|
83
|
-
import federation from '@fulgurjs/federation'
|
|
84
|
-
import fulgurjsConfig from './fulgurjs.config'
|
|
44
|
+
在每个参与联邦的 Vite 项目中安装:
|
|
85
45
|
|
|
86
|
-
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add -D @fulgurjs/federation
|
|
48
|
+
# 使用 npm 的项目:npm install -D @fulgurjs/federation
|
|
87
49
|
```
|
|
88
50
|
|
|
89
|
-
|
|
51
|
+
- 支持浏览器端 Vue 3、React 18/19,以及普通 JS/TS 模块。
|
|
52
|
+
- 支持 Vite 5.1 及以上的 5/6/7/8 系列;项目必须同时满足所用 Vite 和框架插件的版本要求。
|
|
53
|
+
- 本插件要求 Node.js ≥18,但 **Vite 7/8 要求 Node.js 20.19+ 或 22.12+**,不能只按插件的最低版本选 Node。
|
|
54
|
+
- 构建目标使用 `es2022` 或更新;浏览器基线为 Chrome 108+,其他浏览器需要相应的 ESM、动态导入和顶层 await 支持。
|
|
55
|
+
- 普通 Vue 项目安装 Vue 即可;普通 React 项目安装 React 和 react-dom 即可。双向跨框架桥接的宿主按下面说明安装两个框架。
|
|
90
56
|
|
|
91
|
-
|
|
92
|
-
import { remoteComponent, useLoadRemote, createReactHostPages, remoteSchema } from '@fulgurjs/federation/react'
|
|
93
|
-
import { pages, remotePrefixes } from './src/federation/pages.data' // 纯数据模块(CLI 与浏览器共用)
|
|
57
|
+
## 快速开始:两个 Vue 应用
|
|
94
58
|
|
|
95
|
-
|
|
96
|
-
const RemoteButton = remoteComponent<{ label: string; onClick?: () => void }>('remote-react/Button', {
|
|
97
|
-
fallback: <p>正在加载远程按钮…</p>,
|
|
98
|
-
})
|
|
59
|
+
下面是在**已有 Vite + Vue 项目**中增加联邦功能。每个项目仍保留自己的 `index.html`、入口文件和原有插件。
|
|
99
60
|
|
|
100
|
-
|
|
101
|
-
type Utils = { formatMoney(v: number, currency?: string): string }
|
|
61
|
+
我们使用两个项目:
|
|
102
62
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
63
|
+
```text
|
|
64
|
+
remote-vue/ 提供按钮和加法函数,开发端口 5174
|
|
65
|
+
host-vue/ 加载它们,开发端口 5173
|
|
106
66
|
```
|
|
107
67
|
|
|
108
|
-
|
|
68
|
+
### 1. 远程声明要提供的文件
|
|
109
69
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**推荐接入形态(4.2.0 起)——每项目一份 `fulgurjs.config.ts`,Vite 只注册一次插件**:
|
|
70
|
+
`remote-vue/fulgurjs.config.ts`:
|
|
113
71
|
|
|
114
72
|
```ts
|
|
115
|
-
// my-app/fulgurjs.config.ts —— 默认导出直接可传给 federation()(satisfies 做编译期形状检查)
|
|
116
73
|
import type { FederationOptions } from '@fulgurjs/federation'
|
|
117
74
|
|
|
118
75
|
export default {
|
|
119
|
-
name: '
|
|
120
|
-
exposes: { './pages/home': './src/views/Home.vue' },
|
|
121
|
-
remotes: { 'remote-a': { dev: 'http://localhost:5174/remote-a', prod: '/remote-a' } },
|
|
122
|
-
shared: { vue: { singleton: true } },
|
|
123
|
-
} satisfies FederationOptions
|
|
124
|
-
|
|
125
|
-
// my-app/vite.config.ts —— 联邦相关的全部代码就这两行(其余 Vite 配置原样保留)
|
|
126
|
-
import federation from '@fulgurjs/federation'
|
|
127
|
-
import fulgurjsConfig from './fulgurjs.config'
|
|
128
|
-
// plugins: [ ...原有插件, federation(fulgurjsConfig) ]
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
`npx fulgurjs init` 生成该模板;`npx fulgurjs explain` / `check-pages` 直接读它(宿主应用另以
|
|
132
|
-
**具名导出 `hostPages`** 提供 CLI 核对用的页面数据,与运行时页面表同一份数据模块)。宿主与远程
|
|
133
|
-
即使分属互不相邻的仓库也各自独立:只声明对方 URL 与容器名,不依赖共同父目录或对方源码。
|
|
134
|
-
下面三条路径按需选路,**不必全做**,互相独立、可组合(普通小应用也可以不建配置文件、
|
|
135
|
-
直接在 vite.config.ts 里写 `federation({ name, ... })`):
|
|
136
|
-
|
|
137
|
-
- **路径 ①:暴露并加载普通模块**——任何 Vue 组件或 TS/JS 函数模块,跨应用共享。不需要桥、不需要页面表、不需要任何初始化协议。
|
|
138
|
-
- **路径 ②:宿主多页面接入**——宿主有一批路由要映射到远程页面。用 `createHostPages` 一份页面表解决 URL 解析/组件缓存/骨架屏/错误占位/保活名称。
|
|
139
|
-
- **路径 ③:远程业务页需要宿主环境**——远程页面依赖全局组件注册、用户/权限/字典等启动期初始化。用 `setup`/`onSession` 声明式初始化 + `AppContext` 传值。
|
|
140
|
-
|
|
141
|
-
### 跨框架桥接(第 4 条路径:Vue 宿主嵌 React 子应用 / React 宿主嵌 Vue 子应用)
|
|
142
|
-
|
|
143
|
-
需要**整站级**跨框架嵌入(子应用自带路由与状态、整站挂载/卸载)时,用 `/bridge` 入口——完整 API 见 [§8.2](#82-跨框架桥接-api--bridge),最小示例见 `examples/bridge/`(Vue 宿主×React 远程、React 宿主×Vue 远程双向各一对)。组件级混渲染(Vue 模板里直接渲染 React 组件)**不支持**,那是框架桥接库的产品。
|
|
144
|
-
|
|
145
|
-
### 路径 ①:暴露并加载普通模块(无 setup、无桥、无页面表)
|
|
146
|
-
|
|
147
|
-
**谁配置**:远程应用 vite.config.ts 写 `exposes`;宿主 vite.config.ts 写 `remotes`。**谁调用**:消费方业务代码。**何时执行**:`loadRemote` 只取得模块导出,**调用导出函数仍由业务代码决定**——expose ≠ 自动执行。
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
// ── remote-a/vite.config.ts ──
|
|
151
|
-
federation({
|
|
152
|
-
name: 'remote-a',
|
|
76
|
+
name: 'remote-vue',
|
|
153
77
|
exposes: {
|
|
154
|
-
'./Button': './src/
|
|
155
|
-
'./math': './src/math.ts',
|
|
78
|
+
'./Button': './src/Button.vue',
|
|
79
|
+
'./math': './src/math.ts',
|
|
156
80
|
},
|
|
157
|
-
shared: { vue: { singleton: true } },
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ── remote-a/src/math.ts:普通 TS 文件,无任何联邦 API ──
|
|
161
|
-
export function add(a: number, b: number) { return a + b }
|
|
162
|
-
|
|
163
|
-
// ── host(消费方)代码 ──
|
|
164
|
-
import { loadRemote, remoteComponent } from '@fulgurjs/federation/runtime'
|
|
165
|
-
|
|
166
|
-
// 组件:remoteComponent 直渲染(defineAsyncComponent 标准封装,失败显式错误占位)
|
|
167
|
-
const RemoteButton = remoteComponent('remote-a/Button')
|
|
168
|
-
|
|
169
|
-
// 普通 TS 模块:loadRemote 返回【模块命名空间】——这一步只是加载,add 尚未执行
|
|
170
|
-
const mod = await loadRemote('remote-a/math')
|
|
171
|
-
mod.add(1, 2) // ← 显式调用才执行
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
最小文件树(只有路径 ① 时):
|
|
175
|
-
|
|
176
|
-
```text
|
|
177
|
-
remote-a/
|
|
178
|
-
vite.config.ts # federation({ name, exposes })
|
|
179
|
-
src/components/Button.vue
|
|
180
|
-
src/math.ts
|
|
181
|
-
host/
|
|
182
|
-
vite.config.ts # federation({ name, remotes: { 'remote-a': ... } })
|
|
183
|
-
src/App.vue # remoteComponent(...) / loadRemote(...)
|
|
81
|
+
shared: { vue: { singleton: true, strictVersion: true } },
|
|
82
|
+
} satisfies FederationOptions
|
|
184
83
|
```
|
|
185
84
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
### 路径 ②:宿主多页面接入(`createHostPages`)
|
|
189
|
-
|
|
190
|
-
宿主有一批路由要落到远程页面时,用页面适配器替代手写路由/加载样板:**一份页面表**供宿主路由与布局共用,URL 解析、最长前缀远程归属、异步组件缓存、骨架屏、错误占位、保活名称全部内置。
|
|
85
|
+
`remote-vue/src/Button.vue`:
|
|
191
86
|
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
import {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{ route: '/remote-a/home', name: 'RemoteAHome', title: '首页' },
|
|
199
|
-
// 带参路由须显式 spec(剥参推导会与列表页 expose 键收敛相同,R1 校验拦截)
|
|
200
|
-
{ route: '/remote-a/detail/:id', name: 'RemoteADetail', spec: 'pages/remote-a/detail', title: '详情' },
|
|
201
|
-
],
|
|
202
|
-
remotePrefixes: { '/remote-a/': 'remote-a' }, // 最长前缀匹配
|
|
203
|
-
schema: remoteSchema, // dev 探针校验 exposes 存在性
|
|
204
|
-
// base: '/main', // 站点有 base 前缀时声明,resolve 自动剥离
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
hostPages.pages // 原页面记录(供 vue-router 注册)
|
|
208
|
-
hostPages.resolve(path) // { page, remote, spec, params } | null
|
|
209
|
-
hostPages.component(spec) // 异步页面组件(同 spec 复用)
|
|
210
|
-
hostPages.keepAliveNames // keepAlive 页面的组件 name(KeepAlive include 用)
|
|
211
|
-
```
|
|
87
|
+
```vue
|
|
88
|
+
<script setup lang="ts">
|
|
89
|
+
import { ref } from 'vue'
|
|
90
|
+
defineProps<{ label: string }>()
|
|
91
|
+
const count = ref(0)
|
|
92
|
+
</script>
|
|
212
93
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
path: p.route, name: p.name,
|
|
217
|
-
props: (r) => ({ ...r.params, ...r.query }),
|
|
218
|
-
component: hostPages.component(hostPages.resolve(p.route)!.spec),
|
|
219
|
-
}))
|
|
220
|
-
// 布局内容区:<component :is="hostPages.component(resolved.spec)" :key="fullPath" v-bind="props" />
|
|
94
|
+
<template>
|
|
95
|
+
<button @click="count++">{{ label }}:{{ count }}</button>
|
|
96
|
+
</template>
|
|
221
97
|
```
|
|
222
98
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
远程页面依赖「全局组件注册、用户/权限/字典」等启动期初始化时,在远程的 `federation()` 配置里**显式声明**一个初始化入口文件——插件保证它的执行时序,宿主不再手写「loadRemote 启动器并调用」:
|
|
99
|
+
`remote-vue/src/math.ts`:
|
|
226
100
|
|
|
227
101
|
```ts
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
name: 'remote-a',
|
|
231
|
-
exposes: { './pages/remote-a/home': './src/views/Home.vue' },
|
|
232
|
-
setup: './src/fulgurjs/setup.ts', // ← 可选项;缺省时无任何初始化行为
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
// ── remote-a/src/fulgurjs/setup.ts ──
|
|
236
|
-
import type { RemoteSetupContext } from '@fulgurjs/federation/runtime'
|
|
237
|
-
|
|
238
|
-
// 默认导出:应用级初始化,同一容器只执行一次(全局组件/样式/locale 注册)
|
|
239
|
-
export default async function setup(context: RemoteSetupContext) {
|
|
240
|
-
const { hostApp } = context.appContext
|
|
241
|
-
// hostApp 全局组件注册、全局样式 import 等
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// 可选具名导出:会话级初始化,按宿主 sessionKey 去重(换账号/重登自动重跑)
|
|
245
|
-
export async function onSession(context: RemoteSetupContext) {
|
|
246
|
-
// 同步当前用户、权限、字典;await 之后写状态前检查 context.signal.aborted
|
|
102
|
+
export function add(a: number, b: number): number {
|
|
103
|
+
return a + b
|
|
247
104
|
}
|
|
248
105
|
```
|
|
249
106
|
|
|
250
|
-
|
|
251
|
-
// ── host:先提供 context(含 sessionKey),之后正常 loadRemote —— 初始化自动发生 ──
|
|
252
|
-
import { provideAppContext, loadRemote, clearAppContext } from '@fulgurjs/federation/runtime'
|
|
253
|
-
|
|
254
|
-
// 登录成功后(每次成功登录/重登生成新的非敏感 sessionKey,不是 token):
|
|
255
|
-
provideAppContext({ user, getToken, store, hostApp, locale, sessionKey: 's-101-1730...', events })
|
|
256
|
-
|
|
257
|
-
const Page = await loadRemote('remote-a/pages/remote-a/home')
|
|
258
|
-
// ↑ 内部时序:取得容器 → init(共享协商)→ 执行 remote-a 的 setup(一次)→
|
|
259
|
-
// 执行 onSession(本 sessionKey 首次)→ 返回页面模块。任一步失败显式报错(MFU-011~014)。
|
|
260
|
-
|
|
261
|
-
// 退出登录时:
|
|
262
|
-
clearAppContext() // 清 context + 作废会话信号/onSession 去重(下次登录必须重跑 onSession)
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
**执行时机总表(路径 ③)**:
|
|
266
|
-
|
|
267
|
-
| 动作 | setup(默认导出) | onSession(具名导出) |
|
|
268
|
-
|---|---|---|
|
|
269
|
-
| 谁声明 | 远程 `federation({ setup })` | 同一 setup 文件的具名导出(可选) |
|
|
270
|
-
| 谁触发 | 宿主首次 `loadRemote('remote-a/任何模块')` | 同左,且宿主须已提供 `sessionKey` |
|
|
271
|
-
| 执行次数 | 每容器一次(并发共享同一 Promise) | 每个 sessionKey 一次;换账号/重登重跑;退出清理后重跑 |
|
|
272
|
-
| 失败行为 | 该次 loadRemote 拒绝(MFU-012),仅清失败缓存可重试 | 同左(缺 sessionKey 报 MFU-013) |
|
|
273
|
-
| 预载 | `preloadRemote` 只下载资源,**不执行** | 同左 |
|
|
274
|
-
| `getContainer` / `loadRemote('remote-a')` | 不执行(只取容器) | 不执行 |
|
|
275
|
-
|
|
276
|
-
**数据从哪里来**:`context.appContext` 就是宿主 `provideAppContext` 写入的同一页面级对象(同浏览器页面直接共享,无网络传输);`user` 是提供时快照、`getToken()` 每次调用取最新值、`store` 是宿主 pinia 实例引用。dev/prod 行为一致。
|
|
277
|
-
|
|
278
|
-
> 只有配置在 `setup` 的文件才是生命周期入口。插件**不扫描目录、不按文件名猜测、不执行其他 TS exposes**——普通 TS 模块仍按路径 ① 的语义「加载不等于调用」。「expose 一个普通 TS 模块 + 宿主手动 `loadRemote` 并调用」本质是普通 expose 的通用语义,始终可以做;但它不是插件的生命周期机制,也没有应用级一次 / 会话级去重 / 失败重试——初始化请用 `setup`/`onSession`(见[迁移指南](#文档))。
|
|
279
|
-
|
|
280
|
-
### 通用边界(三条路径都适用)
|
|
281
|
-
|
|
282
|
-
**唯一 API 入口**:应用代码的一切联邦导入——运行时函数、context 函数(含 `clearAppContext`)、`definePages`、`remoteSchema`、`remoteComponent`、`createHostPages`——**只来自物理子路径**:
|
|
283
|
-
|
|
284
|
-
```ts
|
|
285
|
-
import { loadRemote, provideAppContext, getAppContext, requireAppContext, clearAppContext, definePages, createHostPages, remoteSchema, remoteComponent } from '@fulgurjs/federation/runtime'
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
`/runtime` 是 ESM 应用入口,导出 `remoteComponent`/`createHostPages`,所以**使用该入口的应用需要安装 Vue**(Vue 为可选 peer:只用包根(Vite 插件)时无需安装;`runtime.js` 内核本身零 Vue 依赖零体积增量)。`remoteSchema` 必须以具名静态导入取得 dev 探针结果;命名空间导入、动态导入和 re-export 不触发探针拆写,未经过插件转换时该值是空表。dev 下 remote 跑它自己的 `vite dev`(容器入口 `/@fulgurjs-entry.js` 由插件中间件直出);build 下 expose 自动拆独立 chunk、shared 自动剥离——同一份配置两端通用。
|
|
289
|
-
|
|
290
|
-
**导入改写边界**:宿主/远程的任何普通源码文件都可以直接静态导入 `/runtime`——包括 exposes 目标文件(远程页面),其导入会被插件自动改写为惰性单例委托(求值期零副作用)。**构建入口文件是边界**:Vite 的 HTML module entry 在 build 时由插件优先内联 init 并直接返回,入口文件自身的 remote import 不会进入改写管线;把 remote 动态导入放在入口导入的普通模块中,不要写在 `main.ts` / `main.js` 里。
|
|
291
|
-
|
|
292
|
-
**CSS 预载**:`loadRemote('remote-a/Page')` 在 remote 提供 manifest 时,先按 expose 预载对应 CSS 再解析返回模块;CSS 请求失败报 `MFU-007` 但不阻断 JS 模块加载。expose 依赖的全局 CSS 需从该 expose 的依赖图中导入(路径 ③ 的 setup 文件导入全局样式是该场景的标准位置),确保资源进入 manifest。
|
|
293
|
-
|
|
294
|
-
> 以上是最常用面。**全部选项、运行时 API、CLI、错误码见下方 [API 参考](#api-参考)。**
|
|
295
|
-
|
|
296
|
-
## CLI:init 起步模板 / explain 配置解释 / check-pages 页面契约 / doctor 部署体检
|
|
297
|
-
|
|
298
|
-
```bash
|
|
299
|
-
# 1) 生成单项目 fulgurjs.config.ts 起步模板(默认导出直接是 federation() 选项;已存在则拒绝,--force 覆盖)
|
|
300
|
-
npx fulgurjs init
|
|
301
|
-
# 样例:examples/vue/remote/fulgurjs.config.ts 与 examples/vue/host/fulgurjs.config.ts(完整可复制工程的联邦声明)
|
|
302
|
-
|
|
303
|
-
# 2) 校验配置并输出接入块:federation(fulgurjsConfig) 两行接法 + 通用核对清单(纯打印,不写文件)
|
|
304
|
-
npx fulgurjs init --config fulgurjs.config.ts
|
|
305
|
-
|
|
306
|
-
# 3) 配置解释器(纯本地无网络):角色(按实际选项判定,双向联邦显示「双角色」)/remotes/exposes/
|
|
307
|
-
# setup/shared/页面映射/devSharedSelf 来源/加载链
|
|
308
|
-
npx fulgurjs explain # --json 供 CI(在应用根目录运行,默认读 ./fulgurjs.config.ts)
|
|
309
|
-
|
|
310
|
-
# 4) 页面契约核对:宿主页面表 ↔ 远程 manifest exposes(宿主项目内运行;manifest 来源
|
|
311
|
-
# 优先级 --manifest > --site/prod 推导,输出实际命中来源;确定性错误非零退出,
|
|
312
|
-
# 远程不可达报「无法验证」而非通过,--require-verified 时无法验证也非零)
|
|
313
|
-
npx fulgurjs check-pages --site http://your-site
|
|
314
|
-
npx fulgurjs check-pages --manifest remote-a=/abs/fulgurjs-manifest.json --require-verified
|
|
315
|
-
|
|
316
|
-
# 5) 部署体检(CI 可嵌):缓存头/资源形态/CORS/chunk 可达/版本 skew
|
|
317
|
-
npx fulgurjs doctor --base http://your-site --apps app-a,app-b
|
|
318
|
-
npx fulgurjs doctor --base http://localhost:5173 --apps app-a --dev
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
**插件保持项目无关**:init 不改写任何项目文件、不生成项目源码(不生成桥/路由/启动器/NGINX 文件);
|
|
322
|
-
权限路由、项目侧桥与页面表等集成细节由各项目按 init 输出的通用核对清单自行落地。
|
|
323
|
-
|
|
324
|
-
### 每项目一份配置:`fulgurjs.config.ts` + `federation(fulgurjsConfig)`(默认主路径)
|
|
107
|
+
### 2. 宿主声明远程地址
|
|
325
108
|
|
|
326
|
-
`fulgurjs.config.ts
|
|
327
|
-
编译期形状检查,无运行时包装函数),Vite 只导入本项目常量并注册一次插件:
|
|
109
|
+
`host-vue/fulgurjs.config.ts`:
|
|
328
110
|
|
|
329
111
|
```ts
|
|
330
|
-
// my-app/fulgurjs.config.ts —— 本项目自己的配置;键直接属于 federation 选项
|
|
331
112
|
import type { FederationOptions } from '@fulgurjs/federation'
|
|
332
113
|
|
|
333
114
|
export default {
|
|
334
|
-
name: '
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
115
|
+
name: 'host-vue',
|
|
116
|
+
remotes: {
|
|
117
|
+
'remote-vue': {
|
|
118
|
+
dev: 'http://localhost:5174',
|
|
119
|
+
prod: '/remote-vue',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
shared: { vue: { singleton: true, strictVersion: true } },
|
|
340
123
|
} satisfies FederationOptions
|
|
341
|
-
|
|
342
|
-
// my-app/vite.config.ts —— 联邦相关行(原有 Vite 配置原样保留)
|
|
343
|
-
import federation from '@fulgurjs/federation'
|
|
344
|
-
import fulgurjsConfig from './fulgurjs.config'
|
|
345
|
-
// plugins: [ ...原有插件, federation(fulgurjsConfig) ]
|
|
346
124
|
```
|
|
347
125
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
- 各项目 `vite.config.ts` 中**不得也不需要**出现 `fileURLToPath(new URL(...))` / 父目录配置路径 /
|
|
351
|
-
按字符串查应用名(4.1.0 聚合链 `loadRepoConfig`/`federationOptionsForApp` 已在 5.0.0 删除)——CLI 内部有自己的加载器,
|
|
352
|
-
项目侧永远只见「导入一个常量、调用一次插件」;
|
|
353
|
-
- 宿主应用的页面核对数据以**具名导出 `hostPages`**(`{ pages, remotePrefixes, deriveSpec? }`)提供,
|
|
354
|
-
与运行时 `createHostPages` 消费同一份数据模块(页面表唯一手工维护位置);Vite 只消费默认导出,
|
|
355
|
-
`pages` 等非插件字段不会误传给 `federation()`;
|
|
356
|
-
- base / dev 端口 / 代理 / 插件顺序等继续归各项目 `vite.config.ts`,不复制进第二套配置;
|
|
357
|
-
- 同一 monorepo 中的应用也各自持有配置;宿主与远程分属不同仓库时各自独立构建、部署、诊断。
|
|
126
|
+
`dev` 是开发地址,`prod` 是部署后的地址。这里的 `/remote-vue` 表示宿主所在域名下的远程目录,**不是磁盘文件夹路径**。
|
|
358
127
|
|
|
359
|
-
|
|
360
|
-
> 子路径的 `defineRepoConfig` / `loadRepoConfig` / `federationOptionsForApp` 三层转换)与 CLI
|
|
361
|
-
> `--app` 选择器已删除——传入旧形状会得到「当前形状 → 期望形状 → 迁移写法」的中文错误
|
|
362
|
-
> (`explain`/`check-pages` 传 `--app` 也报同类错误)。迁移 = 拆出各应用的 `name/remotes/
|
|
363
|
-
> exposes/setup/shared` 到各自项目根的 `fulgurjs.config.ts`,`host.pages`/`remotePrefixes`/
|
|
364
|
-
> `deriveSpec` 改为具名导出 `hostPages`,然后删除父目录聚合文件。
|
|
128
|
+
### 3. 两个项目都注册插件
|
|
365
129
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
以下覆盖插件的全部公开 API,签名与默认值与源码一致——**本 README 为唯一权威文档**。
|
|
369
|
-
|
|
370
|
-
### 1. `federation(options)` — Vite 插件(宿主/远程同一份 API)
|
|
130
|
+
两个项目的 `vite.config.ts` 都导入本项目的配置:
|
|
371
131
|
|
|
372
132
|
```ts
|
|
373
|
-
import {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
#### 全部选项
|
|
379
|
-
|
|
380
|
-
| 选项 | 类型 | 默认 | 说明 |
|
|
381
|
-
|---|---|---|---|
|
|
382
|
-
| `name` | `string` **必填** | — | 容器名。同页面宿主/远程必须唯一(也是 uniqueName);须匹配 `/^[a-zA-Z][\w.-]*$/` |
|
|
383
|
-
| `filename` | `string` | `'fulgurjs-remoteEntry.js'` | prod 容器入口文件名(固定文件名便于引用与部署规则落位;入口内容每次构建变,**必须 no-cache**,长缓存只给带内容哈希的 chunk) |
|
|
384
|
-
| `exposes` | `Record<string, string \| { import: string; name?: string }>` | — | 对外暴露模块:键 `'./X'`,值源文件路径;`name` 为稳定 chunk 文件名。键不得占用内部保留键 `./__fulgurjs_setup__`(CFG-012) |
|
|
385
|
-
| `setup` | `string` | —(无初始化行为) | **可选远程初始化入口**:相对应用根的 TS/JS 模块路径。默认导出 `setup(context)` 应用级执行一次(容器首次被加载业务模块前);可选具名导出 `onSession(context)` 按宿主 `sessionKey` 去重执行。其余导出不作为生命周期入口。执行时序/去重/失败重试/错误码见 §10 |
|
|
386
|
-
| `remotes` | `Record<string, string \| RemoteEntryConfig \| (() => Promise<any>)>` | — | 消费的远程,三种形态见下表 |
|
|
387
|
-
| `shared` | `string[] \| Record<string, string \| SharedHint>` | — | 共享依赖;字符串简写 = requiredVersion(缺省从本应用 package.json 推断) |
|
|
388
|
-
| `shareScope` | `string` | `'default'` | 默认共享作用域名 |
|
|
389
|
-
| `runtime` | `string \| false` | 内置运行时 | 自定义运行时模块路径;`false` 禁用内置运行时 |
|
|
390
|
-
| `runtimeChunk` | `boolean \| 'single'` | — | 运行时是否拆独立 chunk |
|
|
391
|
-
| `manifest` | `boolean` | `true` | prod 构建生成 `fulgurjs-manifest.json`(preloadRemote 依赖它) |
|
|
392
|
-
| `runtimePlugins` | `string[]` | `[]` | 运行时插件模块路径列表(写法见「运行时插件」) |
|
|
393
|
-
| `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 的安全边界——只对可信来源开启 |
|
|
394
|
-
| `devSharedSelf` | `boolean` | 提供 `exposes`(或 `setup`)的应用 `true`;纯宿主(只消费)`false`;显式配置永远优先 | dev 下自身源码(含依赖)是否参与 shared 协商改写。双向联邦(既 expose 又消费 remote)默认即 `true`——无需再背诵显式配置(4.1.0 起按角色推断,§12.4;此前默认 false 曾是已知错误配置的来源)。build 下该路径的协商门面自动隔离进插件专属 chunk(`fulgurjs-runtime` + `fulgurjs-shared-<key>`),与用户 `manualChunks` 强制分组正交、不产生 chunk 循环依赖(D6 修复) |
|
|
395
|
-
| `devCorsOrigins` | `string[] \| '*'` | `'*'`(现状兼容) | dev 跨源访问策略:插件端点(`/@fulgurjs-entry.js`、`/@fulgurjs-manifest.json`)与 `server.cors` 使用同一来源。缺省或 `'*'` 全放开(非 loopback host 时提醒 DEV-011);数组按 Origin 反射 allowlist(未命中省略头)。用户显式配置的 `server.cors` 永远优先。开/关/自定义三态示例见下方 |
|
|
396
|
-
| `devFsRoot` | `boolean` | `true`(现状兼容) | dev manifest 是否携带 `fsRoot`(remote 根目录本机绝对路径,宿主 dts 类型直连用)。`false` 不写入(本机路径不外发),宿主 dts 降级 any 桩并提示;该字段永不进入 prod manifest。非 loopback host 下默认值会提醒 DEV-012 |
|
|
397
|
-
|
|
398
|
-
#### remotes 的三种形态
|
|
399
|
-
|
|
400
|
-
```ts
|
|
401
|
-
remotes: {
|
|
402
|
-
// ① 字符串单地址:dev 自动拼 /@fulgurjs-entry.js,prod 自动拼 filename
|
|
403
|
-
'remote-a': 'http://localhost:5101',
|
|
404
|
-
// ② '自报名@url':重命名语义(仅字符串形式支持;对象形式不支持 name@,配置期即报 CFG-007)
|
|
405
|
-
'checkout': 'shop@http://localhost:5102',
|
|
406
|
-
// ③ 对象:dev/prod 显式拆分 + 容错参数(全部可选)
|
|
407
|
-
'remote-b': {
|
|
408
|
-
dev: 'http://localhost:5103/remote-b',
|
|
409
|
-
prod: '/remote-b',
|
|
410
|
-
shareScope: 'default',
|
|
411
|
-
timeout: 15000, // 加载超时 ms(有限正数,配置期校验 CFG-009)
|
|
412
|
-
retries: 2, // 失败重试次数
|
|
413
|
-
fallback: ['http://backup/remote-b'], // 备用 remoteEntry,依次尝试
|
|
414
|
-
breaker: { threshold: 5, resetMs: 30000 }, // 连续失败熔断
|
|
415
|
-
},
|
|
416
|
-
// ④ 函数:promise-based remote(构建时地址未知;等价 webpack "promise new Promise",
|
|
417
|
-
// 需在运行时配合 registerRemote 注册,见下文运行时 API)
|
|
418
|
-
'remote-c': () => fetch('/api/remote-url').then(r => r.text()),
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
#### devCorsOrigins / devFsRoot 三态示例
|
|
133
|
+
import { defineConfig } from 'vite'
|
|
134
|
+
import vue from '@vitejs/plugin-vue'
|
|
135
|
+
import federation from '@fulgurjs/federation'
|
|
136
|
+
import fulgurjsConfig from './fulgurjs.config'
|
|
423
137
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// ② 显式全开:同 ①,但不再提醒(声明"我知情")
|
|
429
|
-
federation({ name: 'remote-a', exposes: { './Button': './src/Button.vue' }, devCorsOrigins: '*' })
|
|
430
|
-
|
|
431
|
-
// ③ 自定义 allowlist:仅列出的宿主来源可跨源访问联邦端点与源码模块
|
|
432
|
-
federation({
|
|
433
|
-
name: 'remote-a',
|
|
434
|
-
exposes: { './Button': './src/Button.vue' },
|
|
435
|
-
devCorsOrigins: ['http://localhost:5100', 'https://team.example.com'],
|
|
436
|
-
devFsRoot: false, // 同时不把本机绝对路径写进 dev manifest(宿主 dts 降级 any 桩并提示)
|
|
138
|
+
export default defineConfig({
|
|
139
|
+
plugins: [vue(), federation(fulgurjsConfig)],
|
|
140
|
+
build: { target: 'es2022' },
|
|
437
141
|
})
|
|
438
142
|
```
|
|
439
143
|
|
|
440
|
-
|
|
144
|
+
保留项目已有的别名、代理等配置。远程与宿主应安装兼容的 Vue 版本;示例开启 `strictVersion`,不兼容时会报错,而不是继续使用错误版本。
|
|
441
145
|
|
|
442
|
-
|
|
146
|
+
### 4. 在宿主页中使用
|
|
443
147
|
|
|
444
|
-
|
|
445
|
-
shared: {
|
|
446
|
-
vue: {
|
|
447
|
-
singleton: true, // 全页单实例(vue/pinia/vue-router 强烈建议 true)
|
|
448
|
-
requiredVersion: '^3.4.0', // semver 全语法;false = 接受任意;缺省从 package.json 推断
|
|
449
|
-
strictVersion: false, // 缺省:有本地副本且非 singleton → true(不满足即抛 MFU-003)
|
|
450
|
-
shareKey: 'vue', // 共享作用域里的键(导入名与共享名不同时用)
|
|
451
|
-
shareScope: 'default', // 该项的共享作用域
|
|
452
|
-
eager: false, // true = 本地副本打进初始 chunk(同步可用)
|
|
453
|
-
import: 'vue', // 本地副本模块;false = 纯消费不提供(与 eager 互斥,CFG-008)
|
|
454
|
-
version: '3.4.21', // 显式提供版本(缺省读本机安装版本)
|
|
455
|
-
},
|
|
456
|
-
// 字符串简写:等价 { requiredVersion: '^4.4.5' }
|
|
457
|
-
'vue-router': '^4.4.5',
|
|
458
|
-
// 数组形式:shared: ['vue', 'pinia']
|
|
459
|
-
}
|
|
460
|
-
```
|
|
148
|
+
`host-vue/src/App.vue`:
|
|
461
149
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
**任何文件都直接静态导入**——宿主页面、exposes 目标文件(远程页面)都一样,插件自动保证同一页面只有一个运行时实例(远程页面里的导入会被自动改写为惰性单例委托):
|
|
467
|
-
|
|
468
|
-
```ts
|
|
469
|
-
// 宿主页面、远程页面,写法完全一致
|
|
470
|
-
import { loadRemote } from '@fulgurjs/federation/runtime'
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
> 仍可绕过代理直取全局单例(等价,调试用):`(globalThis as any).__FULGURJS_RUNTIME__`。
|
|
474
|
-
|
|
475
|
-
#### 函数总表
|
|
476
|
-
|
|
477
|
-
> 下表全部函数与 `definePages` / `remoteSchema` / `provideAppContext` 等 context 函数 / `remoteComponent` / `createHostPages` 都从 Vue 入口 `@fulgurjs/federation/runtime` 导入(见 §2);旧入口已删除。**React 浏览器应用请使用 `@fulgurjs/federation/react`**(通用函数同名提供 + §8.1 的 React 适配 API;不含本表的 Vue 专属项 `remoteComponent` Vue 形态 / `createHostPages` / `keepAliveNames`)。
|
|
478
|
-
|
|
479
|
-
> **TS 提示**:`@fulgurjs/federation/runtime` 的类型随包发布,由包的 `exports` 和 `typesVersions` 直接解析;不需要 `client` 类型垫片。dev 启动时插件仅在类型目录(默认 `src/fulgurjs/types/`)生成远程 exposes 的类型声明。
|
|
480
|
-
|
|
481
|
-
| 函数 | 签名 | 说明 |
|
|
482
|
-
|---|---|---|
|
|
483
|
-
| `loadRemote` | `(spec: string, opts?) => Promise<模块命名空间>` | 加载远程模块。`spec = '远程名/./Expose键'`(`./` 可省)。远程配置了 `setup` 时,该函数是初始化生命周期的**统一触发入口**(容器 init 后、返回模块前执行 setup/onSession,见 §10);`loadRemote('remote')` 只取容器不执行初始化。opts 见下 |
|
|
484
|
-
| `loadShare` | `(name: string, opts?) => Promise<命名空间>` | 共享模块协商(最高版本胜出/已加载优先/singleton 收敛)。opts:`{ requiredVersion?, singleton?, strictVersion?, shareKey?, shareScope?, fallback? }` |
|
|
485
|
-
| `preloadRemote` | `(spec: string, opts?: { mode?: 'preload' \| 'prefetch' }) => Promise<void>` | `remote/Expose` 只预载该 expose 的 chunk + CSS;仅传 remote 名则预载全部 exposes。`preload` 等待 CSS load/error,`prefetch` 低优先级并立即返回 |
|
|
486
|
-
| `getContainer` | `(name: string) => Promise<容器>` | 取远程容器(触发加载 + init),容器协议 `{ name, init, get }`;**不执行 setup/onSession**。直接访问 `container.get()` 同样不保证执行初始化——需要生命周期的加载一律走 `loadRemote` |
|
|
487
|
-
| `registerRemote` / `registerRemotes` | `(config \| list) => void` | 运行时注册远程(promise remote / 动态地址)。`RemoteInput`:`{ name, entry, shareScope?, timeout?, retries?, fallback?, breaker? }`。参数校验:`timeout` 有限正数、`retries` 0..10 整数、`breaker.threshold/resetMs` 有限正数——非法值**注册当场抛错**(配置文件路径在配置期即报 CFG-009);重复注册时 entry/timeout/retries/breaker 参数按最新配置刷新,熔断计数状态保留。`timeout` 语义:超时只代表"调用方不再等待",浏览器不会取消已发出的动态 import——后续调用复用同一 in-flight 记录,不会重复初始化同一容器 |
|
|
488
|
-
| `registerShare` | `(scope, name, version, get, opts?) => void` | 手工注册共享模块(一般由 init 模块自动完成) |
|
|
489
|
-
| `initSharing` | `(scopeName?) => ShareScopeMap` | 初始化共享作用域(一般由 init 模块自动完成) |
|
|
490
|
-
| `registerPlugins` | `(plugins: RuntimePlugin[]) => void` | 注册运行时插件(见下) |
|
|
491
|
-
| `getRuntime` | `() => FgRuntime` | 取运行时单例本体(与 `__FULGURJS_RUNTIME__` 同一实例) |
|
|
492
|
-
| `version` | `string` | 运行时/插件版本(跨源副本一致性诊断用) |
|
|
493
|
-
| `unwrapDefault` | `(ns: any) => any` | ESM/CJS default interop 工具 |
|
|
494
|
-
|
|
495
|
-
#### loadRemote 选项
|
|
496
|
-
|
|
497
|
-
```ts
|
|
498
|
-
const Panel = await loadRemote('shop/Panel', {
|
|
499
|
-
shareScope: 'default', // 覆盖远程声明的 shareScope
|
|
500
|
-
retries: 3, // 单次调用覆盖 remote.retries
|
|
501
|
-
fallbackModule: () => import('./PanelFallback.vue'),
|
|
502
|
-
// 失败时返回 fallback 模块;错误事件/console 仍显式发出(不是静默兜底);不传则抛错
|
|
503
|
-
})
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
#### 运行时插件
|
|
507
|
-
|
|
508
|
-
(`runtimePlugins: ['./src/fulgurjsPlugin.ts']`)
|
|
150
|
+
```vue
|
|
151
|
+
<script setup lang="ts">
|
|
152
|
+
import { ref } from 'vue'
|
|
153
|
+
import { loadRemote, remoteComponent } from '@fulgurjs/federation/runtime'
|
|
509
154
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
> **resolveShare 与消费路径(5.7.1 起)**:配置了 `runtimePlugins` 的 HTML 入口在执行应用前完成共享裁决与加载;远程容器也会在执行 expose 前完成异步裁决。同步门面复用同一消费条件的决策与实例,异步 hook 可以选择低版本或原表之外的条目,不会被本地副本覆盖。应用与 provider 之间保留动态导入边界,Vite 8 的消费方门面仍无 TLA,避免把协商等待传入消费方循环依赖。
|
|
513
|
-
>
|
|
514
|
-
> **入口边界**:没有 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 版本隔离与恢复](demo/react-versions/README.md)。
|
|
155
|
+
const RemoteButton = remoteComponent('remote-vue/Button')
|
|
156
|
+
const result = ref('尚未计算')
|
|
515
157
|
|
|
516
|
-
|
|
517
|
-
|
|
158
|
+
async function calculate() {
|
|
159
|
+
try {
|
|
160
|
+
const math = await loadRemote<{ add(a: number, b: number): number }>('remote-vue/math')
|
|
161
|
+
result.value = String(math.add(1, 2))
|
|
162
|
+
} catch (error) {
|
|
163
|
+
result.value = error instanceof Error ? error.message : String(error)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
</script>
|
|
518
167
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
hooks.beforeLoadRemote = ({ remote, module }) => {}
|
|
526
|
-
hooks.afterLoadRemote = ({ remote, module, module_ns }) => {}
|
|
527
|
-
hooks.onRemoteError = ({ remote, error }) => {} // error.code ∈ 错误码总表
|
|
528
|
-
},
|
|
529
|
-
} satisfies RuntimePlugin
|
|
168
|
+
<template>
|
|
169
|
+
<RemoteButton label="远程按钮" />
|
|
170
|
+
<button @click="calculate">调用远程加法函数</button>
|
|
171
|
+
<p>{{ result }}</p>
|
|
172
|
+
</template>
|
|
530
173
|
```
|
|
531
174
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
| 出口 | 内容 |
|
|
535
|
-
|---|---|
|
|
536
|
-
| `window.__FULGURJS_SCOPE__` | share scope 实时协商结果(键 → 版本 → `{ get, from, loaded }`) |
|
|
537
|
-
| `window.__FULGURJS_INFO__` | `{ remotes: { [名]: { entry, status, lastLoadMs, error, setup } }, errors: [] }`——`setup` ∈ none/pending/ready/failed |
|
|
538
|
-
| `window.__FULGURJS_APP_CONFIG__` | W4 全局配置镜像 |
|
|
539
|
-
| `window` 事件 `fulgurjs:error` | `CustomEvent<{ remote, error }>`,所有远程加载/共享错误都会发出 |
|
|
540
|
-
|
|
541
|
-
### 3. `definePages` — 宿主页面路由表(`@fulgurjs/federation/runtime`)
|
|
175
|
+
这里的名字一一对应:
|
|
542
176
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
import { remoteSchema } from '@fulgurjs/federation/runtime' // dev 自动生成;build 恒为空(诚实降级)
|
|
548
|
-
|
|
549
|
-
export const PAGES = definePages(
|
|
550
|
-
[
|
|
551
|
-
{ route: '/remote-a/home', name: 'RemoteAHome', title: '首页' },
|
|
552
|
-
// 带参路由:缺省推导 spec = 去首段 + 剥 :参 段;与其它条目冲突时 ERROR,
|
|
553
|
-
// 指向独立 expose 用 spec 显式覆盖
|
|
554
|
-
{ route: '/remote-a/detail/:id', name: 'RemoteADetail', spec: 'pages/remote-a/detail', title: '详情' },
|
|
555
|
-
],
|
|
556
|
-
{
|
|
557
|
-
deriveSpec: (route) => 'pages/' + route.replace(/^\//, '').split('/').filter(s => !s.startsWith(':')).join('/'),
|
|
558
|
-
remotes: { '/remote-a/': 'remote-a' }, // 路由前缀 → 远程名
|
|
559
|
-
schema: remoteSchema, // { [remoteName]: { exposes: string[], exists?: boolean } }
|
|
560
|
-
strict: true, // ERROR 默认 throw;false 降级 console.error
|
|
561
|
-
},
|
|
562
|
-
)
|
|
177
|
+
```text
|
|
178
|
+
remote-vue/Button
|
|
179
|
+
└─ remotes 中的键 remote-vue
|
|
180
|
+
└─ 远程 exposes 中的键 ./Button(调用时省略 ./)
|
|
563
181
|
```
|
|
564
182
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
| 规则 | 级别 | 内容 |
|
|
568
|
-
|---|---|---|
|
|
569
|
-
| R1 | ERROR | 带参路由(无显式 spec)的推导 spec 与其它条目收敛相同——会静默加载错误组件 |
|
|
570
|
-
| R2 | WARN | 多条目有效 spec 完全相同(刻意的菜单别名可忽略) |
|
|
571
|
-
| R3 | ERROR | spec 不在该 remote 的 exposes 清单中(dev 有 schema 时校验;远程不可达诚实跳过) |
|
|
572
|
-
| R4 | ERROR | 静态路由被更靠前的带参路由遮蔽(先到先得)/路由完全重复 |
|
|
573
|
-
| R5 | WARN | name 重复(vue-router 命名跳转歧义) |
|
|
574
|
-
|
|
575
|
-
`validatePages(pages, options)` 为独立导出:返回违例清单不抛错,便于自测。
|
|
576
|
-
|
|
577
|
-
同子路径的类型:`PageRouteLike`(路由条目形状)、`PagesOptions`(校验选项,含 `deriveSpec` / `remotes` / `schema` / `strict`)、`PageViolation`(`validatePages` 的返回条目,含 `level` 与说明)、`RemoteSchemaEntry`(`schema` 里每个远程的条目形状)。
|
|
183
|
+
`loadRemote` 返回文件导出的内容。加载 `math.ts` 后,仍要调用 `math.add()` 才会执行加法。
|
|
578
184
|
|
|
579
|
-
###
|
|
185
|
+
### 5. 启动并检查结果
|
|
580
186
|
|
|
581
|
-
|
|
582
|
-
// my-app/fulgurjs.config.ts —— 默认导出直接可传给 federation();无 root/apps[]/角色壳
|
|
583
|
-
import type { FederationOptions } from '@fulgurjs/federation'
|
|
187
|
+
在两个终端分别运行:
|
|
584
188
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
remotes: { 'remote-a': { dev: 'http://localhost:5174/remote-a', prod: '/remote-a' } },
|
|
589
|
-
setup: './src/fulgurjs/setup.ts', // 可选:远程初始化入口(§10)
|
|
590
|
-
shared: { vue: { singleton: true } },
|
|
591
|
-
devSharedSelf: true, // 可选:显式覆盖;缺省按角色推断(提供 exposes/setup → true)
|
|
592
|
-
} satisfies FederationOptions
|
|
189
|
+
```bash
|
|
190
|
+
# 终端一,remote-vue 项目内
|
|
191
|
+
npm run dev -- --port 5174 --strictPort
|
|
593
192
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
// import { pages, remotePrefixes, deriveSpec } from './src/fulgurjs/host/pages.data'
|
|
597
|
-
// export const hostPages = { pages, remotePrefixes, deriveSpec }
|
|
193
|
+
# 终端二,host-vue 项目内
|
|
194
|
+
npm run dev -- --port 5173 --strictPort
|
|
598
195
|
```
|
|
599
196
|
|
|
600
|
-
|
|
601
|
-
相对导入的纯 TS/JS 数据模块(extensionless 可)、Node ≥ 18、CJS/ESM 双形态;缺失文件、无
|
|
602
|
-
`name`、字段形状不对、expose/setup 指向项目外或不存在文件等均三段式报错。运行时(Vite)与
|
|
603
|
-
CLI 解析同一份配置值;dev/prod 的 URL 选择规则与 `federation({ remotes })` 一致(§1)。
|
|
604
|
-
|
|
605
|
-
**已删除(5.0.0)**:4.1.0 聚合配置入口 `@fulgurjs/federation/config`(`defineRepoConfig` /
|
|
606
|
-
`loadRepoConfig` / `federationOptionsForApp` 及 `RepoConfig` 等聚合类型)不再发布——导入该子路径
|
|
607
|
-
会得到 exports 解析错误;CLI 读到旧形状(`root + apps[]`)会输出「拆分到各项目根」的中文迁移
|
|
608
|
-
指引。`PageEntry` 仍是现行类型(宿主页面表记录,随单项目契约从主入口类型面使用)。
|
|
609
|
-
|
|
197
|
+
打开 `http://localhost:5173`,应看到能增加计数的远程按钮;点击计算按钮应显示 `3`。使用 pnpm 的项目也可用 `pnpm dev` 启动。
|
|
610
198
|
|
|
611
|
-
|
|
199
|
+
完整工程与生产部署配置见 [Vue examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/vue)。
|
|
612
200
|
|
|
613
|
-
|
|
614
|
-
|---|---|
|
|
615
|
-
| `fulgurjs init` | 在当前目录生成**单项目** `fulgurjs.config.ts` 起步模板(默认导出 = `federation()` 选项 + 可选 `hostPages` 具名导出示例);`--template <path>` 指定输出路径;已存在拒绝覆盖,`--force` 强制。init **只生成配置起步模板**,不生成桥/路由/启动器/NGINX 文件 |
|
|
616
|
-
| `fulgurjs init --config <path>` | 校验配置(CFG 三段式报错)+ 输出 `federation(fulgurjsConfig)` 接入块与通用核对清单(纯打印)。旧聚合形状报中文迁移错误 |
|
|
617
|
-
| `fulgurjs explain [--config <path>] [--json]` | 配置解释器(纯本地、无网络、不读 token/环境秘密):应用角色(**按实际 federation 选项判定**——配 `remotes` 即消费、配 `exposes`/`setup` 即提供,两者均有=双角色,如双向联邦的 BPM)、有效 remotes、公开 exposes、内部 setup、shared、页面 spec 映射与数据来源、`devSharedSelf` 最终值及来源、加载链。`--json` 供 CI。传 `--app`(5.0.0 已删除的聚合选择器)报中文迁移错误 |
|
|
618
|
-
| `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 |
|
|
619
|
-
| `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 门禁 |
|
|
620
|
-
|
|
621
|
-
### 6. 错误码总表(48 个)
|
|
201
|
+
## React 怎么接入
|
|
622
202
|
|
|
623
|
-
|
|
624
|
-
|---|---|---|
|
|
625
|
-
| CFG 配置期 | `CFG-001` | name 缺失或非法 |
|
|
626
|
-
| | `CFG-002` | exposes 配置形状错误 |
|
|
627
|
-
| | `CFG-003` | remotes 配置形状错误 / 键含非法字符 |
|
|
628
|
-
| | `CFG-004` | shared 配置形状错误 |
|
|
629
|
-
| | `CFG-005` | remotes 键与 shared 键同名冲突 |
|
|
630
|
-
| | `CFG-006` | 孤岛配置(既不提供也不消费) |
|
|
631
|
-
| | `CFG-007` | remotes 对象形式误用 name@ 前缀(整串当 URL 拼接) |
|
|
632
|
-
| | `CFG-008` | shared 非法组合(eager+import:false / shareKey 重复声明) |
|
|
633
|
-
| | `CFG-009` | remotes 运行参数非法(timeout/retries/breaker 非有限正数/超上限) |
|
|
634
|
-
| | `CFG-010` | devCorsOrigins 形态非法(须为 "*" 或 http(s) 来源数组) |
|
|
635
|
-
| | `CFG-011` | 已删除的 webpack 兼容/无效选项(remoteType/library/automaticAsyncBoundary/dataPrefetch/usedExports/ignoreUnusedSharedExports——传入任何值报错并给出迁移写法) |
|
|
636
|
-
| | `CFG-012` | setup 配置非法(路径为空/非字符串,或 exposes 占用内部保留键 `./__fulgurjs_setup__`) |
|
|
637
|
-
| DEV 开发期 | `DEV-001` | remote dev server 不可达(manifest 拉取失败) |
|
|
638
|
-
| | `DEV-002` | remote dev manifest 为空或格式不识别 |
|
|
639
|
-
| | `DEV-004` | 已知 UMD-only 依赖不在 optimizeDeps.include(预构建内联本地 vue 风险) |
|
|
640
|
-
| | `DEV-005` | remotes dev URL 端口无监听 |
|
|
641
|
-
| | `DEV-006` | 宿主/远程插件版本不一致 |
|
|
642
|
-
| | `DEV-009` | 门面/虚拟模块 404(.vite 缓存漂移,需清缓存重启) |
|
|
643
|
-
| | `DEV-010` | dev 冷启动预构建窗口提示(首轮 30~60s 瞬态,非故障) |
|
|
644
|
-
| | `DEV-011` | 非 loopback host + 通配 dev CORS(暴露面扩大提醒) |
|
|
645
|
-
| | `DEV-012` | 非 loopback host + dev manifest 携带 fsRoot(本机路径外发提醒) |
|
|
646
|
-
| BLD 构建期 | `BLD-001` | expose 源文件解析失败 |
|
|
647
|
-
| | `BLD-002` | 构建目标低于 es2022(TLA 需要) |
|
|
648
|
-
| | `BLD-003` | expose 目标组件含必填 props(文档化核对项) |
|
|
649
|
-
| | `BLD-006` | output 数组形态下无法自动注入协商门面 chunk 隔离(需手工加分支) |
|
|
650
|
-
| MFU 运行时 | `MFU-001` | 远程容器/模块加载失败(网络/超时/重试耗尽/熔断) |
|
|
651
|
-
| | `MFU-002` | remoteEntry 自报名与配置名不一致 |
|
|
652
|
-
| | `MFU-003` | strictVersion 版本不满足 |
|
|
653
|
-
| | `MFU-004` | 共享模块缺失且无本地 fallback |
|
|
654
|
-
| | `MFU-005` | 同一容器用不同 share scope 重复 init |
|
|
655
|
-
| | `MFU-006` | 请求的模块未被该远程 exposes |
|
|
656
|
-
| | `MFU-007` | 预加载失败(不阻断业务) |
|
|
657
|
-
| | `MFU-008` | 未知远程 |
|
|
658
|
-
| | `MFU-009` | 加载到的模块没有任何导出 |
|
|
659
|
-
| | `MFU-010` | 选中的共享单例版本不满足消费方要求;显示版本、提供方、影响和修法,同一组合只告警一次 |
|
|
660
|
-
| | `MFU-011` | setup 生命周期入口导出形态非法(默认导出/具名 onSession 不是函数;报实际类型/预期签名/修法) |
|
|
661
|
-
| | `MFU-012` | setup/onSession 执行抛错(该次 loadRemote 拒绝;仅清失败阶段缓存,可直接重试,已成功的阶段不重复) |
|
|
662
|
-
| | `MFU-013` | 远程声明 onSession 但宿主 AppContext 缺 sessionKey(登录代次;禁止用 token 充当) |
|
|
663
|
-
| | `MFU-014` | setup/onSession 同步段内递归 loadRemote 同一远程(自等待死锁防线) |
|
|
664
|
-
| | `MFU-015` | 桥接契约非法(`./bridge` 默认导出缺 mount/unmount 或非函数;修法指向 defineBridgeApp) |
|
|
665
|
-
| | `MFU-016` | 桥接准备或生命周期失败(`details.phase` 区分 getContext/mount/unmount;根因含子应用原始错误) |
|
|
666
|
-
| | `MFU-017` | 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾、非法值(空串/数字)、页面级单会话冲突) |
|
|
667
|
-
| | `MFU-030` | 桥接路由同步配置/前缀冲突(basePath 非法:空/根/带 query·hash·通配、同页重叠前缀登记) |
|
|
668
|
-
| | `MFU-031` | 桥接路由协议缺失/通道失效(子应用未以 `{ routing: true }` 声明协议、通道销毁后复用) |
|
|
669
|
-
| | `MFU-032` | 桥接非法导航(子应用导航目标越界自身前缀、`go` 参数非法、失效通道的请求被拒绝) |
|
|
670
|
-
| | `MFU-033` | 桥接路由准备/同步失败(重定向超限或导航异常,附目标链/cause;不静默回退 memory) |
|
|
671
|
-
| CC 跨应用上下文 | `CC-001` | AppContext 必需字段缺失(三段式:got/expected/example,修法指向宿主桥 `provideAppContext`) |
|
|
672
|
-
| | `CC-002` | 运行时单例不可用(独立直开远程页;修法 = 经宿主联邦加载,时序契约 bridge → 远程 setup → 页面模块) |
|
|
673
|
-
|
|
674
|
-
错误排查三段式文案见「配置出错?报错看得懂」一节(下文);`fulgurjs doctor` 可提前把部署面的 MFU-001 类问题拦在上线前。
|
|
675
|
-
|
|
676
|
-
### 7. 产物与端点约定
|
|
677
|
-
|
|
678
|
-
| 环境 | 路径 | 说明 |
|
|
679
|
-
|---|---|---|
|
|
680
|
-
| dev | `/<base>/@fulgurjs-entry.js` | 远程容器入口(插件中间件直出,自包含) |
|
|
681
|
-
| dev | `/<base>/@fulgurjs-manifest.json` | dev manifest(宿主 dts / preloadRemote 消费) |
|
|
682
|
-
| prod | `/<base>/fulgurjs-remoteEntry.js` | 固定文件名容器入口(内容每次构建变——**必须 no-cache**) |
|
|
683
|
-
| prod | `/<base>/fulgurjs-manifest.json` | expose chunk/CSS 清单(preloadRemote 消费,**no-cache**) |
|
|
203
|
+
配置方式与 Vue 相同,只需换成 React 插件、共享依赖和浏览器导入入口。
|
|
684
204
|
|
|
685
|
-
|
|
205
|
+
在两个已有 React + Vite 项目中:
|
|
686
206
|
|
|
687
|
-
|
|
207
|
+
1. `vite.config.ts` 使用 `@vitejs/plugin-react`,后面注册 `federation(fulgurjsConfig)`。
|
|
208
|
+
2. 远程把 `./Button` 指向 `./src/Button.tsx`;宿主配置对应 `remotes` 地址。
|
|
209
|
+
3. 两边用兼容的 React/renderer 版本,并共享 `react`、`react-dom`:
|
|
688
210
|
|
|
689
211
|
```ts
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
loadingComponent: MyLoading, // 可选:加载期组件
|
|
695
|
-
errorComponent: MyError, // 可选:失败期组件(收到 error prop)
|
|
696
|
-
retries: 2, // 可选:透传 loadRemote 单次调用级重试覆盖
|
|
697
|
-
})
|
|
212
|
+
shared: {
|
|
213
|
+
react: { singleton: true, strictVersion: true },
|
|
214
|
+
'react-dom': { singleton: true, strictVersion: true },
|
|
215
|
+
}
|
|
698
216
|
```
|
|
699
217
|
|
|
700
|
-
|
|
701
|
-
|---|---|---|---|
|
|
702
|
-
| `loadingComponent` | `Component` | — | 加载期间展示 |
|
|
703
|
-
| `errorComponent` | `Component` | 内置错误占位 | 加载失败展示(Vue 会传入 `error` prop)。自定义时完全接管展示,插件不再注入恢复按钮;默认占位自带「重试加载 / 刷新页面重试」 |
|
|
704
|
-
| `retries` | `number` | 远程注册值(默认 2) | 透传 `loadRemote` |
|
|
705
|
-
| `delay` | `number` | `200` | 切到 loadingComponent 前的等待(ms) |
|
|
706
|
-
| `timeout` | `number` | — | 超时进错误态(ms);不设由 runtime 容器超时兜底 |
|
|
707
|
-
|
|
708
|
-
语义与边界:
|
|
709
|
-
|
|
710
|
-
- 内部 = `defineAsyncComponent({ loader: () => loadRemote(spec, opts).then(m => m.default ?? m) })`,返回标准 Vue 异步组件,`props`(如 `form-params`)在使用处直接透传;
|
|
711
|
-
- **无任何兜底/降级**(H3 零兜底):加载失败显式进错误态;不传 `errorComponent` 时渲染内置占位(错误码 + 根因 + 修法 + **重试加载 / 刷新页面重试**),`window` 的 `fulgurjs:error` 事件由 runtime 层照常发出;
|
|
712
|
-
- 模块去重沿用 `loadRemote` 内部 Promise 缓存——同 spec 多组件实例只加载一次容器模块;
|
|
713
|
-
- `vue` 为**可选 peerDependency**(`peerDependenciesMeta.optional`):只使用包根(Vite 插件)时无需安装;应用使用 `/runtime` 时需要安装 Vue,因为该入口导出 `remoteComponent`。内部 `runtime.js` 仍不导入 Vue,体积零增量;
|
|
714
|
-
- 运行时实例经 `globalThis.__FULGURJS_RUNTIME__` 页面级单例复用,与 `@fulgurjs/federation/runtime` 的导入殊途同归,无需额外接线。
|
|
715
|
-
|
|
716
|
-
### 8.1 React 适配 API — `@fulgurjs/federation/react`
|
|
218
|
+
远程的 `src/Button.tsx`:
|
|
717
219
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
#### `remoteComponent<Props>(spec, options?)`
|
|
721
|
-
|
|
722
|
-
返回可渲染的 React 组件类型(`Props` 约束 JSX 使用;类型参数是编译期合同,不是运行时校验)。工厂与页面表声明**零加载副作用**;首次渲染才 `loadRemote`(经容器协商与可选 setup/onSession),内部自带 pending 占位、错误占位与错误边界——最简用法无需手写 Suspense/`React.lazy`。**不用 `React.lazy`**:lazy 实例缓存失败的 Promise,仅重置错误边界无法恢复;本实现的 retry 会重建加载尝试(已成功的模块经运行时缓存不会重复下载)。
|
|
723
|
-
|
|
724
|
-
| 选项 | 类型与默认 | 语义 |
|
|
725
|
-
|---|---|---|
|
|
726
|
-
| `fallback` | `ReactNode`,默认 `null` | 本次加载 pending 时的占位(区别于失败占位) |
|
|
727
|
-
| `error` | `ReactNode` 或 `(error, retry) => ReactNode`,默认内置中文占位 | 加载失败或子树渲染错误的展示;渲染函数收到真实错误与可用的重试 |
|
|
728
|
-
| `retries` | `number`,沿用 `loadRemote` 默认(2) | 透传重试次数(0–10 整数,非法值工厂调用期抛错) |
|
|
729
|
-
| `timeout` | `number`(ms),默认不设适配层超时 | 本次组件加载等待上限;超时只结束本次等待,**不取消**已发出的共享请求;迟到的成功/失败不覆盖终态、不产生未处理 rejection |
|
|
730
|
-
|
|
731
|
-
- 组件导出校验:默认导出(或模块本身)必须是函数组件 / class / `memo` / `forwardRef` 等合法组件类型;字符串、数字、空命名空间显式报错(不渲染空白成功页)
|
|
732
|
-
- `ref` 透传:`forwardRef` 导出可正确接收 ref(React 18/19 实测);普通函数组件传 ref 遵循 React 标准行为
|
|
733
|
-
- 渲染期异常由内置边界捕获并与网络/导出错误**分开记录与展示**(文案区分「加载失败」与「渲染出错」);ErrorBoundary 不捕获事件处理器与任意异步回调异常——这两类错误遵循 React 自身语义
|
|
734
|
-
- 内置默认错误占位包含:错误码(FgError 的 `code`,无码渲染错误显示 `UNKNOWN`)、真实根因 message、可执行修法,以及两个恢复操作——**「重试加载」**(同页重建加载链)与**「刷新页面重试」**(仅用户点击才整页刷新,保留当前地址;用于浏览器已缓存模块失败的场景,见下条边界)。渲染阶段错误只提供「重试加载」(错误抛自远程代码本身,刷新无法修复)
|
|
735
|
-
- 失败恢复真实穿透浏览器 ESM 失败缓存:运行时对入口 URL 与容器 expose loader 均在失败后的重试上变更 URL(`fulgurjs_retry=N`),服务恢复后点击重试可真实重新拉取(不是只在 mock 下可恢复)。并发加载同一模块失败后重试只推进一个代次(不会因并发失败产生多个重试 URL 导致模块实例分裂);已成功模块的重复访问零重复网络请求
|
|
736
|
-
- **已知边界**:expose 的**静态依赖** chunk(expose chunk 内 `import` 的普通 chunk)失败后,同页重试不可恢复——浏览器 module map 缓存了该依赖 URL 的失败,重试换 URL 的 expose chunk 重新拉取后其静态 import 仍命中缓存失败。恢复需整页刷新——默认占位的**「刷新页面重试」**就是这条路径的用户操作(用户点击触发,保留当前地址,永不自动刷新);动态 import 形态的共享依赖不受此限。插件不做全站依赖图递归改写来穿透该限制
|
|
737
|
-
|
|
738
|
-
#### `useLoadRemote<Module>(spec, options?)`
|
|
220
|
+
```tsx
|
|
221
|
+
import { useState } from 'react'
|
|
739
222
|
|
|
740
|
-
|
|
741
|
-
const
|
|
223
|
+
export default function Button({ label }: { label: string }) {
|
|
224
|
+
const [count, setCount] = useState(0)
|
|
225
|
+
return <button onClick={() => setCount(count + 1)}>{label}:{count}</button>
|
|
226
|
+
}
|
|
742
227
|
```
|
|
743
228
|
|
|
744
|
-
|
|
745
|
-
- `options`:`shareScope` / `retries` / `fallbackModule`(透传 `loadRemote`;配置 `fallbackModule` 是显式声明的行为——失败返回兜底值而非写 error)
|
|
746
|
-
- 按字段比较依赖(调用方每次 render 新建 options 对象不会无限重载);spec/选项变化时清理旧数据进入新请求
|
|
747
|
-
- 每轮 effect 与 `reload` 有独立代次:快速 A→B、慢请求晚返回、连续 reload、卸载后返回、StrictMode 双 effect 都只允许最新有效请求写状态;不宣称重复 effect 从未发生(运行时缓存去重网络与生命周期)
|
|
748
|
-
- `reload` 开始时清空旧 data/error 并设 loading=true;当前尝试成功后写 data,失败后仅写 error,均结束 loading。卸载会作废未完成的 effect/reload,卸载后调用已保存的 reload 不发起请求。已成功缓存的模块不会重新下载;`Promise<void>` 正常结束(按钮 `onClick` 调用不产生未处理拒绝)
|
|
749
|
-
- `AppContext` 不是 React 状态订阅:宿主读到新的非空 `sessionKey` 时由**宿主自身状态/路由**触发重新渲染(`createHostPages` 的组件缓存会在新登录代次自动重建,触发新代次 `onSession`)
|
|
750
|
-
|
|
751
|
-
#### `RemoteErrorBoundary`
|
|
229
|
+
宿主的 `src/App.tsx`(假设 `remotes` 中配置的名字是 `remote-react`):
|
|
752
230
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
#### `createReactHostPages(options)`
|
|
756
|
-
|
|
757
|
-
与 Vue 侧共用同一份页面表数据与 `definePages` R1–R5 校验(5.1.0 起纯解析提取为共用内核);返回 `{ pages, resolve(path), component(spec) }`——`component(spec)` 返回 React 组件类型,路由层用 JSX / `createElement` 渲染即可(不提供 `.element()` 同义入口)。
|
|
758
|
-
|
|
759
|
-
- `options` 数据项:`pages / remotePrefixes / deriveSpec / schema / strict / base`(语义与 Vue 完全一致);React 展示项:`fallback / error / retries / timeout`(语义与 `remoteComponent` 一致)+ `beforeLoad`(每次实际加载尝试前执行,供宿主刷新 context;页面表创建时不执行)
|
|
760
|
-
- `resolve` 保持 base 剥离、最长前缀、参数解码(坏 `%` 序列只让该次匹配失败)、query/hash、无匹配返回 `null`
|
|
761
|
-
- 组件缓存按 spec 与登录代次复用;**仅新的非空 `sessionKey` 到来时重建**(登出变 `undefined` 不重建——与 Vue 侧同语义);换账号后重新加载触发新代次 `onSession`
|
|
762
|
-
- React 侧不提供 `keepAliveNames`(不承诺组件保活);路由不是插件的运行时依赖——示例用 React Router 7(`path` 在路由表声明、`element` 渲染 `component(spec)` 产物;带参数路由经 `useParams`/`useSearchParams` 传给远程页面 props)
|
|
763
|
-
- 跨框架共享 Context:宿主与远程消费方经**同一 expose 实例**拿到同一 Context 对象(如远程 `expose './theme-context'` 导出 `createContext` 实例,宿主 `useLoadRemote` 取得后作 Provider,远程组件 `useContext` 读到宿主值);插件不自动桥接任意 React Context——必须显式共享该对象
|
|
764
|
-
|
|
765
|
-
#### React 的 dev 类型
|
|
766
|
-
|
|
767
|
-
`@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`。
|
|
768
|
-
|
|
769
|
-
类型生成支持字符串或数组 `extends`(后项覆盖前项)、指向目录的 `references`,并按声明文件目录解析继承路径。`baseUrl` 与 `paths` 独立继承。多个实际应用上下文的远程 `paths` 接管不一致时,会保留默认宽松声明并给出中文提示;需要精确类型时请统一这些应用配置。生命周期错误 `MFU-012` 的 `cause` 保留 setup/onSession 抛出的原始异常。
|
|
770
|
-
|
|
771
|
-
### 8.2 跨框架桥接 API — `/bridge`(子应用级 Vue↔React 互嵌,5.3.0 起)
|
|
772
|
-
|
|
773
|
-
**产品范围**:整站挂载/卸载的双向嵌入——Vue 3 宿主嵌 React 18/19 子应用、React 18/19 宿主嵌 Vue 3 子应用。组件级互转、宿主与子应用 URL 同步、Angular、SSR/RSC、JS 沙箱、CSS 隔离不在支持面(见 §12)。
|
|
774
|
-
|
|
775
|
-
#### 入口与导入图
|
|
776
|
-
|
|
777
|
-
```text
|
|
778
|
-
构建期 @fulgurjs/federation -> 插件(不变)
|
|
779
|
-
Vue 子应用 @fulgurjs/federation/runtime -> defineBridgeApp(零 React)
|
|
780
|
-
React 子应用 @fulgurjs/federation/react -> defineBridgeApp(零 Vue;react-dom/client 实际 mount 时才加载)
|
|
781
|
-
桥接宿主 @fulgurjs/federation/bridge/vue -> createVueBridgeApp(推荐:Vue 宿主,零 React)
|
|
782
|
-
@fulgurjs/federation/bridge/react -> createReactBridgeApp(推荐:React 宿主,零 Vue)
|
|
783
|
-
@fulgurjs/federation/bridge -> 聚合入口(兼容保留;dev 原生 ESM 会同时执行两个宿主适配器)
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
**推荐用法是分离入口**:只用 `createVueBridgeApp` 的宿主页在 dev 首屏与生产产物中都不执行 React 宿主适配器,反之亦然(e2e 断言请求图)。聚合 `/bridge` 在生产可摇树、在 dev 无摇树保证——文档与示例默认分离入口。
|
|
231
|
+
```tsx
|
|
232
|
+
import { remoteComponent } from '@fulgurjs/federation/react'
|
|
787
233
|
|
|
788
|
-
|
|
234
|
+
// 放在模块顶层,不要在每次组件渲染时重新创建。
|
|
235
|
+
const RemoteButton = remoteComponent<{ label: string }>('remote-react/Button', {
|
|
236
|
+
fallback: <p>正在加载…</p>,
|
|
237
|
+
})
|
|
789
238
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
shared: {
|
|
793
|
-
vue: { singleton: true },
|
|
794
|
-
react: { singleton: true },
|
|
795
|
-
'react-dom': { singleton: true },
|
|
239
|
+
export default function App() {
|
|
240
|
+
return <RemoteButton label="远程 React 按钮" />
|
|
796
241
|
}
|
|
797
242
|
```
|
|
798
243
|
|
|
799
|
-
|
|
244
|
+
React 加载普通 TS 模块时可以使用 `loadRemote` 或 `useLoadRemote`,都从 `/react` 导入。完整工程见 [React examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/react)。
|
|
800
245
|
|
|
801
|
-
|
|
246
|
+
## Vue 和 React 怎么互相嵌套
|
|
802
247
|
|
|
803
|
-
|
|
248
|
+
**嵌入的是子应用:子应用创建自己的组件树,宿主提供显示区域。** 不需要把 React 组件转换成 Vue 组件。
|
|
804
249
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
import { createMemoryHistory, createRouter } from 'vue-router'
|
|
809
|
-
import { defineBridgeApp } from '@fulgurjs/federation/runtime'
|
|
810
|
-
import App from './App.vue'
|
|
811
|
-
|
|
812
|
-
export default defineBridgeApp((props) => {
|
|
813
|
-
const app = createApp(App, props)
|
|
814
|
-
app.use(createRouter({ history: createMemoryHistory(), routes }))
|
|
815
|
-
return app // 返回装配完整的 VueApp;mount/unmount 由契约负责
|
|
816
|
-
})
|
|
817
|
-
```
|
|
250
|
+
以 Vue 宿主嵌入 React 为例:
|
|
251
|
+
|
|
252
|
+
1. React 远程创建 `src/bridge.tsx`,默认导出桥接对象:
|
|
818
253
|
|
|
819
254
|
```tsx
|
|
820
|
-
// React 子应用 src/bridge.tsx
|
|
821
|
-
import { MemoryRouter } from 'react-router-dom'
|
|
822
255
|
import { defineBridgeApp } from '@fulgurjs/federation/react'
|
|
823
256
|
|
|
824
257
|
export default defineBridgeApp((props) => (
|
|
825
|
-
<
|
|
258
|
+
<section>React 子应用:{String(props.message ?? '')}</section>
|
|
826
259
|
))
|
|
827
260
|
```
|
|
828
261
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
- `mount(el, props?): void | Promise<void>`——返回 `void` 表示首次根提交已同步完成(Vue 同步 mount);返回 Promise 时宿主保持 pending 直到首次根提交后完成(React 由契约内建提交探针兑现,`root.render()` 返回**不**算成功)。首次提交前的失败必须抛错/拒绝(宿主转 `MFU-016`,`details.phase: 'mount'`)并清理已创建的 app/root。
|
|
832
|
-
- `unmount(el): void`——同步使该容器代次失效并清理;未知容器为 no-op。pending 时卸载立即作废本轮代次,迟到的成功/失败不得复活 DOM、改写宿主状态或产生未处理拒绝。unmount 抛错由宿主捕获报 `MFU-016`(`phase: 'unmount'`),该容器清理状态不确定,插件会**持久封锁该容器**:同页「重试加载」与换会话都不会在此容器重新挂载(默认占位随之移除「重试加载」按钮),只能整页刷新恢复;残留资源(事件订阅/定时器/全局副作用)请如实排查。
|
|
833
|
-
- 契约实例**按容器 el 分键**:同一契约多处挂载互不干扰;同一容器未卸载再次 mount 拒绝(`MFU-016`,容器已被占用)且不覆盖原实例。
|
|
834
|
-
- 首次根提交后的子应用内部错误由**子应用自己的错误边界**负责——宿主 ErrorBoundary/errorCaptured 捕不到跨 root 的渲染错误,插件不冒充兜底(§4.4 语义,README 不承诺「宿主兜底子应用一切错误」)。
|
|
835
|
-
|
|
836
|
-
#### 宿主侧工厂(`/bridge/vue` 与 `/bridge/react`)
|
|
262
|
+
2. 在 React 远程的 `exposes` 中增加:
|
|
837
263
|
|
|
838
264
|
```ts
|
|
839
|
-
|
|
840
|
-
import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
|
|
841
|
-
import { getLatestHostContext } from './host-context' // 宿主自有的同步纯 getter
|
|
842
|
-
|
|
843
|
-
const RemoteReactApp = createVueBridgeApp('bridge-react-remote/bridge', {
|
|
844
|
-
retries: 1,
|
|
845
|
-
getContext: () => getLatestHostContext(),
|
|
846
|
-
})
|
|
847
|
-
// 模板:<RemoteReactApp :session-key="loginKey" :app-props="{ userId, onReady }" />
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
```tsx
|
|
851
|
-
// React 宿主
|
|
852
|
-
import { createReactBridgeApp } from '@fulgurjs/federation/bridge/react'
|
|
853
|
-
const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', {
|
|
854
|
-
getContext: () => getLatestHostContext(),
|
|
855
|
-
})
|
|
856
|
-
// JSX:<RemoteVueApp sessionKey={loginKey} appProps={{ userId, onReady }} />
|
|
265
|
+
exposes: { './bridge': './src/bridge.tsx' }
|
|
857
266
|
```
|
|
858
267
|
|
|
859
|
-
|
|
860
|
-
|---|---|---|
|
|
861
|
-
| 工厂选项 | `loadingComponent?` `errorComponent?`(收到 `error` prop,完全接管) `retries?`(0–10 整数) `timeout?`(正有限 ms) `getContext?` | `fallback?`(pending 占位) `error?`(节点或 `(error, retry) => ReactNode`) `retries?` `timeout?` `getContext?` |
|
|
862
|
-
| 返回组件 props | `appProps: P`(业务数据)+ `sessionKey?: string \| null`(控制参数,不混入业务 props) | 同左,`ComponentType<{ appProps: P; sessionKey?: string \| null }>` |
|
|
863
|
-
| 泛型 | `createVueBridgeApp<P>(spec, options?)`,P 只约束 `appProps` | 同左 |
|
|
864
|
-
| spec | 完整 `<remote>/<expose>`,与 `remoteComponent` 同一解析规则;无 remotePrefixes/schema/deriveSpec | 同左 |
|
|
865
|
-
| 默认错误占位 | 中文诊断(错误码+根因+修法)+「重试加载 / 刷新页面重试」 | 同左 |
|
|
866
|
-
|
|
867
|
-
- **`appProps` 快照语义**:挂载时浅拷贝顶层字段传入,嵌套对象/响应式 store/函数保留原引用;之后的顶层替换**不追踪、不重渲染子应用**,需要重置用 `:key`/key 重建。宿主新闭包不会自动传给子应用——实时读取宿主状态请传稳定回调(内部读 ref/store)或主动重挂。跨 root 不继承宿主 provide/inject、Pinia、React Context 或路由——需要的数据经 `appProps`、AppContext、共享实例或子应用自装。
|
|
868
|
-
- **`getContext`**:无副作用的**同步** getter,在首次、重试及换会话的实际加载前调用;返回快照对象(拒绝 Promise/thenable 与非对象——`MFU-016`,`phase: 'getContext'`)。桥接层先校验快照 `sessionKey` 与受控值一致(不一致 `MFU-017`,且不写全局),**校验通过后由桥接层调用 `provideAppContext`**——getter 本身不写全局。未提供 getter 时校验现有 `AppContext.sessionKey` 必须与受控值一致。换代时桥接层先 `clearAppContext()` 清旧账号独有字段再写新快照,保证零旧账号残留。
|
|
869
|
-
- **`sessionKey` 受控语义**:只接受 `undefined`(不启用受控会话)/`null`(登出态:立即卸载、保持空容器、不再 loadRemote)/非空字符串(登录代次)。空字符串、数字等非法值按 `MFU-017` 拒绝挂载。
|
|
870
|
-
|
|
871
|
-
| 触发 | 行为 |
|
|
872
|
-
|---|---|
|
|
873
|
-
| 首次渲染,`sessionKey` 非空字符串 | getContext(若提供)→ 校验快照/现有 context → 桥接层 provideAppContext → loadRemote → 契约校验 → `contract.mount(el, appProps 快照)`;远程 onSession 用同一代次 |
|
|
874
|
-
| 首次渲染,`sessionKey` 省略 | 不启用受控校验;仍可提供快照或复用现有 AppContext;远程声明 onSession 时按 runtime 既有规则(无 sessionKey → `MFU-013`) |
|
|
875
|
-
| `sessionKey` A→B | 推荐宿主先置 null 等卸载、`clearAppContext()` 后再更新;直接 A→B 时包装组件先作废并卸载 A、确认完成后才写 B 的 context 并挂载 |
|
|
876
|
-
| `sessionKey` → `null` | 立即作废旧加载并卸载;保持空容器不再请求;宿主随后 `clearAppContext()` 并移除/禁用缓存的私有页面 |
|
|
877
|
-
| 同会话重渲染 / 只换 `appProps` 引用 | 不重挂、不重复 loadRemote;业务数据仍是上次挂载快照 |
|
|
878
|
-
| 点错误占位「重试加载」 | 同页重建尝试(成功模块走运行时缓存;失败入口按现有机制换 URL 重取) |
|
|
879
|
-
|
|
880
|
-
- **多实例与页面级单会话**:同页多个同 spec 实例并存合法(契约按 el 分键);`AppContext` 是页面级单例——同页所有受控桥接实例必须同一会话,后挂实例与活跃实例代次不一致按 `MFU-017` 拒绝(不让两实例互相覆盖身份)。不承诺同页同时承载两个账号。
|
|
881
|
-
- **DOM 所有权**:包装组件只创建并保持稳定的空挂载容器;pending/error 占位是它的兄弟节点,宿主重渲染不 patch 子应用 root 内部。React 宿主 StrictMode 双 effect(mount→cleanup→mount)安全。Vue `<KeepAlive>` 的 deactivate 不是卸载——缓存页中的子应用保有 root 与状态;需要离页即销毁就别缓存该页,登出流程应同时移除缓存的私有页面。
|
|
882
|
-
- **旧请求不冒充取消**:已进入 `loadRemote` 的工作不因桥接层作废而被取消——迟到的旧结果按代次丢弃(不 mount、不覆盖、无未处理拒绝);远程 `onSession` 必须遵守既有 `signal.aborted` 契约(异步等待后、写私有状态前检查信号)。
|
|
883
|
-
|
|
884
|
-
### 8.3 桥接 URL 同步 — `/bridge/router/*`(子应用内部路由 ↔ 宿主浏览器地址,5.4.0 起)
|
|
885
|
-
|
|
886
|
-
桥接默认 memory 路由:子应用内部跳转不改浏览器地址、刷新不能恢复子应用内部页面。URL 同步让**宿主 URL 表达子应用内部位置**——刷新直达、收藏分享、前进后退、宿主菜单跳转全部一致。显式开启,**默认关闭**(5.3.x 行为与老契约完全不变)。
|
|
887
|
-
|
|
888
|
-
**架构约定**:宿主 Router 是浏览器历史唯一写入方;子应用使用受控 memory 路由;两端经独立路由通道(不进 appProps/Context)传递位置;同实例内 path/search/hash 变化**不重挂 root、不重建 store、不重新加载远程**。
|
|
889
|
-
|
|
890
|
-
**① 宿主(Vue Router 4,history/hash 模式皆可)**:
|
|
891
|
-
|
|
892
|
-
```ts
|
|
893
|
-
// main.ts:宿主路由声明后缀匹配(缺它详情导航会卸载子应用!)
|
|
894
|
-
const router = createRouter({
|
|
895
|
-
history: createWebHistory(import.meta.env.BASE_URL), // hash 模式用 createWebHashHistory
|
|
896
|
-
routes: [
|
|
897
|
-
{ path: '/', component: Home },
|
|
898
|
-
{ path: '/approval/:pathMatch(.*)*', component: ApprovalBridgePage },
|
|
899
|
-
],
|
|
900
|
-
})
|
|
901
|
-
// 真实权限守卫:拒绝 → 通道收到 cancelled,URL/历史/子应用位置全部不变
|
|
902
|
-
router.beforeEach((to) => (to.path.startsWith('/approval/secret') ? false : undefined))
|
|
903
|
-
```
|
|
268
|
+
3. Vue 宿主配置该远程地址,然后在页面中使用:
|
|
904
269
|
|
|
905
270
|
```vue
|
|
906
|
-
<!-- ApprovalBridgePage.vue:路由 prop = 独立控制通道 -->
|
|
907
271
|
<script setup lang="ts">
|
|
908
272
|
import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
|
|
909
|
-
|
|
910
|
-
const navigation = createVueBridgeNavigation(router) // Vue Router fullPath 已是逻辑路径,无需传部署 base
|
|
911
|
-
const routing: BridgeHostRouting = { basePath: '/approval', navigation }
|
|
912
|
-
const RemoteApp = createVueBridgeApp('remote/bridge-routed', { /* 同 8.2 */ })
|
|
273
|
+
const RemoteApp = createVueBridgeApp<{ message: string }>('remote-react/bridge')
|
|
913
274
|
</script>
|
|
275
|
+
|
|
914
276
|
<template>
|
|
915
|
-
<RemoteApp :
|
|
277
|
+
<RemoteApp :app-props="{ message: '来自 Vue 宿主' }" />
|
|
916
278
|
</template>
|
|
917
279
|
```
|
|
918
280
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
```ts
|
|
922
|
-
// bridge.ts(Vue 子应用):defineBridgeApp(工厂, { routing: true })——工厂第二参数 { signal, routing }
|
|
923
|
-
import { createRouter, createMemoryHistory, RouterView } from 'vue-router'
|
|
924
|
-
import { defineBridgeApp } from '@fulgurjs/federation/runtime'
|
|
925
|
-
import { connectVueBridgeRouter } from '@fulgurjs/federation/bridge/router/vue'
|
|
926
|
-
export default defineBridgeApp(async (props, ctx) => {
|
|
927
|
-
if (!ctx?.routing) throw new Error('本契约需要宿主启用 URL 同步')
|
|
928
|
-
const router = createRouter({ history: createMemoryHistory(), routes: [
|
|
929
|
-
{ path: '/list', component: List },
|
|
930
|
-
{ path: '/detail/:id', component: Detail },
|
|
931
|
-
] })
|
|
932
|
-
await connectVueBridgeRouter(ctx.routing!, router, { signal: ctx.signal }).ready // 初始 push 落定后再 install(顺序不能反)
|
|
933
|
-
const app = createApp({ setup: () => () => h(RouterView) }, props)
|
|
934
|
-
app.use(router)
|
|
935
|
-
return app
|
|
936
|
-
}, { routing: true })
|
|
937
|
-
```
|
|
938
|
-
|
|
939
|
-
```tsx
|
|
940
|
-
// bridge.tsx(React 子应用):createReactBridgeRouter 返回 RouterProvider 元素
|
|
941
|
-
import { createReactBridgeRouter } from '@fulgurjs/federation/bridge/router/react'
|
|
942
|
-
import { defineBridgeApp } from '@fulgurjs/federation/react'
|
|
943
|
-
export default defineBridgeApp((_props, ctx) => {
|
|
944
|
-
if (!ctx?.routing) throw new Error('本契约需要宿主启用 URL 同步')
|
|
945
|
-
return createReactBridgeRouter(ctx.routing, [
|
|
946
|
-
{ path: '/list', element: <List /> },
|
|
947
|
-
{ path: '/detail/:id', element: <Detail /> },
|
|
948
|
-
], { signal: ctx.signal }).element
|
|
949
|
-
}, { routing: true })
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
宿主端 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)。
|
|
953
|
-
|
|
954
|
-
**③ 行为契约与边界**:
|
|
955
|
-
|
|
956
|
-
- **basePath**:宿主路由视角的静态绝对路径(拒绝空/根/带 query·hash·通配符,`MFU-030`);按路径段匹配(`/approval` 命中 `/approval/detail/1`,不命中 `/approval-old`);同页各同步实例前缀不得相同或重叠。`/approval` 对应子应用 `/`;根重定向由子应用路由定义、以 replace 规范化(不凭空多一条历史)。
|
|
957
|
-
- **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-*/`,可运行参考实现)。
|
|
958
|
-
- **位置三段全等**:search/hash 原样保留(重复 query 键、编码、中文、片段不二次 decode/encode);仅参数变化也同步,且不重挂。
|
|
959
|
-
- **取消语义**:Vue Router 4 的 push/replace 落定 `NavigationFailure` 即真实取消;React Router data router 等待真实 blocker 的取消/放行,`canNavigate` 仅为可选提前拒绝。取消后 URL、历史、子应用位置保持最后确认状态,**绝不自动重试**(`router.push` 的函数返回不冒充提交成功)。
|
|
960
|
-
- **导航与错误**:子应用 push/replace 保留原动作,go/back/forward 委托宿主历史;连续请求串行落定,外部导航作废旧的在飞与排队请求。守卫/加载器/端口执行异常拒绝 Promise(MFU-033,保留 cause),不会伪装 cancelled。两端子路由接线的第三参数 `{ signal?: AbortSignal }` 默认为空;推荐传 `ctx.signal` 自动 dispose,未传时由子应用显式调用连接的 `dispose()`。自定义 `BridgeHostNavigation.navigate(target, action, { signal })` 应在异步提交前复核可选 signal,已 aborted 时禁止迟到写入。
|
|
961
|
-
- **会话与生命周期**:换账号/登出(`sessionKey→null`)作废旧通道——旧通道的导航一律 cancelled、不写 URL、不复活子应用;unmount 后迟到通知失效(通道销毁,再订阅得 `MFU-031`)。KeepAlive 缓存离页的实例暂停路由写入(不抢占 URL、不销毁通道,激活重同步)。同一容器 unmount 抛错的持久封锁(BN09)不因路由绕过。
|
|
962
|
-
- **协议校验**:宿主启用 routing 而子应用未声明 `{ routing: true }`(契约 `routing: { protocol: 1 }`)→ `MFU-031` 占位,**不静默退回 memory 假装深链成功**。
|
|
963
|
-
- **非法导航与循环**:目标越界自身前缀(`../`、跨前缀)、非法 `go` 参数 → `MFU-032`;连续内部 replace 超过 5 次(重定向环)→ `MFU-033`(附目标链,不静默回入口)。
|
|
964
|
-
- **按需加载**:`/bridge`、`/runtime`、`/react` 默认入口不引入任何路由库;`/bridge/router/vue`、`/bridge/router/react` 为按需入口(`vue-router` / `react-router-dom` 为可选 peer,消费者自装)。两个入口各 ≤4096B gzip 门禁。
|
|
965
|
-
- **不承诺**:SSR/RSC、跨浏览器窗口、嵌套多级桥接子应用路由代理、TanStack Router 及其他路由库(可经 `BridgeHostNavigation`/`BridgeChildRoute` 端口自定义扩展)。
|
|
966
|
-
|
|
967
|
-
### 9. `AppContext` — 跨应用传值与方法引用(`@fulgurjs/federation/runtime`)
|
|
968
|
-
|
|
969
|
-
宿主向子应用传值、子应用向宿主反向注册方法,一律走这条一等公民通道(对标乾坤 `props`,但带类型与错误契约)——不再各自挂 `window.*` 裸口子。
|
|
970
|
-
|
|
971
|
-
```ts
|
|
972
|
-
// —— 宿主桥(host/src/fulgurjs/host/bridge.ts):登录态同步(可多次调用,幂等 merge) ——
|
|
973
|
-
import { provideAppContext, clearAppContext } from '@fulgurjs/federation/runtime'
|
|
974
|
-
|
|
975
|
-
provideAppContext({
|
|
976
|
-
user, // 宿主登录用户原始形态(当时快照)
|
|
977
|
-
getToken, // 取最新 token(拉取式防过期)
|
|
978
|
-
store: piniaInstance, // 宿主 pinia:子应用 useUserStore(ctx.store) 共享响应式状态
|
|
979
|
-
hostApp: app, // 宿主 Vue App 实例:全局组件/指令注册目标
|
|
980
|
-
locale, // EP locale 等 UI 配置
|
|
981
|
-
sessionKey: 's-101-1730…', // 非敏感登录代次 ID:每次成功登录/重登生成新值;
|
|
982
|
-
// token 刷新但会话未变时沿用。远程 onSession 按它去重,
|
|
983
|
-
// 缺失时声明了 onSession 的远程报 MFU-013。不是 token、不做授权凭证
|
|
984
|
-
events: { main: mainEvents }, // 事件/方法池:宿主提供 main;子应用反向注册 bpm.* / lowcode.*
|
|
985
|
-
// 只传有真实消费的键。项目自定义键经扩展位按需自行提供(如 baseUrl: '/demo')
|
|
986
|
-
})
|
|
987
|
-
|
|
988
|
-
// 退出登录时清理:删 context + 作废远程会话信号/onSession 去重状态
|
|
989
|
-
// (不重置远程模块缓存、共享模块图与应用级 setup 注册)
|
|
990
|
-
clearAppContext()
|
|
991
|
-
|
|
992
|
-
// —— 远程 setup/onSession:显式校验消费 ——
|
|
993
|
-
import { requireAppContext } from '@fulgurjs/federation/runtime'
|
|
994
|
-
|
|
995
|
-
const { store, user, hostApp } = requireAppContext('store', 'user', 'hostApp')
|
|
996
|
-
// 缺任一键 → [fulgurjs:CC-001] 三段式抛错(got / expected / example 指向宿主桥);
|
|
997
|
-
// 页面无运行时单例(独立直开远程页)→ [fulgurjs:CC-002] 显式,修法 = 经宿主联邦加载。
|
|
998
|
-
|
|
999
|
-
// —— 远程页面读点 ——
|
|
1000
|
-
import { getAppContext } from '@fulgurjs/federation/runtime'
|
|
1001
|
-
const dict = getAppContext().events?.main?.getDictItems?.('sex')
|
|
1002
|
-
|
|
1003
|
-
// —— 子应用反向注册方法给宿主(页面 onUnmounted 时记得摘除,见迁移指南「页面卸载清理清单」)——
|
|
1004
|
-
getAppContext().events!.bpm = { formEvent, formSubmitEvent }
|
|
1005
|
-
```
|
|
1006
|
-
|
|
1007
|
-
标准字段表:
|
|
281
|
+
跨框架宿主安装 `vue`、`react`、`react-dom`,共享这三个依赖。子应用只安装和共享自己的框架。React 与 react-dom 必须兼容;同页有多个 React 大版本时请按版本隔离 Demo 配置,不能只加 `singleton` 就认为兼容问题解决了。
|
|
1008
282
|
|
|
1009
|
-
|
|
1010
|
-
|---|---|---|---|
|
|
1011
|
-
| `user` | `Record<string, any>` | 宿主登录用户原始形态(提供时快照) | 宿主桥(只读约定;登录态变化时重新 provide 覆盖) |
|
|
1012
|
-
| `getToken` | `() => string \| undefined` | **取最新 token**(拉取式调用,永不过期;context 不提供一次性 token 快照字段) | 宿主桥(只读约定) |
|
|
1013
|
-
| `store` | `unknown`(运行时为宿主 pinia) | 子应用挂载/读取宿主共享响应式状态 | 宿主桥(只读约定) |
|
|
1014
|
-
| `hostApp` | Vue App 实例(同 realm 直引用) | 全局组件/指令注册目标 | 宿主桥(只读约定) |
|
|
1015
|
-
| `locale` | `unknown` | EP locale 等 UI 配置 | 宿主桥(只读约定) |
|
|
1016
|
-
| `sessionKey` | `string` | 非敏感登录代次 ID:每次成功登录/重登生成新值;token 刷新但会话未变时沿用。远程 onSession 按它去重(同一代次只执行一次,换代自动重跑);退出 `clearAppContext` 后必须重跑。生成责任在宿主登录流程;**不得用真实 token 充当**,也不作为授权凭证 | 宿主桥(登录后) |
|
|
1017
|
-
| `events` | `Record<string, any>` | 事件/方法池:`events.main.*` 宿主提供、`events.bpm.*` / `events.lowcode.*` 子应用反向注册 | 宿主桥建池,子应用挂载 |
|
|
1018
|
-
| (扩展位) | `[key: string]: unknown` | 项目自定义键(`formUrl` / `baseUrl` 等按需自行提供,模板默认不传) | 宿主桥;远程 boot 只增不改宿主键 |
|
|
283
|
+
反方向用 React 宿主的 `createReactBridgeApp`,Vue 子应用用 `/runtime` 的 `defineBridgeApp` 返回 `createApp(...)` 创建的应用。
|
|
1019
284
|
|
|
1020
|
-
|
|
285
|
+
需要记住三点:
|
|
1021
286
|
|
|
1022
|
-
- `
|
|
1023
|
-
-
|
|
1024
|
-
-
|
|
287
|
+
- `appProps` 在挂载时取得快照。之后替换顶层字段不会自动更新子应用;实时数据可传稳定回调或共享 store,需要重新挂载时使用组件 `key`。
|
|
288
|
+
- 两个组件树不会自动共用 Context、provide/inject 或路由,需要显式传递或在子应用安装。
|
|
289
|
+
- 普通组件加载用 `remoteComponent`;整个子应用嵌套用桥接工厂。Vue 不能直接用 Vue 的 `remoteComponent` 渲染 React 组件。
|
|
1025
290
|
|
|
1026
|
-
|
|
291
|
+
双向配置、登录切换和卸载示例见 [bridge examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/bridge)。
|
|
1027
292
|
|
|
1028
|
-
|
|
1029
|
-
|---|---|---|
|
|
1030
|
-
| **context 携带函数引用** | 同步直调(bridge 先于一切页面加载) | 高频热路径(`getToken` / `getDictItems` / `getFileAccessHttpUrl`)、子应用反向注册(`formEvent`) |
|
|
1031
|
-
| **exposes 方法模块** | `exposes: { './api': './src/fulgurjs/exposes/api.ts' }` → `const { xxx } = await loadRemote('remote/api')` | 低频/重逻辑跨应用调用;任意 expose 任意消费;dts 类型直连自动覆盖 |
|
|
1032
|
-
|
|
1033
|
-
方法模块规范:`src/fulgurjs/exposes/` 下的 `api.ts` 导出纯函数/服务对象(不挂 Vue 组件);依赖宿主单例的函数(如 defHttp 走 shared)直接写,联邦协商保证同模块图。
|
|
293
|
+
## 子应用路由和浏览器地址怎么同步
|
|
1034
294
|
|
|
1035
|
-
|
|
295
|
+
桥接默认不改宿主地址。比如子应用从列表进入详情,地址不变,刷新就无法凭地址恢复这条详情。
|
|
1036
296
|
|
|
1037
|
-
|
|
1038
|
-
// ① 提供方 vite.config.ts:exposes 加一条
|
|
1039
|
-
exposes: { './api': './src/fulgurjs/exposes/api.ts' }
|
|
1040
|
-
|
|
1041
|
-
// ② 提供方 src/fulgurjs/exposes/api.ts:导出纯函数
|
|
1042
|
-
import { defHttp } from '/@/utils/http/axios'
|
|
1043
|
-
export function getDictItems(dictCode: string) {
|
|
1044
|
-
return defHttp.get({ url: '/sys/dict/getDictItems/' + dictCode }, {})
|
|
1045
|
-
}
|
|
297
|
+
开启 URL 同步后可以这样使用:
|
|
1046
298
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
299
|
+
```text
|
|
300
|
+
宿主地址 /approval/list → 子应用 /list
|
|
301
|
+
宿主地址 /approval/detail/42 → 子应用 /detail/42
|
|
1050
302
|
```
|
|
1051
303
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
### 9.1 乾坤功能融合:保活 / 骨架屏 / 空闲预载 / 诊断面板(宿主与模板侧能力)
|
|
1055
|
-
|
|
1056
|
-
这些能力全部是**项目侧**能力(手工集成的项目按下述接入点自行落位;`fulgurjs init` 只生成配置起步模板,不生成这些项目文件),插件 runtime.js 零参与。配置面总览:
|
|
1057
|
-
|
|
1058
|
-
| 能力 | 配置项 | 类型 | 默认值 | 配置位置 |
|
|
1059
|
-
|---|---|---|---|---|
|
|
1060
|
-
| 页面保活 | `keepAlive` | `boolean` | `false` | 页面路由表条目(`src/fulgurjs/host/pages.ts`) |
|
|
1061
|
-
| 页面加载骨架屏 | —(内置,无配置项) | — | 见下方内置参数 | `src/fulgurjs/host/pages.ts` 页面工厂 |
|
|
1062
|
-
| 空闲预载 | `PREFETCH_REMOTES` | `string[]` | `[]`(关闭整远程预载,按需加载;详见 §9.1.3) | `src/fulgurjs/host/bridge.ts` 顶部常量 |
|
|
1063
|
-
| 联邦诊断面板 | —(内置页面) | — | 常驻 | 路由 `/fulgurjs-demo` |
|
|
1064
|
-
|
|
1065
|
-
#### 9.1.1 页面保活 — `keepAlive`
|
|
304
|
+
接入需要同时设置两端:
|
|
1066
305
|
|
|
1067
|
-
|
|
306
|
+
1. 宿主路由要接住 `/approval` 下的所有子路径,避免详情导航把子应用卸载。
|
|
307
|
+
2. 宿主桥接组件传 `routing`,其中 `basePath` 是 `/approval`,`navigation` 由宿主路由适配器创建。
|
|
308
|
+
3. 子应用声明 `defineBridgeApp(..., { routing: true })`,用受控的 memory 路由接入通道。
|
|
1068
309
|
|
|
1069
|
-
|
|
1070
|
-
|---|---|---|---|
|
|
1071
|
-
| `keepAlive` | `boolean` | `false` | `true` = 该页面纳入 LayoutContent 联邦分支 `<keep-alive>` 的 include 白名单 |
|
|
1072
|
-
|
|
1073
|
-
```ts
|
|
1074
|
-
// src/fulgurjs/host/pages.ts — 页面路由表条目
|
|
1075
|
-
|
|
1076
|
-
// 开启保活(显式)
|
|
1077
|
-
{ route: '/flowable/bpm/task/todo', name: 'BpmTodoTask', title: '待办任务', keepAlive: true }
|
|
1078
|
-
|
|
1079
|
-
// 关闭保活:不写该字段,或显式 false(二者等价,默认即关)
|
|
1080
|
-
{ route: '/flowable/bpm/manager/form', name: 'BpmForm', title: '流程表单', keepAlive: false }
|
|
1081
|
-
```
|
|
310
|
+
Vue 使用 `createVueBridgeNavigation` / `connectVueBridgeRouter`;React 使用 `createReactBridgeNavigation` / `createReactBridgeRouter`。React 宿主需要 data router(`createBrowserRouter` 或 `createHashRouter`),不能直接换成 `BrowserRouter`。内置适配支持 Vue Router 4、React Router ≥6.11。
|
|
1082
311
|
|
|
1083
|
-
|
|
312
|
+
这样刷新、分享链接、前进后退能恢复路由位置;**不会自动保存表单内容或业务数据**。详细步骤见 [URL 同步 API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#url-sync),可运行工程见 [bridge-router Demo](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/bridge-router)。
|
|
1084
313
|
|
|
1085
|
-
|
|
1086
|
-
- 缓存键 = 页面 spec 清洗名(`Fulgurjs_<remote>_<expose键>`),同一路由不同参数(fullPath 不同)各占一个缓存条目;
|
|
1087
|
-
- **默认全关的原因**:vxe-table、表单设计器等重型组件的缓存内存成本高,按页面逐个显式开启;
|
|
1088
|
-
- 开启页面的组件若注册了 `window` 级监听/定时器/context 反向注册,须遵循迁移指南「三D 页面卸载清理清单」(保活页只在真正被 LRU 淘汰时才 unmount)。
|
|
314
|
+
## 远程业务页需要用户信息或初始化时
|
|
1089
315
|
|
|
1090
|
-
|
|
316
|
+
这部分是可选功能。普通按钮、工具函数不需要它。
|
|
1091
317
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
| 内置参数 | 值 | 说明 |
|
|
318
|
+
| 需求 | 使用什么 | 什么时候发生 |
|
|
1095
319
|
|---|---|---|
|
|
1096
|
-
|
|
|
1097
|
-
| `
|
|
1098
|
-
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
#### 9.1.3 空闲预载 — `PREFETCH_REMOTES`(默认关闭)
|
|
1103
|
-
|
|
1104
|
-
**先分清四层(重要,勿把「路由声明多」当成「首屏会执行所有页面代码」)**:
|
|
1105
|
-
|
|
1106
|
-
| 层 | 机制 | 时机 | 网络成本 |
|
|
1107
|
-
|---|---|---|---|
|
|
1108
|
-
| ① 路由表声明 | `pages.data.ts` 26 条记录只是**数据映射**,不导入任何远程代码 | 构建期 | 零 |
|
|
1109
|
-
| ② 页面真实加载 | `createHostPages` 对每页 `defineAsyncComponent` 包装,**渲染时**才 `loadRemote(spec)` | 用户打开该页 | 该页 chunk + CSS(首次该远程还有入口/共享依赖/setup) |
|
|
1110
|
-
| ③ 单页预取 | `preloadRemote('remote-a/pages/remote-a/home', { mode: 'prefetch' })` | 项目主动调用 | 该 expose 的 chunk + CSS(**只下载不执行**) |
|
|
1111
|
-
| ④ 整远程预取 | `preloadRemote('remote-a', { mode: 'prefetch' })` | 项目显式开启 | manifest 全部 expose 的 chunk + CSS(**只下载不执行**) |
|
|
1112
|
-
|
|
1113
|
-
预取是**下载**(`modulepreload`/`stylesheet` 链接,`fetchPriority=low` 只是降低优先级、不等于不下载),**不等于执行页面代码**——`container.get()`、组件实例化、`setup/onSession` 都只由真实页面的 `loadRemote` 触发。已加载模块有 Promise 缓存:重复打开同页复用模块,换账号重做会话初始化但不重新下载 JS。多个 expose 共享同一 chunk 是正常打包结果。
|
|
1114
|
-
|
|
1115
|
-
宿主桥默认**关闭整远程预载**(`PREFETCH_REMOTES = []`):首次进入联邦页只下载该页所需资源,dashboard 不因登录而提前下载两个远程的全部页面文件。某项目的用户路径确有明确的「下一步页面」时,可低优先级预取一两个明确指定的 spec;把 `PREFETCH_REMOTES` 写成远程名列表则是**显式选择**整远程预热(下载完整 expose 清单)。
|
|
1116
|
-
|
|
1117
|
-
> 插件配置面(`FederationOptions`)**没有** `host.prefetch` 字段——4.1.0 前文档曾声称该配置存在,属错误描述,已订正。预载名单就是宿主桥里的常量,改名单只改这一个地方。
|
|
1118
|
-
|
|
1119
|
-
```ts
|
|
1120
|
-
// src/fulgurjs/host/bridge.ts 顶部常量
|
|
1121
|
-
const PREFETCH_REMOTES: string[] = [] // 默认:关闭整远程预载(按需加载)
|
|
320
|
+
| 宿主提供用户、取 token 的方法、store 等 | `provideAppContext` | 宿主加载远程业务模块前提供 |
|
|
321
|
+
| 远程读取这些值 | `getAppContext` / `requireAppContext` | 由远程业务代码调用 |
|
|
322
|
+
| 远程注册全局组件、样式或其他一次性内容 | 配置 `setup` 文件的默认导出 | 首次 `loadRemote('远程/模块')` 返回业务模块前执行 |
|
|
323
|
+
| 每次登录、换账号都要重新同步权限等 | 同文件具名导出 `onSession` | 按 `sessionKey` 区分登录次数 |
|
|
324
|
+
| 退出登录,清除共享的账号上下文 | `clearAppContext` | 由宿主退出流程调用;私有页面/缓存也须由宿主清理 |
|
|
1122
325
|
|
|
1123
|
-
|
|
1124
|
-
// idle(() => preloadRemote('mes-bpm/pages/bpm/task/todo', { mode: 'prefetch' }))
|
|
326
|
+
`sessionKey` 是一次登录的编号,**不是 token,也不是权限凭证**。重新登录或换账号生成新编号,单纯刷新 token 不换编号。
|
|
1125
327
|
|
|
1126
|
-
|
|
1127
|
-
// const PREFETCH_REMOTES: string[] = ['mes-bpm', 'mes-lowcode']
|
|
1128
|
-
```
|
|
1129
|
-
|
|
1130
|
-
行为与边界:
|
|
1131
|
-
|
|
1132
|
-
- 预载失败**不阻断业务**:runtime 按 MFU-007 语义发出 `window` 的 `fulgurjs:error` 事件并 console 警告(诊断面板⑤可查历史);
|
|
1133
|
-
- 预载注入 `<link rel="modulepreload">` 与 `<link rel="stylesheet">`,不执行模块——首次打开页面时才真正初始化容器;`preload` 等待样式 load/error,`prefetch` 低优先级后台加载;
|
|
1134
|
-
- 触发时机:宿主桥每次页面加载同步执行(幂等),实际预取发生在浏览器空闲回调中。
|
|
1135
|
-
|
|
1136
|
-
#### 9.1.4 联邦诊断面板(免登录页,无配置项)
|
|
1137
|
-
|
|
1138
|
-
演示页升级为运行时诊断面板(源自乾坤 v3 inspector 概念的轻量化),访问路由 `meta.ignoreAuth` 的 `/fulgurjs-demo`(prod 为 `/main/fulgurjs-demo`),六块信息实时读取运行时注册表:
|
|
328
|
+
桥接组件可以通过 `getContext` 在加载前取得最新信息;受控 `sessionKey` 为 `null` 表示退出,组件会卸载并停止加载。省略 `sessionKey` 表示未启用受控登录切换。
|
|
1139
329
|
|
|
1140
|
-
|
|
1141
|
-
|---|---|
|
|
1142
|
-
| ① 方法模块调用演示 | 按钮实调 `loadRemote('demo-host/api')` → `getDictItems('sex')` 并显示结果(方法引用通道②的活样例) |
|
|
1143
|
-
| ② remotes 状态 | 各 remote 的 entry / 加载状态(idle/loading/loaded/failed)/ 容器加载耗时 |
|
|
1144
|
-
| ③ shared 协商 | 共享键 → version ← 提供方(多版本并存可见) |
|
|
1145
|
-
| ④ context 快照 | AppContext 每个键的值形态(函数引用 / 对象 / 字符串,含 events 池) |
|
|
1146
|
-
| ⑤ fulgurjs:error 历史日志 | window 事件累积(时间戳 + remote + 错误消息),本轮会话零错误显示"无错误" |
|
|
1147
|
-
| ⑥ 远程资源加载耗时 | performance resource 中 fulgurjs / remoteEntry / chunk 相关条目与耗时 |
|
|
330
|
+
`setup` 不会由文件名或目录自动触发,必须写在联邦配置里。`preloadRemote` 只预载资源,不执行 `setup/onSession`。异步初始化写入状态前要检查 `context.signal.aborted`,避免退出后迟到的请求写回旧账号数据。
|
|
1148
331
|
|
|
1149
|
-
|
|
332
|
+
完整代码见 [初始化与上下文 API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#context)。
|
|
1150
333
|
|
|
1151
|
-
|
|
1152
|
-
- 升级插件版本后若 `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`。
|
|
1153
|
-
- **根治红波浪线**:`federation({ dts: { mode: 'shim' } })` —— 生成物不再引用跨工程源文件(宽松占位形态),IDE 全程干净;取舍是失去"跳转直达远程源码"的补全能力(默认 `source` 不变,按项目偏好选择)。
|
|
334
|
+
## 多个远程页面怎么管理
|
|
1154
335
|
|
|
1155
|
-
|
|
336
|
+
宿主有很多路由对应远程页面时,可以维护一份页面表,再交给 `createHostPages`(Vue)或 `createReactHostPages`(React)。它们负责查找对应模块、缓存加载组件和显示加载/错误状态,**不会自动替你创建宿主 Router**。
|
|
1156
337
|
|
|
1157
|
-
|
|
338
|
+
页面表里 `route` 是宿主路径,`spec` 是远程的 exposes 键(通常省略 `./`,不要重复加远程名);`remotePrefixes` 指定这批路径属于哪个远程。例如 `/shop/home` + `spec: 'pages/Home'` + `remotePrefixes: { '/shop': 'shop' }`,最终加载的是 `shop/pages/Home`。Vue 可结合 KeepAlive 保存组件状态;React 不提供相同的保活承诺。
|
|
1158
339
|
|
|
1159
|
-
|
|
1160
|
-
import { createHostPages } from '@fulgurjs/federation/runtime'
|
|
1161
|
-
|
|
1162
|
-
export const hostPages = createHostPages({
|
|
1163
|
-
pages: [/* PageRouteLike[]:宿主路由与布局共用的唯一页面来源 */],
|
|
1164
|
-
remotePrefixes: { '/remote-a/': 'remote-a' }, // 必填;最长前缀匹配
|
|
1165
|
-
deriveSpec: (route) => `pages/${...}`, // 可选;缺省 = 去首段 + 剥 :参数 段
|
|
1166
|
-
schema: remoteSchema, // 可选;dev 探针结果(R3 校验),build 为空表诚实降级
|
|
1167
|
-
strict: true, // 可选;ERROR 级校验失败默认 throw
|
|
1168
|
-
base: '/main', // 可选;resolve 时剥离的站点 base 前缀
|
|
1169
|
-
beforeLoad: () => { /* 每次页面模块实际加载前执行(同步/异步);宿主在此提供最新 context */ },
|
|
1170
|
-
loadingComponent: MySkeleton, // 可选;缺省无骨架
|
|
1171
|
-
errorComponent: MyError, // 可选;缺省 = 内置三段式错误占位
|
|
1172
|
-
delay: 200, // 可选;骨架屏延迟 ms
|
|
1173
|
-
})
|
|
1174
|
-
```
|
|
340
|
+
完整配置见 [页面 API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#pages) 和 [pages-cli Demo](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/pages-cli)。
|
|
1175
341
|
|
|
1176
|
-
|
|
1177
|
-
|---|---|
|
|
1178
|
-
| `pages` | 原页面记录(不经改写,宿主路由注册直接用) |
|
|
1179
|
-
| `resolve(path)` | `{ page, remote, spec, params } \| null`——兼容 base 前缀与深链;参数解码失败只让该次匹配失败(console 提示),不抛错 |
|
|
1180
|
-
| `component(spec)` | 异步页面组件(`<remote>/<exposes键>` 完整 spec);同 spec 复用。**会话感知**:AppContext.sessionKey 变化(换账号/重登/退出)后自动重建组件——下一次渲染重新走 `beforeLoad → loadRemote`,触发新代次的 `onSession`(模块本体经 loadRemote 缓存复用,不重复下载)。无 sessionKey 的用法缓存永不失效 |
|
|
1181
|
-
| `keepAliveNames` | `keepAlive: true` 页面的组件 name(与实际被 KeepAlive 缓存的包装组件一致,直接绑 `<keep-alive :include>`) |
|
|
342
|
+
## 构建与部署
|
|
1182
343
|
|
|
1183
|
-
|
|
344
|
+
宿主和远程分别执行自己的构建命令,然后分别部署:
|
|
1184
345
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
- 组件是**本地包装组件**(稳定 name 供 KeepAlive include 匹配),不修改远程模块导出的组件对象(它可能是跨页面共享的模块实例);attrs/slots 全量透传;
|
|
1188
|
-
- 加载顺序:`beforeLoad` → 远程可选 `setup`/`onSession`(loadRemote 生命周期)→ 页面模块;失败进 `errorComponent` 错误态(显式错误码/根因/修法),不静默回退。
|
|
1189
|
-
|
|
1190
|
-
#### 10.2 `federation({ setup })` — 远程初始化生命周期
|
|
1191
|
-
|
|
1192
|
-
```ts
|
|
1193
|
-
// 远程 vite.config.ts
|
|
1194
|
-
federation({ name: 'remote-a', exposes: { ... }, setup: './src/fulgurjs/setup.ts' })
|
|
1195
|
-
```
|
|
1196
|
-
|
|
1197
|
-
```ts
|
|
1198
|
-
// remote-a/src/fulgurjs/setup.ts —— 只有下面两个函数名有自动生命周期语义
|
|
1199
|
-
import type { RemoteSetupContext, RemoteSetupModule } from '@fulgurjs/federation/runtime'
|
|
1200
|
-
|
|
1201
|
-
export default async function setup(context: RemoteSetupContext) {
|
|
1202
|
-
// 应用级一次性注册:全局组件/指令、全局样式、locale 注入、宿主 app 上的插件安装
|
|
1203
|
-
// context.appContext = 调用时的 AppContext 快照;context.signal = 生命周期信号
|
|
1204
|
-
}
|
|
1205
|
-
export async function onSession(context: RemoteSetupContext) {
|
|
1206
|
-
// 会话级同步:当前用户、权限、字典、token 相关缓存
|
|
1207
|
-
const data = await fetchUserDicts()
|
|
1208
|
-
if (context.signal.aborted) return // ← await 之后写状态前必须检查:期间可能已换账号/退出
|
|
1209
|
-
writeToStores(data)
|
|
1210
|
-
}
|
|
346
|
+
```bash
|
|
347
|
+
npm run build
|
|
1211
348
|
```
|
|
1212
349
|
|
|
1213
|
-
`
|
|
350
|
+
远程默认生成 `fulgurjs-remoteEntry.js` 和 `fulgurjs-manifest.json`,宿主通过配置的 `prod` 地址找到它们。远程可以部署到同域子目录,也可以部署到另一个域名。
|
|
1214
351
|
|
|
1215
|
-
|
|
352
|
+
部署时核对:
|
|
1216
353
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
| 执行次数(onSession) | 按非敏感 `sessionKey` 去重:同一登录代次一次;新代次先作废旧 `signal`,再按远程**串行**衔接旧调用与新调用(防两账号异步写入交错);`clearAppContext()` 失效去重状态,下次登录必须重跑。应用级 setup 不因退出/换代重复执行 |
|
|
1223
|
-
| sessionKey | 宿主登录流程每次成功登录/重登生成新代次(非敏感 ID,禁止用 token);token 刷新但会话未变时沿用。**有 onSession 却缺 sessionKey → MFU-013**,不凭用户对象引用猜测身份;无 onSession 的远程无需 sessionKey |
|
|
1224
|
-
| 导出校验 | 必须默认导出函数;具名 `onSession` 可选且必须是函数;其他导出不作为入口。违反 → MFU-011(报实际类型/预期签名/修法) |
|
|
1225
|
-
| 失败与重试 | setup/onSession 抛错 → 该次 `loadRemote` 拒绝(MFU-012);**只清失败阶段的缓存**(setup 失败重试从 setup 开始;onSession 失败只重跑会话段),已成功的阶段不重复。`fallbackModule` 不掩盖初始化失败 |
|
|
1226
|
-
| 自递归 | setup/onSession 同步段内 `loadRemote(同 remote/…)` → MFU-014(该调用会等待自身形成死锁)。异步段内的同远程递归无法精确归因,表现为挂起——不要在初始化内加载同远程模块 |
|
|
1227
|
-
| dev/prod 一致 | dev 容器(中间件直出)与 prod 容器(构建产物)携带同一 setup 元数据(容器上的 `__fulgurjsSetup` 字段 + manifest 的 `setup` 字段);内部 expose 键 `./__fulgurjs_setup__` 不出现在 dts 类型与公开文档 exposes 清单中 |
|
|
1228
|
-
| 错误码 | MFU-011 导出非法 / MFU-012 执行失败 / MFU-013 缺 sessionKey / MFU-014 自递归;全部带 remote 名、模块路径/阶段、实际结果、预期与修法,不记录 token |
|
|
354
|
+
- **地址与 base 一致**:远程部署在 `/remote-vue/` 时,远程 Vite 构建的 `base` 也应为 `/remote-vue/`;宿主 `prod` 配置为 `/remote-vue`。
|
|
355
|
+
- **入口及时更新**:HTML、remoteEntry、manifest 使用 `Cache-Control: no-cache`,让浏览器重新验证最新内容。带内容哈希的 chunk 可以长缓存。
|
|
356
|
+
- **刷新能回到页面**:宿主和独立远程的页面路由分别配置 SPA 回退;资源请求不存在时应返回 404,不要把 JS 请求回退成 HTML。
|
|
357
|
+
- **跨域允许访问**:不同域名时,远程服务器要正确提供 CORS 响应头;开发配置不会自动替你修改生产服务器。
|
|
358
|
+
- **避免旧文件突然失效**:发布期间保留仍被旧页面引用的 chunk,或使用能避免版本混搭的部署流程。
|
|
1229
359
|
|
|
1230
|
-
|
|
360
|
+
生产部署样例见 [Vue 部署说明](https://github.com/chenmingye/fulgurjs-federation/blob/master/examples/vue/README.md) 与 [React 部署说明](https://github.com/chenmingye/fulgurjs-federation/blob/master/examples/react/README.md)。
|
|
1231
361
|
|
|
362
|
+
## 加载失败时怎么办
|
|
1232
363
|
|
|
364
|
+
| 现象 | 先检查 | 插件提供的恢复方式 |
|
|
365
|
+
|---|---|---|
|
|
366
|
+
| 远程连不上 | 远程是否启动、remotes 地址和 CORS | 超时、重试、错误占位;可配置备用入口或 fallback 模块 |
|
|
367
|
+
| 提示模块不存在 | `远程名/模块名` 是否对应 remotes/exposes | 修正名称后重试 |
|
|
368
|
+
| 提示共享版本不兼容 | 两边依赖版本、requiredVersion、strictVersion、作用域 | 对齐依赖或隔离不同版本,不能靠忽略错误解决 |
|
|
369
|
+
| 远程静态依赖曾下载失败,服务恢复后仍失败 | 浏览器可能保留该依赖 URL 的失败记录 | 默认占位提供用户主动刷新,保留当前地址 |
|
|
370
|
+
| 子应用卸载失败 | 子应用清理逻辑、事件和定时器 | 该容器不再重新挂载,需刷新;同时修复清理逻辑 |
|
|
1233
371
|
|
|
1234
|
-
|
|
372
|
+
`remoteComponent` 和桥接组件有默认错误占位。直接调用 `loadRemote`,或使用 React 的 `useLoadRemote`,需要自己处理错误状态。`fallbackModule` 是你显式选择的备用模块,不会自动修复原远程。
|
|
1235
373
|
|
|
1236
|
-
|
|
374
|
+
错误中会给出代码、原因和处理建议;完整清单见 [错误码手册](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#error-codes)。
|
|
1237
375
|
|
|
1238
|
-
|
|
376
|
+
## Vite 8 和使用范围
|
|
1239
377
|
|
|
1240
|
-
|
|
378
|
+
**支持 Vite 8 开发和生产构建,已修复此前大型应用启动挂起的问题,并通过相关回归测试。**
|
|
1241
379
|
|
|
1242
|
-
|
|
1243
|
-
# 全部分类
|
|
1244
|
-
FULGURJS_DEBUG='fulgurjs:*' pnpm dev # 或 DEBUG='fulgurjs:*'
|
|
1245
|
-
# 只开一个分类(transform / facade / manifest)
|
|
1246
|
-
FULGURJS_DEBUG='fulgurjs:transform' pnpm dev
|
|
1247
|
-
# build 同样适用
|
|
1248
|
-
FULGURJS_DEBUG='fulgurjs:*' pnpm build 2>fulgurjs-debug.log
|
|
1249
|
-
```
|
|
380
|
+
还有两点与使用有关:
|
|
1250
381
|
|
|
1251
|
-
|
|
382
|
+
- **开发第一次打开可能重载**:Vite 在准备依赖,发现新依赖时可能重新优化并刷新页面。等准备完成后再判断页面是否正常;这不是生产页面每次都会发生的行为。
|
|
383
|
+
- **部分共享场景会多下载文件**:浏览器可能下载未采用的本地库副本。同一 singleton 作用域仍使用一个实例;你主动隔离 React 18/19 时,则可以各自使用一个实例。文件下载数量与运行时实例数量不是同一件事。
|
|
1252
384
|
|
|
1253
|
-
|
|
385
|
+
当前不提供 SSR/RSC、Node 服务端联邦、React Native、自动 JS 沙箱、自动 CSS 隔离,或 webpack `script/var` 产物互操作。远程全局样式和变量仍可能影响宿主;子应用内部错误也需要子应用自己的错误处理。
|
|
1254
386
|
|
|
1255
|
-
|
|
387
|
+
跨框架支持子应用级嵌套,不提供组件类型转换。多层桥接路由自动代理、跨窗口路由同步及其他路由库的内置适配也不在当前范围。完整说明及 webpack 的区别见 [能力对照](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/webpack-mf-对照与缺口.md)。
|
|
1256
388
|
|
|
1257
|
-
|
|
389
|
+
## 调试、类型和命令行
|
|
1258
390
|
|
|
1259
|
-
|
|
391
|
+
在应用根目录运行:
|
|
1260
392
|
|
|
1261
393
|
```bash
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
```
|
|
1265
|
-
|
|
1266
|
-
### 3. UMD / CJS-only 依赖(element-plus、avue、dayjs…)放 `include`,不要 exclude
|
|
1267
|
-
|
|
1268
|
-
插件会自动向 `optimizeDeps` 注入 esbuild 插件:把预构建产物内的 shared 键(vue 等)外部化到运行时协商门面。所以这些依赖**应该正常预构建**——移出预构建反而会让 CJS 文件被裸服务(dev 直接白屏报错)。也不要为它们手工加 `dayjs → dayjs/esm` 之类的别名:那会让构建期 CJS 消费方撞上双重 interop(典型症状 `xxx.default.extend is not a function`)。
|
|
1269
|
-
|
|
1270
|
-
### 4. dev 冷启动后,联邦页面先「预热」再判断
|
|
1271
|
-
|
|
1272
|
-
首次访问会触发依赖再预构建(504 Outdated Optimize Dep / 临时 Failed to fetch)。这是 vite 机制而非故障:把所有页面访问一轮(或重启后重访问一次)即稳定。e2e 脚本请先预热再断言,且断言一律条件轮询,不要固定短等待。
|
|
1273
|
-
|
|
1274
|
-
### 5. 不要手工别名/改写 shared 依赖的导入
|
|
394
|
+
npx fulgurjs init # 创建联邦配置起步文件
|
|
395
|
+
npx fulgurjs explain # 查看当前应用的联邦配置
|
|
1275
396
|
|
|
1276
|
-
|
|
397
|
+
# 使用页面表时,核对宿主页面声明:
|
|
398
|
+
npx fulgurjs check-pages --site http://localhost:5173
|
|
1277
399
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
插件未显式配置 `build.target` 时会自动提升;若你自行配置了 es2021 及以下会收到警告——协商门面的 top-level await 需要它。
|
|
1281
|
-
|
|
1282
|
-
### 7. loadRemote 的显式降级(fallbackModule)
|
|
1283
|
-
|
|
1284
|
-
远程部署不稳定时,可给单次调用声明 fallback 模块:失败时返回 fallback(错误事件/console 仍会发出,**不是静默兜底**);不传则照旧抛错:
|
|
1285
|
-
|
|
1286
|
-
```ts
|
|
1287
|
-
const Panel = await loadRemote('shop/Panel', {
|
|
1288
|
-
retries: 3, // 覆盖 remote.retries
|
|
1289
|
-
fallbackModule: () => import('./PanelFallback.vue'),
|
|
1290
|
-
})
|
|
400
|
+
# 部署到 /remote-vue/ 后,将域名替换为你的实际站点:
|
|
401
|
+
npx fulgurjs doctor --base https://your-site.example --apps remote-vue
|
|
1291
402
|
```
|
|
1292
403
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
登录页预检类接口(如租户按域名解析 `get-by-website`)在部分后端不存在时,浏览器会记录 404/401 资源报错。新站点部署时按后端要求登记域名,或在代理/NGINX 层加**诚实空响应**垫片(不伪造业务数据)。
|
|
1296
|
-
|
|
1297
|
-
### 9. 多版本组件库 CSS 共存
|
|
1298
|
-
|
|
1299
|
-
多版本 element-plus 等组件库 CSS 同挂 `:root` 变量时,**后加载的覆盖先加载的**。当前主流版本变量一致则无感;升级组件库时留意变量默认值变化。
|
|
1300
|
-
|
|
1301
|
-
### 10. 项目里的「env 同步脚本」会回写 env 文件
|
|
404
|
+
`doctor` 的 `--base` 是站点地址,`--apps` 是要检查的部署子目录;上例检查 `/remote-vue/`。它不会从容器名自动猜测另一个开发端口。
|
|
1302
405
|
|
|
1303
|
-
|
|
406
|
+
`init` 只生成联邦配置模板,不替你创建完整应用、路由或 Nginx 配置。`check-pages` 核对页面表与远程模块声明;远程不可达会报告无法验证,不代表通过。
|
|
1304
407
|
|
|
1305
|
-
|
|
408
|
+
开发类型默认开启:插件为远程模块生成类型声明。能访问远程源码时可获得更精确的提示;不能访问时生成 `any` 声明,表示可以导入但没有准确类型。需要关闭时设 `dts: false`。详细规则见 API 手册。
|
|
1306
409
|
|
|
1307
|
-
|
|
410
|
+
高级排查可查看 `window.__FULGURJS_SCOPE__`、`window.__FULGURJS_INFO__`,或设置 `FULGURJS_DEBUG`。普通接入不需要修改这些对象。
|
|
1308
411
|
|
|
1309
|
-
|
|
1310
|
-
[fulgurjs] federation() 配置无效:remotes["remote-a"] 没有地址(external、dev、prod 至少填写一个)
|
|
1311
|
-
当前值:{"dev":""}
|
|
1312
|
-
预期值:至少一个地址;只填一个地址时开发与生产共用
|
|
1313
|
-
修法示例:remotes: { 'remote-a': 'http://localhost:5101' }
|
|
1314
|
-
// 或分别填写:{ 'remote-a': { dev: 'http://localhost:5101', prod: '/remote-a' } }
|
|
1315
|
-
```
|
|
412
|
+
## API 参考
|
|
1316
413
|
|
|
1317
|
-
|
|
414
|
+
不要猜接口,也不要照搬旧任务书中的签名。查参数时使用当前手册:
|
|
1318
415
|
|
|
1319
|
-
|
|
416
|
+
| 你要查什么 | 入口 |
|
|
417
|
+
|---|---|
|
|
418
|
+
| 所有插件配置、remotes/shared 参数与默认值 | [插件选项](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#plugin-options) |
|
|
419
|
+
| loadRemote/loadShare、注册远程、预载、运行时插件 | [运行时 API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#runtime) |
|
|
420
|
+
| Vue/React 组件、页面和 Hooks | [中文 API 手册](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md) |
|
|
421
|
+
| 跨框架挂载、appProps、sessionKey、卸载规则 | [桥接 API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#bridge) |
|
|
422
|
+
| URL 同步、导航取消和部署 base | [路由 API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md#url-sync) |
|
|
423
|
+
| 英文参数说明 | [English API reference](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md) |
|
|
1320
424
|
|
|
1321
|
-
|
|
425
|
+
### 给使用 AI 接入的项目
|
|
1322
426
|
|
|
1323
|
-
|
|
427
|
+
给 AI 明确这几件事:项目用 Vue 还是 React、加载组件还是整个子应用、远程地址、exposes 名称、是否需要登录切换和 URL 同步。
|
|
1324
428
|
|
|
1325
|
-
|
|
1326
|
-
- **桥接的隔离边界(§8.2 如实声明)**:桥接只隔离两棵组件树的挂卸边界,不提供浏览器 realm 隔离——远程全局 CSS、`body`/`html` 样式、全局变量、经 React Portal / Vue Teleport 渲染到容器外的 DOM 仍影响宿主,`unmount` 不承诺撤销浏览器已加载的共享 CSS(样式命名空间与全局副作用清理是接入方责任)。子应用内部错误不冒泡进宿主错误边界(跨 root);子应用路由用 memory 路由,v1 **不与宿主 URL 同步**(刷新不恢复子应用内部路径,不计为深链)
|
|
1327
|
-
- React 侧不承诺组件保活:`createReactHostPages` 不提供 `keepAliveNames`(Vue 的 KeepAlive 专属);页面表里的 `keepAlive` 字段在 React 侧只作普通扩展位。重复打开已下载页面的模块复用照常
|
|
1328
|
-
- 跨源 Fast Refresh(5.2.0 修复):远程 React 组件修改(文本/样式/Hooks 结构不变的兼容改动)自动热更新到正在显示的宿主页面并保留组件本地状态,普通 TS 模块修改自动传播到引用它的组件边界——零手动刷新(插件保证全页单一 react-refresh 实例)。React Refresh 不兼容的导出/Hooks 结构变化、Vite 要求 full-reload 的改动按框架标准重新挂载/整页刷新;不承诺任意改动保活
|
|
1329
|
-
- 不兼容 originjs 的 `virtual:__federation__` 旧写法
|
|
1330
|
-
- 不支持 SSR(检测到即警告并禁用钩子)
|
|
1331
|
-
- 无浏览器 DevTools 扩展(提供 `window.__FULGURJS_SCOPE__ / __FULGURJS_INFO__` 调试面)
|
|
1332
|
-
- 无 JS 沙箱 / CSS 隔离——联邦是同 realm 共存架构,靠 shared 单例协商防止双运行时(详见 `docs/沙箱边界审计.md` 的三维度实测)
|
|
429
|
+
要求它先读使用指南和对应 API 章节,再改代码;沿用现有 Vite 配置,核对实际依赖版本,正确使用浏览器导入入口。参数以当前类型声明为准,不创建文档中不存在的字段。完成后检查真实挂载、交互、失败处理;启用 URL 同步时再检查深链刷新、前进后退和导航取消。
|
|
1333
430
|
|
|
1334
431
|
## 文档
|
|
1335
432
|
|
|
1336
|
-
- [
|
|
1337
|
-
- [
|
|
1338
|
-
- [
|
|
1339
|
-
- [
|
|
1340
|
-
-
|
|
433
|
+
- [完整 Demo 与运行步骤](https://github.com/chenmingye/fulgurjs-federation/blob/master/demo/README.md):基础加载、双向嵌套、URL 同步、版本隔离和 Jeecg 场景。
|
|
434
|
+
- [可复制运行模板](https://github.com/chenmingye/fulgurjs-federation/tree/master/templates):Vue×Vue、React×React、双向跨框架桥接与完整 showcase,五个 pnpm workspace 模板,复制后 `pnpm install && pnpm dev` 即可运行。
|
|
435
|
+
- [迁移指南](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/迁移指南.md):从已有微前端方案接入。
|
|
436
|
+
- [CHANGELOG](https://github.com/chenmingye/fulgurjs-federation/blob/master/CHANGELOG.md):版本变化与迁移说明。
|
|
437
|
+
- [验收报告](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/整夜全量验收报告-20261004.md):真实 MES 业务项目(两个 SVN 项目全新副本)接入验收:dev/生产/故障恢复/HMR(历史记录:该轮曾要求大型应用停用 manualChunks,**5.8.0 起已修复,可保留业务 manualChunks**——共享本体自动隔离进 `fulgurjs-provider-*` 组,不受用户分组影响)。
|
|
438
|
+
- 历史验收:[完整Demo展示与全面复测-20261002](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/完整Demo展示与全面复测-验收报告-20261002.md)。历史结果不能代替当前项目验收。
|
|
1341
439
|
|
|
1342
440
|
## 开发与测试
|
|
1343
441
|
|
|
442
|
+
以下是**开发本插件仓库**的命令,不是使用者接入项目必须运行的步骤:
|
|
443
|
+
|
|
1344
444
|
```bash
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
pnpm
|
|
1348
|
-
for app in fixtures/host-vue fixtures/remote-a fixtures/remote-b fixtures/remote-auto fixtures/host-auto fixtures/remote-react fixtures/host-react e2e; do pnpm --dir "$app" install; done
|
|
1349
|
-
pnpm --dir e2e exec playwright install chromium
|
|
1350
|
-
|
|
1351
|
-
pnpm test:unit # 全量单测(数量以本次输出为准)
|
|
1352
|
-
pnpm test:dev # Vue + React 的 dev 与 fault 四个项目
|
|
1353
|
-
pnpm test:prod # Vue + React 的 prod 两个项目;需 NGINX,结束后清理脚本启动的隔离实例
|
|
1354
|
-
pnpm test # unit + dev + prod 全跑
|
|
1355
|
-
pnpm --dir e2e exec playwright test --list # 核对用例归属,数量以本次输出为准
|
|
1356
|
-
node e2e/scripts/pack-smoke.mjs # 本地 tarball 的隔离消费者检查,不替代发版后正式包验收
|
|
445
|
+
pnpm --dir packages/plugin install
|
|
446
|
+
pnpm --dir packages/plugin build
|
|
447
|
+
pnpm test:unit
|
|
1357
448
|
```
|
|
1358
449
|
|
|
1359
|
-
CI
|
|
1360
|
-
|
|
1361
|
-
- `test`:单测 + 双口径 typecheck(pinned / latest)+ build 门禁(runtime gzip ≤ 9216B、错误码三方一致性);
|
|
1362
|
-
- `e2e`:Vue + React 的 dev/fault × Vite 6.4.3 / 7.3.6 / 8.3.0 兼容矩阵;
|
|
1363
|
-
- `prod-e2e`:隔离 NGINX 下的 Vue + React 生产套件;`tarball`:真实打包消费者检查;
|
|
1364
|
-
- `vite5`:schedule/workflow_dispatch 运行最低支持线(Vite 5.1.4 全量 dev/fault/react 套件;5.2.0 起双 client 错误覆盖层缺陷已修复,无 skip 项)。
|
|
1365
|
-
|
|
1366
|
-
fixtures 测试与真实项目验收分别记录;fixture 全过不代表 MES 双环境已完成验收。
|
|
450
|
+
完整 fixtures 安装与浏览器测试准备见 [贡献指南](https://github.com/chenmingye/fulgurjs-federation/blob/master/CONTRIBUTING.md)。CI 检查构建、类型、单测、实际安装包,以及多个 Vite 版本的浏览器场景;通过数量以对应运行记录为准。
|
|
1367
451
|
|
|
1368
452
|
## License
|
|
1369
453
|
|
|
1370
454
|
[MIT](./LICENSE) © chenmingye (Jason)
|
|
1371
|
-
|
|
1372
|
-
### 远程源码不可访问时的开发类型
|
|
1373
|
-
|
|
1374
|
-
远程设置 `devFsRoot: false`,或远程源码目录在宿主机器上不可访问时,插件根据开发 manifest 为每个公开暴露模块生成 `any` 声明。默认导入、具名导入和副作用导入均可解析,但没有源码补全、类型约束或源码跳转;内部 setup 生命周期入口不生成声明。生成目录遵循 `dts.dir`;默认有 `src` 时为 `src/fulgurjs/types`,否则为 `.fulgurjs/types`。确保项目 tsconfig 包含该目录。恢复源码直连后重启宿主开发服务即可重新生成精确映射;`dts: false` 会完全关闭生成。
|
|
1375
|
-
|
|
1376
|
-
开发类型生成与运行时使用同一份 `remotes.dev` 地址:绝对 URL、`//host:port/path` 和同源相对路径均支持。相对地址以宿主 Vite 开发服务的 origin 解析;显式 `server.origin` 优先,其次实际本地服务地址。
|