@fulgurjs/federation 5.0.4 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/DESIGN.md +11 -0
  3. package/README.en.md +441 -0
  4. package/README.md +116 -3
  5. package/dist/chunk-4PLNAYZX.js +84 -0
  6. package/dist/cli.js +4 -1
  7. package/dist/host-pages-core-BFxp-IOj.d.cts +37 -0
  8. package/dist/host-pages-core-DLfMeDrO.d.ts +37 -0
  9. package/dist/index.cjs +132 -22
  10. package/dist/index.js +132 -22
  11. package/dist/react-adapter.cjs +518 -0
  12. package/dist/react-adapter.d.cts +117 -0
  13. package/dist/react-adapter.d.ts +117 -0
  14. package/dist/react-adapter.js +292 -0
  15. package/dist/react.d.ts +442 -0
  16. package/dist/react.js +10 -0
  17. package/dist/runtime-entry.d.ts +29 -17
  18. package/dist/runtime.js +13 -13
  19. package/dist/vue-adapter.cjs +66 -50
  20. package/dist/vue-adapter.d.cts +4 -25
  21. package/dist/vue-adapter.d.ts +4 -25
  22. package/dist/vue-adapter.js +14 -66
  23. package/examples/README.md +5 -0
  24. package/examples/react-host/README.md +28 -0
  25. package/examples/react-host/fulgurjs.config.ts +19 -0
  26. package/examples/react-host/index.html +11 -0
  27. package/examples/react-host/package.json +23 -0
  28. package/examples/react-host/src/App.tsx +66 -0
  29. package/examples/react-host/src/federation/pages.data.ts +19 -0
  30. package/examples/react-host/src/main.tsx +8 -0
  31. package/examples/react-host/tsconfig.json +14 -0
  32. package/examples/react-host/vite.config.ts +8 -0
  33. package/examples/react-remote/fulgurjs.config.ts +17 -0
  34. package/examples/react-remote/index.html +11 -0
  35. package/examples/react-remote/package.json +22 -0
  36. package/examples/react-remote/src/Button.tsx +15 -0
  37. package/examples/react-remote/src/main.tsx +6 -0
  38. package/examples/react-remote/src/pages/Detail.tsx +10 -0
  39. package/examples/react-remote/src/pages/Home.tsx +14 -0
  40. package/examples/react-remote/src/utils.ts +3 -0
  41. package/examples/react-remote/tsconfig.json +14 -0
  42. package/examples/react-remote/vite.config.ts +8 -0
  43. package/package.json +30 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.0(2026-09-28)
4
+
5
+ - **React 完整支持(浏览器客户端)**:新增 `@fulgurjs/federation/react` 入口——`remoteComponent`(pending/错误占位与错误边界内置、timeout 适配层超时、不用 React.lazy 的失败缓存陷阱)、`useLoadRemote`(代次守卫的模块 hook:StrictMode 双 effect/快速切换/慢请求晚返回/卸载后返回只允许最新有效请求写状态)、`RemoteErrorBoundary`(页面级兜底 + resetKeys)、`createReactHostPages`(与 Vue 共用同一份页面表数据与 definePages R1–R5 校验;不提供 keepAliveNames)。peer 新增可选 `react`/`react-dom`(`>=18 <20`);纯 React 项目零 Vue 依赖、纯 Vue 项目零 React 依赖(静态导入图与 tarball 消费双向守护)。共享 `react`/`react-dom` singleton:dev 期预构建外部化自动改道 jsx-runtime/jsx-dev-runtime 内部引用,prod 期 CJS require 垫片覆盖 `react-dom/client` 子路径;Hooks/StrictMode/Context 跨端单实例经真实浏览器 e2e 验证(React 19.3;18 隔离验证见验收报告)。
6
+ - **修复:失败恢复穿透浏览器 ESM 失败缓存**(Vue/React 通用)——同 URL 的失败 `import()` 会被浏览器 module map 缓存为失败(重试零网络请求)。运行时入口(`entryFailCounts` + `fulgurjs_retry=N` query)、dev 容器 expose loader(字面量主路径保持 vite URL 规范化一致 + `@vite-ignore` 重试分支)、prod remoteEntry 产物(`__fgR` 包装)三处统一实现「失败后的重试变更 URL」;服务恢复后点击重试真实重新拉取(此前仅整页刷新可恢复)。
7
+ - **修复:`react-adapter` 产物内联 react 的隐患**——peerDependencies 曾漏列 react/react-dom 导致 tsup 未外置;本版 peer 完整(此前版本无 React 入口,无实际影响面)。
8
+ - **开发类型双轨(Vue/React 通用)**:此前 ambient declare module 内的相对 re-export 是 TS2439 非法声明,被用户工程常规 skipLibCheck 静默吞成 any。现零配置生成合法带体宽松声明(可解析);新增精确轨 `<types>/<remote>.d/` 目录转发模块,宿主 tsconfig 配一段 `"paths": { "<remote>/*": ["<types目录>/<remote>.d/*"] }` 即获得源码级类型(错误 props/参数编译失败);配置了 paths 的远程自动跳过同名宽松声明避免遮蔽。
9
+ - **完整英文 README(README.en.md)**:与中文 README 同源的当前用法全量文档(含 React API/41 错误码/边界/懒加载口径),随 npm 包发布;npm 默认 README 仍为中文,两份顶部互链。
10
+ - 内部:vue-adapter 纯页面解析提取为共用的 host-pages-core.ts(Vue 行为与既有断言保持);新增 fixtures/host-react + fixtures/remote-react、examples/react-host + examples/react-remote、React dev/fault/prod e2e 套件与 CI 矩阵接线;prod-setup 隔离 NGINX 增加 /host-react 与 /remote-react 子路径。
11
+
3
12
  ## 5.0.4(2026-09-28)
4
13
 
5
14
  - **修复开发类型生成的地址解析**:按宿主开发服务的 origin(包含协议)解析协议相对与同源相对的 remote dev 地址,避免页面可加载却因 `new URL()` 缺基址而跳过类型生成。新增真实 HTTP manifest 回归测试;5.0.3 的 any 降级声明修复保留。
