@fulgurjs/federation 5.1.0 → 5.1.1

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/README.en.md CHANGED
@@ -3,61 +3,80 @@
3
3
  [简体中文](./README.md) | English
4
4
 
5
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.
6
+ > A Vite plugin that makes Module Federation work out of the box: **one config shape per project, separate dev & prod engines, semantics aligned with webpack Module Federation**, with first-class browser support for both Vue 3 and React 18/19.
7
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)
8
+ ![tests](https://img.shields.io/badge/tests-460%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
9
 
10
10
  ---
11
11
 
12
- ## Why
12
+ ## 1. Why
13
13
 
14
14
  | | webpack MF | other vite MF solutions | **@fulgurjs/federation** |
15
15
  |---|---|---|---|
16
- | dev experience | separate builds | manual bootstrap usually required | ✅ dual dev-server direct wiring, zero manual async boundaries |
16
+ | dev experience | separate builds required | manual bootstrap usually required | ✅ dual dev-server direct wiring, zero manual async boundaries |
17
17
  | prod artifacts | ✅ | often missing or degraded | ✅ build-time rewriting, stable remoteEntry filename + manifest |
18
18
  | semantic parity | 100% | incomplete (version negotiation / singleton / fault tolerance often missing) | ✅ aligned clause-by-clause with webpack semantics, e2e-verified |
19
19
  | **UMD / CJS-only deps** | DIY | **commonly unusable** | ✅ automatic (dep-optimizer externalization + build-time require shims) |
20
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` |
21
+ | failure recovery | reload the page | usually missing | ✅ retries vary the URL after a real failure, so they penetrate the browser's failed-import cache and genuinely re-fetch |
22
+ | runtime size | ~40KB+ | varies | **gzip < 9KB** (framework-neutral core; adapters are separate) |
23
+ | misconfiguration | hard to debug | cryptic | three-part diagnostics: symptom / cause / fix |
23
24
 
24
- ## Features
25
+ ## 2. Feature overview
25
26
 
26
27
  - **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
28
+ - **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 re-routes shared keys inside pre-bundled output to negotiation facades (esbuild path on Vite ≤ 7, rolldown plugin on Vite ≥ 8), in build CJS `require(<shared>)` calls are redirected to shims — dual-runtime immune
29
+ - **Automatic async boundaries** — top-level await injected automatically (es2022+); no webpack-style manual `import('./bootstrap')`
30
+ - **Stable artifacts** — remoteEntry keeps a fixed filename (content changes every build → **must be `no-cache`**; only content-hashed chunks may be cached long); `fulgurjs-manifest.json` asset manifest; one chunk per expose
31
+ - **Fault tolerance (webpack MF 2.0 errorLoadRemote aligned)** — 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 (**never silent**; without `fallbackModule` the error re-throws)
32
+ - **Real failure recovery** — browsers cache failed dynamic imports per URL (a retry of the same URL never reaches the network). After a real failure the runtime varies the URL (`fulgurjs_retry=N`) across remote-entry loading, dev container loaders and the prod remoteEntry, so "service recovered → click retry" genuinely re-fetches. Successful modules are never re-requested with a varied URL — module identity and singletons are preserved
33
+ - **Enhancements** — dev type generation (dual-track, see §8.6), manifest-driven `preloadRemote()`, runtime plugin hooks (`beforeLoadRemote` / `afterLoadRemote` / `onRemoteError` / `resolveShare`)
33
34
  - **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
35
+ - **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
36
+ - **CLI** — `fulgurjs init` / `explain` / `check-pages` / `doctor` (see §7)
37
+ - **Optional remote init lifecycle** — `federation({ setup })`: `setup(context)` runs once per app, `onSession(context)` runs once per host `sessionKey`; failures are explicit and retryable
38
+ - **Host page adapter** — one page table shared by routing and layout: URL resolution, longest-prefix remote attribution, R1–R5 validation, component cache keyed by login generation, skeleton/error placeholders, keep-alive names (Vue only)
39
+ - **Cross-app context** — `provideAppContext` / `getAppContext` / `requireAppContext` / `clearAppContext`; transport snapshot + function references (not reactive); account switching carried by `onSession` without page reloads
40
+ - **Vue direct rendering** — `remoteComponent('remote/X')` on the runtime entry: `defineAsyncComponent + loadRemote` wrapper with explicit error placeholder; runtime core stays framework-free
41
+ - **Full React support (browser)** — dedicated `@fulgurjs/federation/react` entry: `remoteComponent`, `useLoadRemote`, `RemoteErrorBoundary`, `createReactHostPages`; shared `react`/`react-dom` singletons with hooks/StrictMode/Context verified single-instance; mounted components follow `sessionKey` changes without remounting; pure-React projects install zero Vue, pure-Vue projects install zero React
42
+ - **CSP friendly** — no `eval` / `new Function` anywhere in loading paths
43
+ - **Error-code system (41 codes)** — CFG / DEV / BLD / MFU / CC segments, drift-checked against the code registry (see §11)
44
+
45
+ ## 3. Installation & requirements
45
46
 
46
47
  ```bash
47
48
  pnpm add -D @fulgurjs/federation
48
49
  ```
49
50
 
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
+ - Vite ≥ 5.1 (tested through 8.x; Vite 8 uses the rolldown dep-optimizer path automatically)
52
+ - Node ≥ 18
53
+ - Vue ≥ 3.2.0 and/or React `>=18.0.0 <20` — all three are **optional peers**; install only the framework you use
54
+ - Chrome 108+ (native top-level await)
51
55
 
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.
56
+ `@fulgurjs/federation/runtime` and `@fulgurjs/federation/react` are **ESM-only** browser entries (no `require()`). The build-time main entry supports both ESM and CJS.
53
57
 
54
- ## Quick start (React)
58
+ ## 4. Project shape: two files per app
55
59
 
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`.
60
+ Every app root owns one `fulgurjs.config.ts` whose **default export is the federation options object itself**; `vite.config.ts` wires it once:
57
61
 
58
- Remote (`fulgurjs.config.ts` — the default export is the federation options object itself):
62
+ ```ts
63
+ // vite.config.ts
64
+ import { defineConfig } from 'vite'
65
+ import react from '@vitejs/plugin-react' // or @vitejs/plugin-vue
66
+ import federation from '@fulgurjs/federation'
67
+ import fulgurjsConfig from './fulgurjs.config.ts'
68
+
69
+ export default defineConfig({ plugins: [react(), federation(fulgurjsConfig)] })
70
+ ```
71
+
72
+ An optional named export `hostPages = { pages, remotePrefixes }` is read by the CLI only; the same pure-data module feeds the browser adapter. Page-data modules must stay pure data (no framework/router/browser imports) so the CLI can evaluate them.
73
+
74
+ Removed in 5.0.0 and not coming back: the aggregate config chain (`root` + `apps[]`, the `/config` entry, `loadRepoConfig`, `federationOptionsForApp`, CLI `--app`), and the no-op options `remoteType`, `library`, `automaticAsyncBoundary`, `dataPrefetch`, `usedExports`, `ignoreUnusedSharedExports` — any of these now fail with `CFG-011` plus migration hints.
75
+
76
+ ## 5. Quick start — React
59
77
 
60
78
  ```ts
79
+ // remote: fulgurjs.config.ts
61
80
  import type { FederationOptions } from '@fulgurjs/federation'
62
81
 
63
82
  export default {
@@ -74,276 +93,216 @@ export default {
74
93
  } satisfies FederationOptions
75
94
  ```
76
95
 
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:
96
+ Host consumption — one import point, three usage shapes:
88
97
 
89
98
  ```tsx
90
99
  import { remoteComponent, useLoadRemote, createReactHostPages, remoteSchema } from '@fulgurjs/federation/react'
91
- import { pages, remotePrefixes } from './src/federation/pages.data' // pure data module (CLI + browser)
100
+ import { pages, remotePrefixes } from './src/federation/pages.data'
92
101
 
93
- // ① Component: create the factory at module top level (never inside render); loads on first render
102
+ // ① Component — create the factory at module top level (never inside render).
103
+ // Loading starts on first render. retry rebuilds the load attempt.
94
104
  const RemoteButton = remoteComponent<{ label: string; onClick?: () => void }>('remote-react/Button', {
95
105
  fallback: <p>Loading remote button…</p>,
96
106
  })
97
107
 
98
- // ② Plain module: useLoadRemote (data/error/loading/reload)
108
+ // ② Plain module — generation-guarded hook
99
109
  type Utils = { formatMoney(v: number, currency?: string): string }
100
110
 
101
- // ③ Page table: same verb as Vue — component(spec); render from your router
111
+ // ③ Page table — same verb as Vue; render the component from your router
102
112
  const hp = createReactHostPages({ pages, remotePrefixes, schema: remoteSchema })
103
113
  const RemoteHome = hp.component('remote-react/pages/home')
104
114
  ```
105
115
 
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)
116
+ **Data flow for host state (context):** the host provides context (`provideAppContext`, including a non-sensitive `sessionKey`) and then triggers its own re-render (React state / router). Mounted remote components and hooks observe the new `sessionKey` on that render and re-run their load lifecycle — A→B account switching works on the same mounted instance without remounting. `AppContext` is a plain snapshot: the plugin does not subscribe to it reactively; the host must trigger the render. `beforeLoad` (page tables) runs before every actual load attempt to refresh context. Logout: call `clearAppContext()` before unmounting authed UI.
177
117
 