package/DESIGN.md CHANGED
@@ -159,6 +159,17 @@ remote 样式改动立即生效;host 自身业务 HMR 不受影响。
159
159
  - **dev 与 prod(NGINX)两套截图各自备齐**,手册按环境归档引用
160
160
  - 手动冒烟(如后台接口连通、NGINX reload)同样截图留证
161
161
 
162
+ ## 6.1 双框架浏览器入口(5.1.0 React 支持)
163
+
164
+ - 构建期入口 `@fulgurjs/federation`(dist/index.js)不变。
165
+ - Vue 浏览器入口 `@fulgurjs/federation/runtime` → dist/runtime-entry.js → runtime.js(框架无关内核)+ context/pages + vue-adapter。
166
+ - React 浏览器入口 `@fulgurjs/federation/react` → dist/react.js(gen-runtime-entry.mjs 生成薄壳)→ 同一 runtime.js + context/pages + react-adapter(类型面 dist/react.d.ts 由 tsup 从 src/react.ts 生成;双入口独立 tsup 构建,防止 dts 共享 chunk)。
167
+ - 纯解析核心 `host-pages-core.ts` 自 vue-adapter 提取共用(definePages 接入/最长前缀/base/参数匹配/会话代次判定);Vue 保留组件命名缓存与 KeepAlive 语义,React 侧组件缓存仅按「新非空 sessionKey」重建且不提供 keepAliveNames。
168
+ - 开发态:`transform.rewriteRuntimeEntryImports` 同时识别 /runtime 与 /react(remoteSchema 拆分共用,expose 目标分别指向 `virtual:fulgurjs-api-facade` / `virtual:fulgurjs-api-facade-react`);`virtual.genApiFacade(framework)` 分别接两套适配器;纯 React 静态图无 Vue、纯 Vue 图无 React(runtime-entry-graph.test 双向守护,tarball smoke 以独立 React consumer 复核)。
169
+ - 共享子路径:`react`/`react-dom` singleton 协商 + dev 期 optimize-shared-external 预构建外部化(jsx-runtime 内部 require 改道门面)+ prod 期 cjsRequireRewrite;peer 声明 `react >=18 <20`(optional)——peer 缺失会导致 tsup 不外置、react 内联进适配器产物(双实例风险,实测教训)。
170
+ - 失败恢复:runtime `importEntry`(entryFailCounts)+ dev 容器 loader(字面量主路径 + `@vite-ignore` 重试分支;字面量保证与 vite importAnalysis 重写形态一致,拼接表达式会经 injectQuery 产生 `?import` 变体 URL 与内部静态 import 形成双模块实例)+ prod remoteEntry 产物后处理(`__fgR` 包装)三处统一「失败后重试变更 URL(fulgurjs_retry=N)穿透浏览器 module map 失败缓存」。
171
+ - 开发类型双轨:零配置 ambient(带体 any,可解析;简写 ambient 会 shadow paths 命中的转发文件,TS2439/TS2709 语言限制见 dts.ts 注释)+ 精确轨 `remote.d/` 目录 .ts 转发模块(export *)配 tsconfig paths;宿主配了 paths 的远程自动跳过同名 ambient。
172
+
162
173
  ## 7. 文档交付物
163
174
 
164
175
  **唯一权威文档 = 仓库根 `README.md`**(随 npm 包发布,GitHub 与 npm 双端可读);迁移路径见 `docs/迁移指南.md`。内容组织:
package/README.en.md ADDED
@@ -0,0 +1,441 @@
1
+ # @fulgurjs/federation
2
+
3
+ [简体中文](./README.md) | English
4
+
5
+ > **fulgurjs** — Latin for "lightning · flash of light".
6
+ > A Vite plugin that makes Module Federation work out of the box: **one config shape, dev & prod engines, semantics aligned with webpack Module Federation**, with first-class browser support for both Vue 3 and React 18/19.
7
+
8
+ ![tests](https://img.shields.io/badge/tests-436%20%2B%20e2e-green) ![runtime](https://img.shields.io/badge/runtime%20gzip-%3C%209KB-blue) ![vite](https://img.shields.io/badge/vite-5%20%7C%206%20%7C%207%20%7C%208-purple)
9
+
10
+ ---
11
+
12
+ ## Why
13
+
14
+ | | webpack MF | other vite MF solutions | **@fulgurjs/federation** |
15
+ |---|---|---|---|
16
+ | dev experience | separate builds | manual bootstrap usually required | ✅ dual dev-server direct wiring, zero manual async boundaries |
17
+ | prod artifacts | ✅ | often missing or degraded | ✅ build-time rewriting, stable remoteEntry filename + manifest |
18
+ | semantic parity | 100% | incomplete (version negotiation / singleton / fault tolerance often missing) | ✅ aligned clause-by-clause with webpack semantics, e2e-verified |
19
+ | **UMD / CJS-only deps** | DIY | **commonly unusable** | ✅ automatic (dep-optimizer externalization + build-time require shims) |
20
+ | remote load failures | raw errors | usually missing | ✅ retry / circuit breaker / timeout built in + explicit `fallbackModule` degradation |
21
+ | runtime size | ~40KB+ | varies | **gzip < 9KB** |
22
+ | misconfiguration | hard to debug | cryptic | three-part diagnostics: `got / expected / example` |
23
+
24
+ ## Features
25
+
26
+ - **Full exposes / remotes / shared semantics** — `name@url` syntax, key renaming, promise-based remotes, full-semver `requiredVersion`, version negotiation (highest wins), singleton / strictVersion, loaded versions are never replaced, multi-version coexistence, `shareKey` redirection, multiple share scopes
27
+ - **UMD / CJS-only deps out of the box** — element-plus, avue and other UMD/CJS-only packages simply go into `optimizeDeps.include`; in dev the plugin automatically re-routes shared keys inside pre-bundled output to negotiation facades, in build CJS `require(<shared>)` is redirected to shims — dual-runtime immune
28
+ - **Automatic async boundaries** — top-level await injected automatically (es2022+), no webpack-style manual `import('./bootstrap')`
29
+ - **Stable artifacts** — remoteEntry keeps a fixed filename for stable referencing (its content changes every build, **it must be `no-cache`** — only content-hashed chunks may be long-cached); `fulgurjs-manifest.json` asset manifest; one chunk per expose
30
+ - **Fault tolerance (aligned with webpack MF 2.0 errorLoadRemote)** — load retry / circuit breaker / timeout built in; `loadRemote(spec, { retries, fallbackModule })` per-call overrides — on failure the fallback module is returned and the error event is still emitted explicitly (**never silent**; without `fallbackModule` the error is re-thrown)
31
+ - **Failure recovery really penetrates the browser ESM failure cache** — a failed `import()` of the same URL is cached as failed by the browser module map and would never hit the network again; the runtime varies the URL on retries after failure (`fulgurjs_retry=N`) for both remote entries and exposed chunks, so "service recovered → click retry" genuinely re-fetches
32
+ - **Enhancements** — dev type generation (dts), manifest-driven `preloadRemote()`, runtimePlugin hooks
33
+ - **Full HMR chain** — remote edits propagate to the host page: component hot swap, state retention, error overlay and recovery
34
+ - **Zero-silent-failure discipline** — config problems fail at startup with three-part diagnostics; federation failures throw explicitly (error code + actionable fix); **no silent fallback paths**
35
+ - **CLI (bin in the main package)** — `fulgurjs init` (single-project `fulgurjs.config.ts` starter, never rewrites project files), `fulgurjs explain` (interprets the effective federation shape and load chain), `fulgurjs check-pages` (page-table ↔ remote manifest contract check; `--manifest` / `--site` sources, `--require-verified` for CI), `fulgurjs doctor` (deployment health check: remoteEntry/manifest/HTML cache headers, CORS, chunk sampling, version-skew rehearsal)
36
+ - **Optional remote init lifecycle** — `federation({ setup })`: default-exported `setup(context)` runs once per app; optional named `onSession(context)` runs once per host `sessionKey` (login generation) — re-login re-runs, logout via `clearAppContext` invalidates session state; failures are explicit and retryable (`MFU-011~014`); `preloadRemote`/`getContainer` are side-effect free
37
+ - **Optional host page adapter** — one page table shared by routing and layout: URL resolution (incl. base stripping), longest-prefix remote attribution, `definePages` R1–R5 validation, async component cache (rebuilt on login-generation change), skeleton/error placeholders, keep-alive names (Vue)
38
+ - **Cross-app values & function references** — `provideAppContext` / `getAppContext` / `requireAppContext` / `clearAppContext` on the runtime entry; page-level singleton snapshot + function references (not reactive — "live" data via function references / host pinia sharing; same-page account switching is handled by `onSession`, no page reload)
39
+ - **Vue direct rendering** — `remoteComponent('remote/X')` on `@fulgurjs/federation/runtime`: `defineAsyncComponent + loadRemote` standard wrapper with explicit error placeholder (code + cause + fix); runtime.js has zero framework dependencies
40
+ - **Full React support (browser)** — dedicated `@fulgurjs/federation/react` entry: `remoteComponent` (Suspense/error placeholders + retry, avoiding the `React.lazy` failed-promise-cache trap), `useLoadRemote` (generation-guarded module hook), `RemoteErrorBoundary`, `createReactHostPages` (same page-table source and R1–R5 validation as Vue); shared `react` / `react-dom` singleton negotiation with Hooks / StrictMode / Context verified single-instance across host and remote (dev pre-bundle externalization + prod CJS shims automatically handle `react/jsx-runtime` and `react-dom/client` subpaths); pure-React projects need zero Vue, pure-Vue projects need zero React
41
+ - **CSP friendly** — native ESM loading with no `eval` / `new Function` anywhere; runs under strict CSP (no `unsafe-eval`)
42
+ - **End-to-end error-code system (41 codes)** — CFG/DEV/BLD/MFU/CC segments + drift-checked code table (§6)
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pnpm add -D @fulgurjs/federation
48
+ ```
49
+
50
+ Requirements: Vite ≥ 5.1 (tested through 8.x), Node ≥ 18, Vue 3 and/or React 18–19 (both optional peers — install per the framework you use), Chrome 108+ (native TLA).
51
+
52
+ The browser runtime entries are ESM-only: `@fulgurjs/federation/runtime` (Vue apps) and `@fulgurjs/federation/react` (React apps). They do not support `require()`; the build-time main entry works in both ESM and CJS.
53
+
54
+ ## Quick start (React)
55
+
56
+ Host and remote use the same "two files per app" config shape; the only difference for React is the browser import point: `@fulgurjs/federation/react`.
57
+
58
+ Remote (`fulgurjs.config.ts` — the default export is the federation options object itself):
59
+
60
+ ```ts
61
+ import type { FederationOptions } from '@fulgurjs/federation'
62
+
63
+ export default {
64
+ name: 'remote-react',
65
+ exposes: {
66
+ './Button': './src/Button.tsx',
67
+ './utils': './src/utils.ts',
68
+ './pages/home': './src/pages/Home.tsx',
69
+ },
70
+ shared: {
71
+ react: { singleton: true },
72
+ 'react-dom': { singleton: true },
73
+ },
74
+ } satisfies FederationOptions
75
+ ```
76
+
77
+ ```ts
78
+ // vite.config.ts — the React plugin stays first; federation is one line
79
+ import { defineConfig } from 'vite'
80
+ import react from '@vitejs/plugin-react'
81
+ import federation from '@fulgurjs/federation'
82
+ import fulgurjsConfig from './fulgurjs.config'
83
+
84
+ export default defineConfig({ plugins: [react(), federation(fulgurjsConfig)] })
85
+ ```
86
+
87
+ Host consumption:
88
+
89
+ ```tsx
90
+ import { remoteComponent, useLoadRemote, createReactHostPages, remoteSchema } from '@fulgurjs/federation/react'
91
+ import { pages, remotePrefixes } from './src/federation/pages.data' // pure data module (CLI + browser)
92
+
93
+ // ① Component: create the factory at module top level (never inside render); loads on first render
94
+ const RemoteButton = remoteComponent<{ label: string; onClick?: () => void }>('remote-react/Button', {
95
+ fallback: <p>Loading remote button…</p>,
96
+ })
97
+
98
+ // ② Plain module: useLoadRemote (data/error/loading/reload)
99
+ type Utils = { formatMoney(v: number, currency?: string): string }
100
+
101
+ // ③ Page table: same verb as Vue — component(spec); render from your router
102
+ const hp = createReactHostPages({ pages, remotePrefixes, schema: remoteSchema })
103
+ const RemoteHome = hp.component('remote-react/pages/home')
104
+ ```
105
+
106
+ Full runnable projects: [`examples/react-host`](./examples/react-host) and [`examples/react-remote`](./examples/react-remote). Exact semantics (timeout / retry / StrictMode / Context / error recovery) in [§8.1 React adapter API](#81-react-adapter-api--fulgurjsfederationreact).
107
+
108
+ ## Quick start (Vue): three integration paths
109
+
110
+ ### Path ①: expose and load plain modules (no setup, no bridge, no page table)
111
+
112
+ ```ts
113
+ // remote-a/vite.config.ts —— 最小远程:只要 name + exposes
114
+ import { defineConfig } from 'vite'
115
+ import vue from '@vitejs/plugin-vue'
116
+ import { federation } from '@fulgurjs/federation'
117
+
118
+ export default defineConfig({
119
+ plugins: [
120
+ vue(),
121
+ federation({
122
+ name: 'remote-a',
123
+ exposes: {
124
+ './utils': './src/utils.ts', // 普通模块
125
+ './Button': './src/Button.vue', // 组件
126
+ },
127
+ }),
128
+ ],
129
+ })
130
+ ```
131
+
132
+ ```ts
133
+ // host/src/main.ts —— 宿主按需加载(spec = '<remote>/<expose 键去 ./ >')
134
+ import { loadRemote } from '@fulgurjs/federation/runtime'
135
+
136
+ const utils = await loadRemote<{ formatMoney(v: number): string }>('remote-a/utils')
137
+ ```
138
+
139
+ ### Path ②: host multi-page integration (`createHostPages`)
140
+
141
+ ```ts
142
+ import { createHostPages, remoteSchema } from '@fulgurjs/federation/runtime'
143
+ import { pages, remotePrefixes } from './federation/pages.data' // 宿主路由与布局共用的唯一数据源
144
+
145
+ export const hostPages = createHostPages({
146
+ pages, // [{ route: '/remote-a/home', name: 'Home' }, ...]
147
+ remotePrefixes, // { '/remote-a': 'remote-a' }
148
+ schema: remoteSchema, // dev 期自动校验 spec 存在性(R3)
149
+ })
150
+ // hostPages.component('remote-a/home') → 异步组件(缓存/占位/错误处理内置)
151
+ // hostPages.keepAliveNames → KeepAlive include 白名单
152
+ ```
153
+
154
+ ### Path ③: remote pages need host environment (`setup` + `AppContext` + session switching)
155
+
156
+ ```ts
157
+ // remote: federation({ setup: './src/fulgurjs/setup.ts' })
158
+ export default async function setup(ctx: { appContext: Record<string, any>; signal: AbortSignal }) {
159
+ // 应用级执行一次:初始化远程自身的 store/实例,消费宿主 context
160
+ }
161
+ export async function onSession(ctx: { appContext: any; sessionKey: string; signal: AbortSignal }) {
162
+ // 会话级:按宿主 sessionKey 去重——换账号/重登自动重跑
163
+ }
164
+ ```
165
+
166
+ ```ts
167
+ // host bridge: 登录成功后 provide;登出时 clearAppContext()
168
+ import { provideAppContext, clearAppContext } from '@fulgurjs/federation/runtime'
169
+ provideAppContext({ user, getToken, store, hostApp, locale, sessionKey, events: { main: mainEvents } })
170
+ ```
171
+
172
+ ### Common boundaries (all three paths)
173
+
174
+ - `spec` = `<remote-name>/<expose-key without './'>`; remote names come from `remotes` keys (or `name@url` self-reported names)
175
+ - shared deps must be declared on **both** sides for negotiation; UMD/CJS-only deps go into `optimizeDeps.include`
176
+ - build target must be es2022+ (TLA)
177
+
178
+ ## CLI
179
+
180
+ `fulgurjs init` / `fulgurjs explain` / `fulgurjs check-pages` / `fulgurjs doctor` — see [§5 CLI command reference](#5-cli-command-reference). The config file `fulgurjs.config.ts` is one per app root; `vite.config.ts` calls `federation(fulgurjsConfig)` once. The page-data module must stay pure data (no React/Vue/router/browser imports) so the CLI can evaluate it standalone.
181
+
182
+ ## API reference
183
+
184
+ ### 1. `federation(options)` — the Vite plugin (host & remote, same API)
185
+
186
+ ```ts
187
+ import federation from '@fulgurjs/federation'
188
+
189
+ federation({
190
+ name: 'my-app', // required, unique per page
191
+ exposes: { './Button': './src/Button.vue' },
192
+ remotes: {
193
+ 'remote-a': { dev: 'http://localhost:5101', prod: '/remote-a' },
194
+ shop: 'remote-a@http://localhost:5101', // webpack syntax + rename
195
+ 'promise-remote': () => Promise.resolve(container),
196
+ },
197
+ shared: {
198
+ vue: { singleton: true, requiredVersion: '^3.4.0' },
199
+ pinia: { singleton: true, eager: true },
200
+ 'lodash-es': { shareKey: 'lodash' },
201
+ },
202
+ setup: './src/fulgurjs/setup.ts', // optional lifecycle entry
203
+ shareScope: 'default',
204
+ filename: 'fulgurjs-remoteEntry.js',
205
+ manifest: true,
206
+ dts: true, // or { dir, mode: 'source' | 'shim' } or false
207
+ devSharedSelf: undefined, // default inferred by role (see below)
208
+ devCorsOrigins: '*', // or ['http://localhost:5100', ...]
209
+ devFsRoot: true, // false → host dts degrades to any stubs
210
+ runtimePlugins: [],
211
+ })
212
+ ```
213
+
214
+ Key semantics (full tables in the Chinese README §1, same source of truth):
215
+
216
+ - `remotes`: `dev` / `prod` split, `external`, `timeout` (default 15s — ends the caller's wait, never cancels the issued import), `retries` (0–10, default 2), `fallback` entry list, `breaker: { threshold, resetMs }`, promise-based remotes
217
+ - `shared`: full `SharedHint` (`import: false` pure consumer, `shareKey`, `shareScope`, `strictVersion` default follows webpack, `eager`, `version`); version read from the installed package; negotiation = highest version satisfying `requiredVersion`; loaded versions never replaced; singleton warnings (`MFU-010`) fire only when the reused instance does not satisfy the consumer's requirement, once per combination
218
+ - `devSharedSelf`: whether the app's own dev source participates in shared rewriting — default `remotes.length === 0 || exposes.length > 0` (pure remotes and dual-role apps participate, pure hosts don't)
219
+ - removed-in-5.0.0 options (`remoteType`, `library`, `automaticAsyncBoundary`, `dataPrefetch`, `usedExports`, `ignoreUnusedSharedExports`) fail with `CFG-011` and migration hints; the old aggregate config chain (`root` + `apps[]`, `/config` entry, `--app`) is gone
220
+
221
+ ### 2. Runtime API — `@fulgurjs/federation/runtime` (Vue apps)
222
+
223
+ Framework-neutral core re-exported through a Vue-shaped entry:
224
+
225
+ | Export | Semantics |
226
+ |---|---|
227
+ | `loadRemote<T>(spec, opts?)` | load a remote module; `opts: { shareScope, retries, fallbackModule }`; goes through container negotiation and the optional setup/onSession lifecycle; module results are cached per `remote@scope#module`, failures are uncached and retryable |
228
+ | `loadShare(name, opts?)` | shared-dependency negotiation: `requiredVersion` / `singleton` / `strictVersion` / `shareKey` / `shareScope` / `fallback` |
229
+ | `registerRemotes([...])` / `registerRemote(r)` | runtime registration (`name`, `entry`, `timeout`, `retries`, `fallback`, `breaker`, promise-based) |
230
+ | `preloadRemote(spec, { mode })` | manifest-driven preload of entry + expose chunks + CSS; `mode: 'preload' | 'prefetch'`; no lifecycle side effects |
231
+ | `getContainer(name)` | acquire the initialized container |
232
+ | `getRuntime()` / `shareScopeMap` / `parseSpec` / `unwrapDefault` / `version` | runtime singleton, live scope map, spec parsing, default-interop helper |
233
+ | `provideAppContext` / `getAppContext` / `requireAppContext` / `clearAppContext` | cross-app context (see §9) |
234
+ | `definePages` / `validatePages` | page-table validation R1–R5 (see §3) |
235
+ | `remoteComponent` / `createHostPages` | Vue adapters (see §8 / §10) |
236
+ | `remoteSchema` | dev-only expose inventory (empty object at build/Node time) |
237
+
238
+ ### 8.1 React adapter API — `@fulgurjs/federation/react`
239
+
240
+ The single import point for React browser apps: re-exports the common runtime API (`loadRemote`, `preloadRemote`, `provideAppContext`, `definePages`, `remoteSchema`, …) plus the React adapters below. It does **not** include Vue's `createHostPages`, Vue `RemoteComponentOptions` or `keepAliveNames`.
241
+
242
+ #### `remoteComponent<Props>(spec, options?)`
243
+
244
+ Returns a renderable React component type (`Props` constrains JSX usage — a compile-time contract, not runtime validation). Factory and page-table creation have **zero load side effects**; loading starts on first render via `loadRemote` (through container negotiation and the optional setup/onSession). Pending placeholder, error placeholder and an error boundary are built in — no hand-written Suspense / `React.lazy` needed. **It deliberately avoids `React.lazy`**: a lazy instance caches its failed promise, and resetting an error boundary alone cannot recover; this implementation's retry rebuilds the load attempt (already-succeeded modules are not re-downloaded through the runtime cache).
245
+
246
+ | Option | Type & default | Semantics |
247
+ |---|---|---|
248
+ | `fallback` | `ReactNode`, default `null` | placeholder while this load is pending (distinct from the failure placeholder) |
249
+ | `error` | `ReactNode` or `(error, retry) => ReactNode`, default built-in Chinese placeholder | shown on load failure **or** subtree render error; the function receives the real error and a working retry |
250
+ | `retries` | `number`, follows the `loadRemote` default (2) | passthrough retry count (integer 0–10; invalid values throw at factory call) |
251
+ | `timeout` | `number` (ms), default none | wait cap for this component load; ending the wait does **not** cancel the issued shared request; late results never overwrite the settled state and never produce unhandled rejections |
252
+
253
+ - Component-export validation: the default export (or the module itself) must be a function component / class / `memo` / `forwardRef`; strings, numbers and empty namespaces fail explicitly (never a blank success page)
254
+ - `ref` passthrough works for `forwardRef` exports (verified on React 18/19); refs to plain function components follow standard React behavior
255
+ - Render-time exceptions are caught by the built-in boundary and reported **separately** from network/export errors ("加载失败" vs "渲染出错"); the boundary does not catch event-handler or arbitrary async-callback errors — those follow React's own semantics
256
+ - The built-in error placeholder contains: the error code (`code` from FgError; `UNKNOWN` for render errors without one), the real cause message, an actionable fix and a retry button
257
+ - Failure recovery genuinely penetrates the browser ESM failure cache (see Features)
258
+
259
+ #### `useLoadRemote<Module>(spec, options?)`
260
+
261
+ ```ts
262
+ const { data, error, loading, reload } = useLoadRemote<Utils>('remote-react/utils')
263
+ ```
264
+
265
+ - Returns `{ data: Module | undefined, error: unknown, loading: boolean, reload: () => Promise<void> }`; `error` is always `undefined` when there is no error
266
+ - `options`: `shareScope` / `retries` / `fallbackModule` (passthrough to `loadRemote`; configuring `fallbackModule` is explicit behavior — failures return the fallback value instead of writing `error`)
267
+ - Dependency comparison is per-field (callers creating a fresh options object per render do not trigger reload loops); spec/option changes clear stale data and start a new request
268
+ - Each effect run and each `reload` carries its own generation: fast A→B switching, late slow responses, consecutive reloads, returns after unmount and StrictMode double effects can only ever write from the latest valid request; duplicate effects do happen (StrictMode) — the runtime cache dedupes network and lifecycle work
269
+ - `reload` re-runs the lifecycle and failure retry but never re-downloads an already-cached successful module; it resolves normally as `Promise<void>` (button `onClick` calls produce no unhandled rejections)
270
+ - `AppContext` is not a React subscription: when the host reads a new non-empty `sessionKey`, the **host's own state/routing** must trigger the re-render (`createReactHostPages` rebuilds its component cache on new login generations, which triggers the new `onSession`)
271
+
272
+ #### `RemoteErrorBoundary`
273
+
274
+ A standalone page-level boundary. Props: `children`, `fallback` (node or `({ error, reset }) => ReactNode`), `onError(error, info)`, `resetKeys` (boundary resets when any entry changes; the usual controlled form is `resetKeys={[retryEpoch]}`). `reset` only resets boundary state; if the subtree holds a failed cache (e.g. an external `React.lazy`) the caller must also rebuild the load attempt — the built-in `remoteComponent` retry already does both. The boundary built into `remoteComponent` consumes its own errors, so an outer `RemoteErrorBoundary` never sees them; to customize one remote component's placeholder use that component's `error` option.
275
+
276
+ #### `createReactHostPages(options)`
277
+
278
+ Shares the same page-table data and `definePages` R1–R5 validation with Vue (the pure parsing core is shared since 5.1.0); returns `{ pages, resolve(path), component(spec) }` — `component(spec)` returns a React component type; render it from your router (JSX / `createElement`; no `.element()` synonym).
279
+
280
+ - Data options: `pages / remotePrefixes / deriveSpec / schema / strict / base` (identical semantics to Vue); display options: `fallback / error / retries / timeout` (same semantics as `remoteComponent`) plus `beforeLoad` (runs before every actual load attempt, including retries, for the host to refresh context; never runs at table creation)
281
+ - `resolve` keeps base stripping, longest-prefix attribution, param decoding (a bad `%` sequence only fails that match), query/hash handling, `null` on no match
282
+ - The component cache is keyed by spec + login generation; **only a new non-empty `sessionKey` rebuilds** (logout → `undefined` does not — same semantics as Vue); after account switching the rebuilt loads trigger the new-generation `onSession`
283
+ - No `keepAliveNames` on the React side (component keep-alive is not promised; the `keepAlive` page field is a plain extension slot); routing is not a runtime dependency — the examples use React Router 7 (`path` declared in the route table, `element` renders `component(spec)`; params reach remote pages as props via `useParams` / `useSearchParams`)
284
+ - Cross-framework Context sharing: host and remote consumers get the **same Context object** through the **same expose instance** (e.g. the remote exposes `./theme-context` exporting a `createContext` instance; the host obtains it via `useLoadRemote` and renders the Provider; the remote component's `useContext` reads the host value). The plugin does not auto-bridge arbitrary React Contexts — the object must be explicitly shared
285
+
286
+ #### React dev types
287
+
288
+ `.tsx`/`.ts` exposes share the same dev type generation as Vue (directories, `dts:false`, `dts.dir`, setup filtering, `devFsRoot:false` degradation), with a **dual-track** addition: zero-config generates resolvable loose declarations (exports typed `any`); after adding `"paths": { "<remote>/*": ["<typesDir>/<remote>.d/*"] }` to any host `tsconfig*.json`, the same imports resolve through forwarder modules to **source-level types** (precise props/signatures; wrong props/arguments fail compilation) — remotes covered by a paths mapping automatically skip their loose declaration to avoid shadowing; see the `_paths.d.ts` note inside the generated directory.
289
+
290
+ ### 3. `definePages` — host page-table validation
291
+
292
+ `validatePages(pages, options)` returns violations; `definePages` aggregates and throws on ERROR level (or `console.error` with `strict: false`):
293
+
294
+ - **R1 [ERROR]** a param route's derived spec (prefix + `:param` segments stripped) collides with another entry's effective spec — would silently load the wrong component
295
+ - **R2 [WARN]** duplicate effective specs (deliberate menu aliases allowed, flagged for awareness)
296
+ - **R3 [ERROR]** spec not in the remote's expose inventory (dev, when schema is available; unreachable remotes are honestly skipped)
297
+ - **R4 [ERROR]** static route shadowed by an earlier param route (first-match-wins dead routes) and exact duplicates
298
+ - **R5 [WARN]** duplicate `name` fields (named-navigation ambiguity)
299
+
300
+ ### 4. `fulgurjs.config.ts` — one federation config per project
301
+
302
+ Default-export the `FederationOptions` object directly; optionally also export `hostPages = { pages, remotePrefixes }` (CLI-only named export; the same pure-data module feeds the browser adapter). `vite.config.ts` calls `federation(fulgurjsConfig)` once. The removed aggregate chain (`root` + `apps[]`, `loadRepoConfig`, `federationOptionsForApp`, CLI `--app`) fails with migration hints.
303
+
304
+ ### 5. CLI command reference
305
+
306
+ ```bash
307
+ npx fulgurjs init # scaffold fulgurjs.config.ts (never rewrites other files)
308
+ npx fulgurjs explain # interpret effective federation shape + load chain
309
+ npx fulgurjs check-pages \
310
+ --manifest remote-a=https://cdn.example.com/remote-a/fulgurjs-manifest.json \
311
+ --require-verified # page-table ↔ manifest contract check; strict CI gate
312
+ npx fulgurjs doctor --site https://example.com # deployment health check
313
+ ```
314
+
315
+ `check-pages` distinguishes "confirmed missing" (errors, non-zero) from "unverifiable" (source unreachable — honestly reported; non-zero with `--require-verified`); it never falls back to stale local dist output.
316
+
317
+ ### 6. Error-code table (41 codes)
318
+
319
+ | Segment | Code | Meaning |
320
+ |---|---|---|
321
+ | CFG (config) | `CFG-001` | name missing or invalid |
322
+ | | `CFG-002` | exposes shape invalid |
323
+ | | `CFG-003` | remotes shape invalid / illegal key characters |
324
+ | | `CFG-004` | shared shape invalid |
325
+ | | `CFG-005` | remotes key collides with a shared key |
326
+ | | `CFG-006` | island config (neither provides nor consumes) |
327
+ | | `CFG-007` | `name@` prefix misuse in object-form remotes |
328
+ | | `CFG-008` | shared illegal combo (eager+import:false / duplicate shareKey declaration) |
329
+ | | `CFG-009` | remote runtime params invalid (timeout/retries/breaker) |
330
+ | | `CFG-010` | devCorsOrigins invalid (must be "*" or an array of http(s) origins) |
331
+ | | `CFG-011` | removed webpack-compat/no-op options (any value errors with migration hints) |
332
+ | | `CFG-012` | setup config invalid (empty/non-string path, or exposes squatting the reserved `./__fulgurjs_setup__` key) |
333
+ | DEV (development) | `DEV-001` | remote dev server unreachable (manifest fetch failed) |
334
+ | | `DEV-002` | remote dev manifest empty or unrecognized |
335
+ | | `DEV-004` | known UMD-only dep missing from optimizeDeps.include |
336
+ | | `DEV-005` | remotes dev URL port not listening |
337
+ | | `DEV-006` | host/remote plugin version mismatch |
338
+ | | `DEV-009` | facade/virtual module 404 (.vite cache drift — clear cache and restart) |
339
+ | | `DEV-010` | dev cold-start pre-bundle window notice (first 30–60s transient) |
340
+ | | `DEV-011` | non-loopback host + wildcard dev CORS (exposure reminder) |
341
+ | | `DEV-012` | non-loopback host + fsRoot in dev manifest (local path disclosure reminder) |
342
+ | BLD (build) | `BLD-001` | expose source resolution failed |
343
+ | | `BLD-002` | build target below es2022 (TLA required) |
344
+ | | `BLD-003` | expose target declares required props (documented checklist item) |
345
+ | | `BLD-006` | array-form output prevents automatic facade chunk isolation (manual branch needed) |
346
+ | MFU (runtime) | `MFU-001` | remote container/module load failure (network / timeout / retries exhausted / breaker open) |
347
+ | | `MFU-002` | remoteEntry self-reported name mismatch |
348
+ | | `MFU-003` | strictVersion requirement not satisfied |
349
+ | | `MFU-004` | shared module missing with no local fallback |
350
+ | | `MFU-005` | same container re-initialized with a different share scope |
351
+ | | `MFU-006` | requested module not exposed by the remote |
352
+ | | `MFU-007` | preload failed (non-blocking) |
353
+ | | `MFU-008` | unknown remote |
354
+ | | `MFU-009` | loaded module has no exports at all |
355
+ | | `MFU-010` | chosen singleton version does not satisfy the consumer's requirement (warn-once per combination, with versions, provider, impact and fix) |
356
+ | | `MFU-011` | setup entry export shape invalid (default/onSession not a function) |
357
+ | | `MFU-012` | setup/onSession threw (this loadRemote rejects; only the failed stage's cache is cleared — directly retryable) |
358
+ | | `MFU-013` | remote declares onSession but host AppContext lacks sessionKey (never use a token as sessionKey) |
359
+ | | `MFU-014` | setup/onSession synchronously re-loading the same remote (self-deadlock guard) |
360
+ | CC (context) | `CC-001` | AppContext required key missing (three-part: got/expected/example) |
361
+ | | `CC-002` | runtime singleton unavailable (remote page opened standalone; load through the host federation instead) |
362
+
363
+ Runtime diagnostics remain in Chinese by design (language policy unchanged in this release).
364
+
365
+ ### 7. Artifacts & endpoints
366
+
367
+ | Artifact | Cache policy |
368
+ |---|---|
369
+ | `fulgurjs-remoteEntry.js` (fixed filename, content changes every build) | **must be `no-cache`** |
370
+ | content-hashed chunks / CSS | long-cache immutable |
371
+ | `fulgurjs-manifest.json` | no-cache (consumed by `preloadRemote` / `check-pages` / `doctor`) |
372
+ | dev endpoints `/@fulgurjs-entry.js` / `/@fulgurjs-manifest.json` | no-cache, CORS per `devCorsOrigins` |
373
+ | `fulgurjs.config.ts` / page-data modules | config files; never hashed |
374
+
375
+ Lazy-loading layers (distinguish them when measuring): ① nothing loaded until first render of a remote component/page; ② container entry + shared metadata on first load; ③ the expose chunk itself; ④ shared-dependency body (negotiated singleton, possibly already loaded by the host). `preloadRemote(spec)` fetches ②③④ without executing the lifecycle.
376
+
377
+ ### 8. `remoteComponent` — Vue direct rendering (`@fulgurjs/federation/runtime`)
378
+
379
+ `defineAsyncComponent + loadRemote` standard wrapper; `loadingComponent` / `errorComponent` / `delay` / `retries` options; the built-in error placeholder shows code + cause + fix; runtime.js stays framework-free.
380
+
381
+ ### 9. `AppContext` — cross-app values & references
382
+
383
+ `provideAppContext(partial)` merges into a page-level singleton mirror (idempotent; later writes win). Standard fields: `user`, `getToken()` (pull-style to avoid stale snapshots), `store` (host pinia), `hostApp` (host Vue app), `locale`, `events`, plus the non-sensitive `sessionKey` (login generation; required by `onSession`, never a token). `requireAppContext(...keys)` validates explicitly (`CC-001`); `clearAppContext()` deletes the context and invalidates session signals/dedup state (module/share caches and successful app-level setup are preserved). The data model is a transport snapshot + function references — not reactive; same-page account switching is carried by `onSession`, never by page reloads.
384
+
385
+ ### 10. `createHostPages` and `setup`/`onSession`
386
+
387
+ `createHostPages(options)` — page table, URL resolution, component cache (rebuilt on new non-empty sessionKey; logout does not rebuild — KeepAlive deactivation-race proven), skeleton/error placeholders, `keepAliveNames`. `setup`/`onSession` — app-level once / session-level per login generation; failures reject that loadRemote with `MFU-012` and stay retryable; `preloadRemote` and `getContainer` never trigger them.
388
+
389
+ ## Gotchas (from real migrations)
390
+
391
+ 1. **Plugin upgraded → restart the dev server** — the plugin self-clears `.vite` caches on version change (DEV-009)
392
+ 2. **pnpm + tarball** — verify the link resolves after installing from a tarball
393
+ 3. **UMD/CJS-only deps** go into `optimizeDeps.include`, never exclude them
394
+ 4. **dev cold start** — warm up once (wait for network idle) before asserting; the first 30–60s pre-bundle window is a transient (DEV-010)
395
+ 5. **never alias/rewrite shared imports by hand** — the negotiation facades own them
396
+ 6. **build target es2022+**
397
+ 7. **`loadRemote` explicit degradation** — `fallbackModule` returns your fallback and still emits the error event
398
+ 8. **missing backend endpoints** stay real errors — no fake 200s
399
+ 9. **multi-version component CSS** coexists via per-expose CSS chunks
400
+ 10. **env-sync scripts** may rewrite env files between dev/prod — pin them
401
+ 11. **React dev types precision** — add the `paths` mapping (see §8.1) to get source-level types; zero-config gives resolvable `any`
402
+
403
+ Debug surfaces: `window.__FULGURJS_SCOPE__` (live share negotiation), `window.__FULGURJS_INFO__` (remote status/latency/errors), `DEBUG=fulgurjs:*` for controlled diagnostics.
404
+
405
+ ## Boundaries (explicitly not supported)
406
+
407
+ - Support covers **browser-client** federation for Vue 3 and React 18–19. Not supported: SSR / React Server Components / Next.js full-stack / React Native / loading remotes from Node servers / directly mixed Vue+React component rendering in one tree. Pure projects of either framework never pull in the other; cross-framework consumption of **plain TS modules** (e.g. a Vue host loading a React remote's utils) works
408
+ - React side does not promise component keep-alive: `createReactHostPages` has no `keepAliveNames` (Vue KeepAlive is Vue-specific); module reuse for re-opened pages still applies
409
+ - Cross-origin Fast Refresh: remote React components update through the remote dev server's `@vite/client` push (after a cold start the first round often needs a host page refresh — state retention across the federation boundary is not promised)
410
+ - Not compatible with originjs's `virtual:__federation__` legacy imports
411
+ - No SSR (warns and disables hooks on detection)
412
+ - No browser DevTools extension (the `window.__FULGURJS_SCOPE__ / __FULGURJS_INFO__` surfaces serve debugging)
413
+ - No JS sandbox / CSS isolation — same-realm coexistence, dual runtimes prevented by shared singleton negotiation (see the sandbox audit doc)
414
+
415
+ ## Documentation
416
+
417
+ - [Migration guide (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/迁移指南.md) — a real qiankun → federation migration case (seven steps + acceptance checklist)
418
+ - [webpack MF comparison & gaps (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/webpack-mf-对照与缺口.md)
419
+ - [Sandbox boundary audit (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/沙箱边界审计.md)
420
+ - [Vite 7/8 compatibility matrix (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/P5-vite7-8兼容矩阵.md)
421
+ - [`DESIGN.md`](https://github.com/chenmingye/fulgurjs-federation/blob/master/DESIGN.md) — architecture, alignment tables, test & acceptance approach
422
+ - Runnable examples: [`examples/react-host`](./examples/react-host) + [`examples/react-remote`](./examples/react-remote) (React, install from the npm registry and run); [`examples/host`](./examples/host) + [`examples/remote-a`](./examples/remote-a) (Vue config samples); in-repo e2e fixtures live under `fixtures/`
423
+
424
+ ## Development & testing
425
+
426
+ ```bash
427
+ pnpm --dir packages/plugin install && pnpm --dir packages/plugin build
428
+ for app in fixtures/host-vue fixtures/remote-a fixtures/remote-b fixtures/remote-react fixtures/host-react e2e; do pnpm --dir "$app" install; done
429
+
430
+ pnpm test:unit # full unit suite (count per actual output)
431
+ pnpm test:dev # dev e2e
432
+ pnpm test:prod # prod e2e (needs NGINX, see e2e/scripts/prod-setup.sh)
433
+ node e2e/scripts/react-types-check.mjs # R15: React dev-type dual-track compile checks
434
+ node e2e/scripts/react-negative-check.mjs # N08/N10/N11 negative checks
435
+ ```
436
+
437
+ CI (GitHub Actions, every push/PR): `test` (unit + dual typecheck + build gates: runtime gzip ≤ 9216B, error-code three-way consistency), `e2e` (Vue + React dev/fault suites across Vite 6.4.3 / 7.3.6 / 8.3.0), `vite5` (scheduled 5.1 floor), `prod-e2e` (NGINX), `tarball` (consumer smoke incl. the React entry).
438
+
439
+ ## License
440
+
441
+ [MIT](./LICENSE) © chenmingye (Jason)