178
- ## CLI
118
+ Runnable examples: [`examples/react-host`](./examples/react-host) + [`examples/react-remote`](./examples/react-remote) (installed from the npm registry, no links). In-repo e2e fixtures: `fixtures/host-react` / `fixtures/remote-react`.
179
119
 
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.
120
+ ## 6. Quick start — Vue
181
121
 
182
- ## API reference
122
+ Three integration paths (plain module / multi-page `createHostPages` / `setup` + `AppContext`) are documented in the Chinese README §快速开始;the API is identical to the tables below, imported from `@fulgurjs/federation/runtime`. The Vue-specific extras are `createHostPages` (with `keepAliveNames`) and the Vue `remoteComponent` options (`loadingComponent` / `errorComponent` / `delay`).
183
123
 
184
- ### 1. `federation(options)` — the Vite plugin (host & remote, same API)
124
+ Cross-framework **plain TS modules** work in both directions (a Vue host can load a React remote's `utils` and vice versa) — direct Vue↔React component rendering in one tree is out of scope.
185
125
 
186
- ```ts
187
- import federation from '@fulgurjs/federation'
126
+ ## 7. CLI
188
127
 
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
- })
128
+ ```bash
129
+ npx fulgurjs init # scaffold fulgurjs.config.ts; never rewrites other files
130
+ npx fulgurjs explain # interpret the effective federation shape + load chain
131
+ npx fulgurjs check-pages \
132
+ --manifest remote-a=https://cdn.example.com/remote-a/fulgurjs-manifest.json \
133
+ --require-verified # page-table ↔ remote manifest contract check (CI gate)
134
+ npx fulgurjs doctor --site https://example.com # deployment health check
212
135
  ```
213
136
 
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) |
137
+ `check-pages`: "confirmed missing" (error, non-zero) is distinct from "unverifiable" (source unreachable — honestly reported, non-zero with `--require-verified`); no fallback to stale local dist output.
237
138
 
238
- ### 8.1 React adapter API — `@fulgurjs/federation/react`
139
+ ## 8. API reference
239
140
 
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`.
141
+ ### 8.1 `@fulgurjs/federation/react` — React entry
241
142
 
242
- #### `remoteComponent<Props>(spec, options?)`
143
+ Re-exports the common runtime API of §8.2 **except** the Vue-only items (`remoteComponent` Vue options form, `createHostPages`, `keepAliveNames`), plus:
243
144
 
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).
145
+ #### `remoteComponent<Props>(spec, options?)` → `ComponentType<Props & { ref? }>`
245
146
 
246
- | Option | Type & default | Semantics |
147
+ | Option | Type / default | Semantics |
247
148
  |---|---|---|
248
149
  | `fallback` | `ReactNode`, default `null` | placeholder while this load is pending (distinct from the failure placeholder) |
249
150
  | `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 |
151
+ | `retries` | `number` (integer 0–10), default follows `loadRemote` (2) | passthrough; invalid values throw at factory call |
152
+ | `timeout` | `number` (ms), default none | adapter-level wait cap for this component load; does **not** cancel the issued shared request; late results never overwrite the settled state and produce no unhandled rejections |
153
+
154
+ - Factory creation and page-table declaration have **zero load side effects**; loading starts on first render via `loadRemote` (container negotiation + optional setup/onSession)
155
+ - Not built on `React.lazy`: a lazy instance caches its failed promise and an error-boundary reset alone cannot recover; this implementation's retry rebuilds the load attempt (already-cached successful modules are not re-downloaded)
156
+ - Export validation: the default export must be a function/class/`memo`/`forwardRef` component; strings/numbers/empty namespaces fail explicitly
157
+ - `ref` passthrough works for `forwardRef` exports (verified on React 18 and 19)
158
+ - Render exceptions are caught by the built-in boundary and reported separately from network/export errors; the boundary does not catch event-handler or async-callback errors (React semantics)
159
+ - **Session switching:** mounted instances read the current `AppContext.sessionKey` on every render; when the host provides new context and re-renders, the load lifecycle re-runs for the new session on the same instance (no remount, no second React). Same-session re-renders do not reload
160
+ - The built-in placeholder shows error code + real cause + fix + a working 重试 (retry) button
161
+
162
+ #### `useLoadRemote<Module>(spec, options?)` → `{ data, error, loading, reload }`
163
+
164
+ - `data: Module | undefined`, `error: unknown` (always `undefined` when no error), `loading: boolean`, `reload: () => Promise<void>`
165
+ - Options: `shareScope`, `retries`, `fallbackModule` (explicit degradation — failures return the fallback value instead of writing `error`)
166
+ - Uniform state contract: first load, spec/option/session change and explicit `reload` all enter `data=undefined, error=undefined, loading=true`; the current attempt writes `data` on success or `error` on failure and clears `loading`; stale attempts never write
167
+ - Generation guards: fast A→B switching, late slow responses, consecutive reloads, unmount-during-flight and StrictMode double effects can only write from the latest valid request
168
+ - `reload` re-runs the lifecycle (onSession dedup by generation) but never re-downloads cached successful modules; resolves normally (failures surface in `error`, never an unhandled rejection)
169
+ - Session-aware: re-runs when `sessionKey` changes; same-session re-renders don't
252
170
 
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
- ```
171
+ #### `RemoteErrorBoundary`
264
172
 
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`)
173
+ Standalone page-level boundary. Props: `children`, `fallback` (node or `({ error, reset }) => ReactNode`), `onError(error, info)`, `resetKeys` (reset when any entry changes). `reset` only clears boundary state; if a child holds a failed cache (e.g. your own `React.lazy`), rebuilding the attempt is the caller's job — `remoteComponent`'s built-in retry already does both. It never sees errors already consumed by `remoteComponent`'s inner boundary.
271
174
 
272
- #### `RemoteErrorBoundary`
175
+ #### `createReactHostPages(options)` → `{ pages, resolve(path), component(spec) }`
273
176
 
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.
177
+ - Data options (identical to Vue): `pages`, `remotePrefixes`, `deriveSpec`, `schema`, `strict`, `base`
178
+ - Display options (same semantics as `remoteComponent`): `fallback`, `error`, `retries`, `timeout`; plus `beforeLoad: () => void | Promise<void>` — runs before **every actual load attempt** (including retries) so the host can refresh context; never at table creation
179
+ - `component<P>(spec)` returns a React component type; the component cache is keyed by spec + login generation (rebuilt only on a new non-empty `sessionKey`; logout → `undefined` does not rebuild). Module-level caching of `component(spec)` results is supported — mounted pages still follow session changes
180
+ - No `keepAliveNames` / no keep-alive promise (Vue-specific); routing is not a runtime dependency — render `component(spec)` output from your router (React Router examples in `examples/react-host`; route params reach remote pages as props)
181
+ - Cross-framework Context: host and remote get the **same Context object** through the same expose instance; the plugin does not auto-bridge arbitrary React Contexts
275
182
 
276
- #### `createReactHostPages(options)`
183
+ ### 8.2 Runtime API — `@fulgurjs/federation/runtime` (Vue apps) and common functions on `/react`
277
184
 
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).
185
+ | Function | Signature | Semantics |
186
+ |---|---|---|
187
+ | `loadRemote` | `<T = Record<string, any>>(spec: string, opts?: { shareScope?: string; retries?: number; fallbackModule?: () => any }) => Promise<T>` | spec = `<remote>/<expose-key-without-./>`. Goes through container negotiation and the optional setup/onSession lifecycle. Modules cached per `remote@scope#module`; failures uncached and retryable. `fallbackModule` returns your module on failure **and** still emits the error event |
188
+ | `loadShare` | `(name: string, opts?: LoadShareOptions) => Promise<any>` | shared-deps negotiation: `requiredVersion` (semver or `false`), `singleton`, `strictVersion`, `shareKey`, `shareScope`, `fallback: () => Promise<any>`. Highest satisfying version wins; loaded versions never replaced; singleton keeps one instance (warns MFU-010 when the reused version doesn't satisfy `requiredVersion`; throws MFU-003 with `strictVersion`) |
189
+ | `initSharing` | `(scopeName?: string) => ShareScopeMap` (default `'default'`) | creates/returns the share scope map (usually called for you by the injected init) |
190
+ | `registerShare` | `(scopeName, name, version, get: () => Promise<any>, opts?: { from?, eager?, loaded? }) => void` | register a provided shared module at runtime; first registration of a version wins |
191
+ | `registerRemote` / `registerRemotes` | `(config: RemoteConfig) => void` / `((list: RemoteConfig[]) => void)` | runtime registration: `{ name, entry, shareScope?, timeout?, retries?, fallback?, breaker?, promise? }`. Promise-based remotes pass `promise: () => Promise<container>` |
192
+ | `registerPlugins` | `(plugins: RuntimePlugin[]) => void` | register runtime plugins; each `init(hooks)` may set `resolveShare`, `beforeLoadRemote({ remote, module })`, `afterLoadRemote({ remote, module, module_ns })`, `onRemoteError({ remote, error })`. Observer-hook failures warn but never break loading |
193
+ | `preloadRemote` | `(spec: string, opts?: { mode?: 'preload' \| 'prefetch' }) => Promise<void>` | manifest-driven preload of entry + expose chunks + CSS; `'prefetch'` = low priority. No lifecycle side effects (setup/onSession are NOT run) |
194
+ | `getContainer` | `(name: string) => Promise<any>` | acquire the initialized container |
195
+ | `getRuntime` | `() => FgRuntime` | the page-level runtime singleton (`globalThis.__FULGURJS_RUNTIME__`) |
196
+ | `parseSpec` | `(spec: string) => { remote, module }` | synchronous spec parsing |
197
+ | `shareScopeMap` | `ShareScopeMap` | live registry (debug surface: `window.__FULGURJS_SCOPE__`) |
198
+ | `unwrapDefault` | `(ns: any) => any` | ESM/CJS default-interop helper |
199
+ | `version` | `string` | plugin/runtime version |
200
+ | `clearSessionState` | `() => void` | invalidate all remotes' session signals and onSession dedup state (called by `clearAppContext`) |
201
+
202
+ Remote-registration config fields: `timeout` (ms, default 15000 — ends the caller's wait, never cancels the issued import), `retries` (0–10, default 2), `fallback: string[]` (spare entry URLs), `breaker: { threshold, resetMs }` (default 5 / 30s).
203
+
204
+ ### 8.3 Plugin options — `federation(options)`
205
+
206
+ | Option | Type / default | Notes |
207
+ |---|---|---|
208
+ | `name` | `string`, **required** | container name; unique per page; `/^[a-zA-Z][\w.-]*$/` |
209
+ | `exposes` | `Record<string, string \| { import, name? }>` | key normalized to `./Key`; stable chunk name optional |
210
+ | `remotes` | `Record<string, string \| RemoteEntryConfig \| (() => Promise<any>)>` | string = url or `name@url`; object = `{ external?, dev?, prod?, timeout?, retries?, fallback?, breaker?, shareScope? }`; function = promise-based remote (runtime-register instead) |
211
+ | `shared` | `string[]` or `Record<string, string \| SharedHint>` | see below |
212
+ | `setup` | `string` | module path; must default-export `setup(context)`, optional named `onSession(context)` |
213
+ | `shareScope` | `string`, default `'default'` | default scope for provides |
214
+ | `filename` | `string`, default `'fulgurjs-remoteEntry.js'` | fixed remoteEntry filename |
215
+ | `manifest` | `boolean`, default `true` | emit `fulgurjs-manifest.json` |
216
+ | `dts` | `boolean \| { dir?, mode?: 'source' \| 'shim' }`, default `true` | dev type generation (see §8.6) |
217
+ | `devSharedSelf` | `boolean`, default inferred | pure remotes & dual-role apps: `true` (dev shared rewriting); pure hosts: `false` |
218
+ | `devCorsOrigins` | `'*'` or `string[]` | dev endpoints + server.cors share the policy; explicit user `server.cors` wins |
219
+ | `devFsRoot` | `boolean`, default `true` | dev manifest carries local fsRoot for type direct-connect; `false` → host falls back to `any` stubs |
220
+ | `runtimePlugins` | `string[]` | modules default-exporting a `RuntimePlugin` |
221
+
222
+ `SharedHint` fields: `import` (local specifier or `false` = pure consumer), `packageName` (infer `requiredVersion` from a different package name), `requiredVersion` (semver or `false`), `singleton`, `strictVersion` (default: `true` when a local fallback exists and not singleton, webpack-aligned), `shareKey`, `shareScope`, `eager`, `version`.
223
+
224
+ ### 8.4 Lifecycle — `setup` / `onSession`
279
225
 
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
226
+ ```ts
227
+ // federation({ setup: './src/fulgurjs/setup.ts' })
228
+ export default async function setup(ctx: { appContext: Record<string, any>; sessionKey?: string; signal: AbortSignal }) {
229
+ // app-level: once per app, before the first business module is returned
230
+ }
231
+ export async function onSession(ctx: { appContext: any; sessionKey: string; signal: AbortSignal }) {
232
+ // session-level: once per host sessionKey (login generation); re-login re-runs, logout invalidates
233
+ }
234
+ ```
285
235
 
286
- #### React dev types
236
+ - Failures reject the triggering `loadRemote` (MFU-011/012) and are retryable; already-succeeded stages are not re-run
237
+ - `signal` aborts on logout/session change — check `signal.aborted` before writing async results
238
+ - `preloadRemote` / `getContainer` never trigger the lifecycle
239
+ - Remote declares `onSession` → the host **must** provide a non-empty `sessionKey` (MFU-013); never use a token as sessionKey
240
+ - No-setup remotes (plain public components) load normally without any context
287
241
 
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.
242
+ ### 8.5 AppContext — cross-app values
289
243
 
290
- ### 3. `definePages` — host page-table validation
244
+ - `provideAppContext(partial)` — merge-write the page-level singleton (idempotent; later writes win). Host bridge calls it after login and re-calls on account change; then triggers its own re-render
245
+ - `getAppContext()` — read the snapshot (`CC-002` if loaded outside the host federation)
246
+ - `requireAppContext(...keys)` — validated read; missing keys → `CC-001` with got/expected/example
247
+ - `clearAppContext()` — delete context + invalidate session signals/dedup (module and share caches, and completed app-level setup, are preserved). Logout must call it before unmounting authed UI
248
+ - Standard fields: `user`, `getToken()`, `store` (host pinia), `hostApp` (host Vue app), `locale`, `events`, `sessionKey` — plus arbitrary extension keys. Transport snapshot + function references; not reactive
291
249
 
292
- `validatePages(pages, options)` returns violations; `definePages` aggregates and throws on ERROR level (or `console.error` with `strict: false`):
250
+ ### 8.6 Dev types (dual-track)
293
251
 
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)
252
+ - Zero config: ambient declarations per expose — imports resolve, exports typed `any`; setup entry never generates declarations
253
+ - Precise track: add `"paths": { "<remote>/*": ["<typesDir>/<remote>.d/*"] }` to any app `tsconfig*.json` (except `tsconfig.node.json`); imports then resolve through forwarder modules to **source-level types** (wrong props/arguments fail compilation). Remotes covered by paths automatically skip their loose declaration to avoid shadowing
254
+ - `devFsRoot: false` or unreachable source: degrades to resolvable `any` declarations and cleans stale precise-track files (precise → degrade → restore cycles compile cleanly)
255
+ - `dts: false` stops generation without deleting existing output; `dts.dir` relocates; `mode: 'shim'` gives loose IDE-clean placeholders
256
+ - Precise track requires the host and remote to share a filesystem (same-machine dev); verified bounds: React 18.0.0–19.x with matching @types
299
257
 
300
- ### 4. `fulgurjs.config.ts` — one federation config per project
258
+ ## 9. Artifacts, endpoints & caching
301
259
 
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.
260
+ | Artifact | Cache policy |
261
+ |---|---|
262
+ | `fulgurjs-remoteEntry.js` (fixed filename, content changes every build) | **`no-cache`** |
263
+ | content-hashed chunks / CSS | `immutable` long cache |
264
+ | `fulgurjs-manifest.json` | `no-cache` (consumed by `preloadRemote` / `check-pages` / `doctor`) |
265
+ | dev endpoints `/@fulgurjs-entry.js` / `/@fulgurjs-manifest.json` | `no-cache`, CORS per `devCorsOrigins` |
303
266
 
304
- ### 5. CLI command reference
267
+ Lazy-loading measurement layers: ① nothing until first render of a remote component/page; ② container entry + shared metadata on first load; ③ the expose chunk; ④ shared-dependency bodies (negotiated, possibly already loaded). `preloadRemote(spec)` fetches ②③④ without executing lifecycle code. Verify with real network records — count URLs and transferred bytes per layer, cold cache vs revisit.
305
268
 
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
- ```
269
+ ## 10. Debugging surfaces
314
270
 
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.
271
+ - `window.__FULGURJS_SCOPE__` — live share-scope registry
272
+ - `window.__FULGURJS_INFO__` — per-remote status/latency/errors + `errors` log
273
+ - `DEBUG=fulgurjs:*` — controlled pipeline diagnostics (off by default)
274
+ - Runtime diagnostics are emitted in Chinese by design (language policy); codes are stable identifiers listed below
316
275
 
317
- ### 6. Error-code table (41 codes)
276
+ ## 11. Error codes (41)
318
277
 
319
278
  | Segment | Code | Meaning |
320
279
  |---|---|---|
321
- | CFG (config) | `CFG-001` | name missing or invalid |
280
+ | CFG | `CFG-001` | name missing or invalid |
322
281
  | | `CFG-002` | exposes shape invalid |
323
282
  | | `CFG-003` | remotes shape invalid / illegal key characters |
324
283
  | | `CFG-004` | shared shape invalid |
325
284
  | | `CFG-005` | remotes key collides with a shared key |
326
285
  | | `CFG-006` | island config (neither provides nor consumes) |
327
286
  | | `CFG-007` | `name@` prefix misuse in object-form remotes |
328
- | | `CFG-008` | shared illegal combo (eager+import:false / duplicate shareKey declaration) |
287
+ | | `CFG-008` | shared illegal combo (eager+import:false / duplicate shareKey) |
329
288
  | | `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) |
289
+ | | `CFG-010` | devCorsOrigins invalid |
290
+ | | `CFG-011` | removed no-op option (any value errors with migration hints) |
291
+ | | `CFG-012` | setup config invalid / reserved expose key squatted |
292
+ | DEV | `DEV-001` | remote dev server unreachable (manifest fetch failed) |
334
293
  | | `DEV-002` | remote dev manifest empty or unrecognized |
335
294
  | | `DEV-004` | known UMD-only dep missing from optimizeDeps.include |
336
295
  | | `DEV-005` | remotes dev URL port not listening |
337
296
  | | `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) |
297
+ | | `DEV-009` | facade/virtual module 404 (.vite cache drift — clear and restart) |
298
+ | | `DEV-010` | dev cold-start pre-bundle window notice (transient) |
299
+ | | `DEV-011` | non-loopback host + wildcard dev CORS reminder |
300
+ | | `DEV-012` | non-loopback host + fsRoot disclosure reminder |
301
+ | BLD | `BLD-001` | expose source resolution failed |
302
+ | | `BLD-002` | build target below es2022 |
303
+ | | `BLD-003` | expose target declares required props (documented checklist) |
304
+ | | `BLD-006` | array-form output prevents automatic facade chunk isolation |
305
+ | MFU | `MFU-001` | remote container/module load failure (network / timeout / retries exhausted / breaker) |
347
306
  | | `MFU-002` | remoteEntry self-reported name mismatch |
348
307
  | | `MFU-003` | strictVersion requirement not satisfied |
349
308
  | | `MFU-004` | shared module missing with no local fallback |
@@ -352,89 +311,45 @@ npx fulgurjs doctor --site https://example.com # deployment health check
352
311
  | | `MFU-007` | preload failed (non-blocking) |
353
312
  | | `MFU-008` | unknown remote |
354
313
  | | `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.
314
+ | | `MFU-010` | reused singleton version doesn't satisfy the consumer requirement (warn-once) |
315
+ | | `MFU-011` | setup entry export shape invalid |
316
+ | | `MFU-012` | setup/onSession threw (retryable; only the failed stage resets) |
317
+ | | `MFU-013` | onSession declared but host sessionKey missing |
318
+ | | `MFU-014` | setup/onSession synchronously re-loading the same remote (deadlock guard) |
319
+ | CC | `CC-001` | AppContext required key missing (got/expected/example) |
320
+ | | `CC-002` | runtime singleton unavailable (standalone remote page) |
404
321
 
405
- ## Boundaries (explicitly not supported)
322
+ ## 12. Boundaries (explicitly not supported)
406
323
 
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)
324
+ - Support covers **browser-client** federation for Vue 3 and React 18–19. Not supported: SSR, React Server Components, Next.js full-stack, React Native, Node-side remote loading, direct Vue↔React component rendering in one tree, JS sandbox, CSS isolation
325
+ - React side does not promise component keep-alive (`keepAliveNames` is Vue-only); re-opened pages still reuse downloaded modules
326
+ - Cross-origin Fast Refresh: remote React components update via the remote dev server's HMR push; after a cold start the first round often needs a host refresh — component-state retention across the federation boundary is not promised
327
+ - Not compatible with originjs `virtual:__federation__` legacy imports
328
+ - No browser DevTools extension (the `window.__FULGURJS_*` surfaces serve debugging)
414
329
 
415
- ## Documentation
330
+ ## 13. Documentation & examples
416
331
 
417
- - [Migration guide (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/迁移指南.md) — a real qiankun → federation migration case (seven steps + acceptance checklist)
332
+ - [Migration guide (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/迁移指南.md) — a real qiankun → federation migration (seven steps + acceptance checklist)
418
333
  - [webpack MF comparison & gaps (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/webpack-mf-对照与缺口.md)
419
334
  - [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/`
335
+ - [`DESIGN.md`](https://github.com/chenmingye/fulgurjs-federation/blob/master/DESIGN.md) — architecture and alignment tables
336
+ - Examples: [`examples/react-host`](./examples/react-host) + [`examples/react-remote`](./examples/react-remote) (React, registry-installable) · [`examples/host`](./examples/host) + [`examples/remote-a`](./examples/remote-a) (Vue config samples)
423
337
 
424
- ## Development & testing
338
+ ## 14. Development & testing
425
339
 
426
340
  ```bash
427
341
  pnpm --dir packages/plugin install && pnpm --dir packages/plugin build
428
342
  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
343
 
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
344
+ pnpm test:unit # full unit suite
345
+ pnpm test:dev # dev e2e (Vue)
346
+ pnpm test:prod # prod e2e (NGINX, isolated instance)
347
+ node e2e/scripts/react-types-check.mjs # dual-track dev types + negative matrix
434
348
  node e2e/scripts/react-negative-check.mjs # N08/N10/N11 negative checks
349
+ bash e2e/scripts/prod-setup.sh # build all fixtures + isolated NGINX
435
350
  ```
436
351
 
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).
352
+ CI: unit + dual typecheck + build gates (gzip, error-code consistency); e2e Vue+React suites across Vite 6.4.3 / 7.3.6 / 8.3.0; scheduled Vite 5.1 floor job; prod-e2e; tarball consumer smoke (Vue + React).
438
353
 
439
354
  ## License
440
355