@fulgurjs/federation 5.7.1 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,475 +1,422 @@
1
1
  # @fulgurjs/federation
2
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 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
-
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
-
10
- ---
11
-
12
- ## 1. Why
13
-
14
- | | webpack MF | other vite MF solutions | **@fulgurjs/federation** |
15
- |---|---|---|---|
16
- | dev experience | separate builds required | 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
- | failure recovery | reload the page | usually missing | ✅ built-in placeholders offer **Retry load** (in-page; failed URLs are varied to penetrate the browser's failed-import cache) and **Refresh page to retry** (a user-initiated full reload for failures the browser caches beyond in-page reach) |
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 |
24
-
25
- ## 2. Feature overview
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
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** — Chromium/Firefox/Safari cache failed dynamic imports per URL, so re-importing the same URL rejects without hitting the network again (verified per browser in this repo's e2e; see MDN import() for the underlying semantics). 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 load" genuinely re-fetches. Successful modules are never re-requested with a varied URL — module identity and singletons are preserved; concurrent failures advance exactly one retry generation (no module-instance split); repeated access to loaded modules issues zero extra requests. **Known boundary**: a failed **static dependency** chunk of an expose cannot recover in-page (the browser caches the dependency URL's failure). The built-in placeholder therefore also offers **Refresh page to retry** — a user-initiated full reload that keeps the current URL (never automatic, no reload loops) — and that is the supported recovery path for this case; the plugin deliberately does not rewrite the whole site dependency graph to work around it
33
- - **Enhancements** — dev type generation (dual-track, see §8.6), manifest-driven `preloadRemote()`, runtime plugin hooks (`beforeLoadRemote` / `afterLoadRemote` / `onRemoteError` / `resolveShare`)
34
- - **Full HMR chain** — remote edits propagate to the host page: component hot swap, state retention, error overlay and recovery
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 (44 codes)** — CFG / DEV / BLD / MFU / CC segments, drift-checked against the code registry (see §11)
44
-
45
- ## 3. Installation & requirements
3
+ [简体中文](./README.md) | [English](./README.en.md)
4
+
5
+ **Use components, pages and functions from another Vite application.**
6
+
7
+ For example, a main application can load a separately deployed approval page, a Vue host can embed a React sub-app, or several applications can use the same utility module. Each application can live in its own repository and build and deploy separately.
8
+
9
+ This is the usage guide, with examples for **5.7.1**. Signatures, defaults and execution rules are in the [API reference](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md).
10
+
11
+ ## Choose what you need
12
+
13
+ | Goal | Use | Example |
14
+ |---|---|---|
15
+ | Load a Vue component in Vue | `remoteComponent` | [Vue examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/vue) |
16
+ | Load a React component in React | `remoteComponent` from `/react` | [React examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/react) |
17
+ | Call a remote JS/TS function | `loadRemote`; React also has `useLoadRemote` | Quick start below |
18
+ | Map several host routes to remote pages | `createHostPages` (Vue) / `createReactHostPages` (React) | [Page demo](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/pages-cli) |
19
+ | Embed Vue in React, or React in Vue | `defineBridgeApp` + a host bridge component | [Bridge examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/bridge) |
20
+ | Restore a sub-app detail route after refresh | Enable bridge URL sync | [Router demo](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/bridge-router) |
21
+ | Provide user data or run remote initialization | `AppContext`, optional `setup`/`onSession` | Initialization below |
22
+ | Run React 18 and 19 on the same page | Separate dependency groups and consumers using `shareScope` | [Version isolation demo](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/react-versions) |
23
+
24
+ Combine these features as needed. **A simple remote component does not require a bridge, page table or login lifecycle.**
25
+
26
+ ## Terms in plain language
27
+
28
+ | Term | Meaning |
29
+ |---|---|
30
+ | Host | The application displaying remote content |
31
+ | Remote | The application providing a module |
32
+ | `exposes` | Files the remote allows other applications to load |
33
+ | `remotes` | The remote names and addresses the host uses |
34
+ | `shared` | Dependencies that participate in sharing, such as Vue or React |
35
+ | `singleton` | Adopt one dependency instance within a share scope; this does not make incompatible major versions compatible |
36
+ | `shareScope` | A group of shared dependencies; separate groups can use separate versions |
37
+ | Bridge | A DOM container in which a sub-app manages its own rendering and cleanup |
38
+ | URL sync | Record the sub-app route in the host URL so refresh, sharing and history navigation can restore it |
39
+
40
+ An application can both expose and consume modules.
41
+
42
+ ## Install
43
+
44
+ Install in every participating Vite project:
46
45
 
47
46
  ```bash
48
47
  pnpm add -D @fulgurjs/federation
48
+ # npm projects: npm install -D @fulgurjs/federation
49
49
  ```
50
50
 
51
- - Vite ≥ 5.1 (tested through 8.x)
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
+ - Supports browser applications using Vue 3, React 18/19, and plain JS/TS modules.
52
+ - Supports Vite 5.1+ within the Vite 5/6/7/8 series. Your framework plugins must also support your chosen Vite version.
53
+ - The plugin requires Node.js ≥18, but **Vite 7/8 require Node.js 20.19+ or 22.12+**. Meet both requirements.
54
+ - Set the build target to `es2022` or newer. Chrome 108+ is the browser baseline; other browsers need corresponding ESM, dynamic import and top-level await support.
55
+ - A pure Vue application needs Vue; a pure React application needs React and react-dom. A cross-framework bridge host installs both frameworks as explained below.
55
56
 
56
- > Vite 8 (rolldown): **fully usable in production as of 5.6.0** — the synchronous negotiation facade (V8-SYNC-FACADE) eliminates the startup mutual-await deadlock between top-level await propagation and application circular dependencies (full JeecgBoot v3.9.5 acceptance: login, A-hosts-B, three-layer deep-link refresh, cross-framework deep links, React host; vite 8.3.2 + rolldown 1.2.12), plus three vite8-specific fixes (remoteEntry failure retry, manifest exposes mapping, dependency-preload negative caching); e2e dev 73/73 + prod 33/33 as a standing CI matrix. The first page open on a cold dev cache still falls inside the dependency pre-bundling window (DEV-010; it self-recovers via reload). Warm up before acceptance runs or manual judgement, as documented. Known cost (vite 8 only): the synchronous facade keeps the local copy chunk reachable by the module graph even when negotiation picks another app's instance (dual-version scenarios fetch the shared library at most twice; runtime identity still converges to a single instance).
57
+ ## Quick start: two Vue applications
57
58
 
58
- `@fulgurjs/federation/runtime` and `@fulgurjs/federation/react` are **ESM-only** browser entries (no `require()`). The build-time main entry supports both ESM and CJS.
59
+ These steps add federation to **existing Vite + Vue projects**, which retain their own HTML and application entry files.
59
60
 
60
- ## 4. Project shape: two files per app
61
+ ```text
62
+ remote-vue/ Provides a button and add() function; dev port 5174
63
+ host-vue/ Loads them; dev port 5173
64
+ ```
65
+
66
+ ### 1. Declare remote files
61
67
 
62
- Every app root owns one `fulgurjs.config.ts` whose **default export is the federation options object itself**; `vite.config.ts` wires it once:
68
+ `remote-vue/fulgurjs.config.ts`:
63
69
 
64
70
  ```ts
65
- // vite.config.ts
66
- import { defineConfig } from 'vite'
67
- import react from '@vitejs/plugin-react' // or @vitejs/plugin-vue
68
- import federation from '@fulgurjs/federation'
69
- import fulgurjsConfig from './fulgurjs.config.ts'
71
+ import type { FederationOptions } from '@fulgurjs/federation'
72
+
73
+ export default {
74
+ name: 'remote-vue',
75
+ exposes: {
76
+ './Button': './src/Button.vue',
77
+ './math': './src/math.ts',
78
+ },
79
+ shared: { vue: { singleton: true, strictVersion: true } },
80
+ } satisfies FederationOptions
81
+ ```
82
+
83
+ `remote-vue/src/Button.vue`:
84
+
85
+ ```vue
86
+ <script setup lang="ts">
87
+ import { ref } from 'vue'
88
+ defineProps<{ label: string }>()
89
+ const count = ref(0)
90
+ </script>
70
91
 
71
- export default defineConfig({ plugins: [react(), federation(fulgurjsConfig)] })
92
+ <template>
93
+ <button @click="count++">{{ label }}: {{ count }}</button>
94
+ </template>
72
95
  ```
73
96
 
74
- 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.
97
+ `remote-vue/src/math.ts`:
75
98
 
76
- 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.
99
+ ```ts
100
+ export function add(a: number, b: number): number {
101
+ return a + b
102
+ }
103
+ ```
104
+
105
+ ### 2. Declare the address in the host
77
106
 
78
- ## 5. Quick start — React
107
+ `host-vue/fulgurjs.config.ts`:
79
108
 
80
109
  ```ts
81
- // remote: fulgurjs.config.ts
82
110
  import type { FederationOptions } from '@fulgurjs/federation'
83
111
 
84
112
  export default {
85
- name: 'remote-react',
86
- exposes: {
87
- './Button': './src/Button.tsx',
88
- './utils': './src/utils.ts',
89
- './pages/home': './src/pages/Home.tsx',
90
- },
91
- shared: {
92
- react: { singleton: true },
93
- 'react-dom': { singleton: true },
113
+ name: 'host-vue',
114
+ remotes: {
115
+ 'remote-vue': {
116
+ dev: 'http://localhost:5174',
117
+ prod: '/remote-vue',
118
+ },
94
119
  },
120
+ shared: { vue: { singleton: true, strictVersion: true } },
95
121
  } satisfies FederationOptions
96
122
  ```
97
123
 
98
- Host consumption — one import point, three usage shapes:
124
+ `dev` is the development URL. `prod` is the deployed URL; `/remote-vue` refers to a path on the host origin, not a local filesystem folder.
99
125
 
100
- ```tsx
101
- import { remoteComponent, useLoadRemote, createReactHostPages, remoteSchema } from '@fulgurjs/federation/react'
102
- import { pages, remotePrefixes } from './src/federation/pages.data'
126
+ ### 3. Register the plugin in both applications
103
127
 
104
- // ① Component — create the factory at module top level (never inside render).
105
- // Loading starts on first render. retry rebuilds the load attempt.
106
- const RemoteButton = remoteComponent<{ label: string; onClick?: () => void }>('remote-react/Button', {
107
- fallback: <p>Loading remote button…</p>,
108
- })
128
+ Each project's `vite.config.ts` imports its own federation config:
109
129
 
110
- // ② Plain module — generation-guarded hook
111
- type Utils = { formatMoney(v: number, currency?: string): string }
130
+ ```ts
131
+ import { defineConfig } from 'vite'
132
+ import vue from '@vitejs/plugin-vue'
133
+ import federation from '@fulgurjs/federation'
134
+ import fulgurjsConfig from './fulgurjs.config'
112
135
 
113
- // ③ Page table — same verb as Vue; render the component from your router
114
- const hp = createReactHostPages({ pages, remotePrefixes, schema: remoteSchema })
115
- const RemoteHome = hp.component('remote-react/pages/home')
136
+ export default defineConfig({
137
+ plugins: [vue(), federation(fulgurjsConfig)],
138
+ build: { target: 'es2022' },
139
+ })
116
140
  ```
117
141
 
118
- **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.
142
+ Keep existing aliases, proxies and other settings. Install compatible Vue versions in both applications; `strictVersion` rejects incompatible shared versions.
119
143
 
120
- Runnable examples: [`examples/vue/{host,remote}`](./examples) and [`examples/react/{host,remote}`](./examples) — four complete copy-and-run projects installed from the npm registry (see the examples entry page). In-repo e2e fixtures: `fixtures/host-react` / `fixtures/remote-react`.
144
+ ### 4. Display and call the remote modules
121
145
 
122
- ## 6. Quick start — Vue
146
+ `host-vue/src/App.vue`:
123
147
 
124
- 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`).
148
+ ```vue
149
+ <script setup lang="ts">
150
+ import { ref } from 'vue'
151
+ import { loadRemote, remoteComponent } from '@fulgurjs/federation/runtime'
125
152
 
126
- 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.
153
+ const RemoteButton = remoteComponent('remote-vue/Button')
154
+ const result = ref('Not calculated yet')
127
155
 
128
- ## 7. CLI
156
+ async function calculate() {
157
+ try {
158
+ const math = await loadRemote<{ add(a: number, b: number): number }>('remote-vue/math')
159
+ result.value = String(math.add(1, 2))
160
+ } catch (error) {
161
+ result.value = error instanceof Error ? error.message : String(error)
162
+ }
163
+ }
164
+ </script>
129
165
 
130
- ```bash
131
- npx fulgurjs init # scaffold fulgurjs.config.ts; never rewrites other files
132
- npx fulgurjs explain # interpret the effective federation shape + load chain
133
- npx fulgurjs check-pages \
134
- --manifest remote-a=https://cdn.example.com/remote-a/fulgurjs-manifest.json \
135
- --require-verified # page-table ↔ remote manifest contract check (CI gate)
136
- npx fulgurjs doctor --site https://example.com # deployment health check
166
+ <template>
167
+ <RemoteButton label="Remote button" />
168
+ <button @click="calculate">Call remote add()</button>
169
+ <p>{{ result }}</p>
170
+ </template>
137
171
  ```
138
172
 
139
- `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.
173
+ In `remote-vue/Button`, `remote-vue` matches the host's `remotes` key and `Button` matches the remote's `./Button` expose key. The `./` can be omitted when loading it.
140
174
 
141
- ## 8. API reference
175
+ `loadRemote` returns module exports. You still need to call `math.add()` to perform the calculation.
142
176
 
143
- ### 8.1 `@fulgurjs/federation/react` — React entry
177
+ ### 5. Run both applications
144
178
 
145
- Re-exports the common runtime API of §8.2 **except** the Vue-only items (`remoteComponent` Vue options form, `createHostPages`, `keepAliveNames`), plus:
146
-
147
- #### `remoteComponent<Props>(spec, options?)` → `ComponentType<Props & { ref? }>`
179
+ ```bash
180
+ # Terminal one, inside remote-vue
181
+ npm run dev -- --port 5174 --strictPort
148
182
 
149
- | Option | Type / default | Semantics |
150
- |---|---|---|
151
- | `fallback` | `ReactNode`, default `null` | placeholder while this load is pending (distinct from the failure placeholder) |
152
- | `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 |
153
- | `retries` | `number` (integer 0–10), default follows `loadRemote` (2) | passthrough; invalid values throw at factory call |
154
- | `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 |
183
+ # Terminal two, inside host-vue
184
+ npm run dev -- --port 5173 --strictPort
185
+ ```
155
186
 
156
- - Factory creation and page-table declaration have **zero load side effects**; loading starts on first render via `loadRemote` (container negotiation + optional setup/onSession)
157
- - 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)
158
- - Export validation: the default export must be a function/class/`memo`/`forwardRef` component; strings/numbers/empty namespaces fail explicitly
159
- - `ref` passthrough works for `forwardRef` exports (verified on React 18 and 19)
160
- - 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)
161
- - **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
162
- - The built-in placeholder shows error code + real cause + fix + a working 重试 (retry) button
187
+ Open `http://localhost:5173`. The remote button should count clicks, and the calculation should display `3`. pnpm projects can use `pnpm dev` instead.
163
188
 
164
- #### `useLoadRemote<Module>(spec, options?)` → `{ data, error, loading, reload }`
189
+ Complete projects and deployment configuration: [Vue examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/vue).
165
190
 
166
- - `data: Module | undefined`, `error: unknown` (always `undefined` when no error), `loading: boolean`, `reload: () => Promise<void>`
167
- - Options: `shareScope`, `retries`, `fallbackModule` (explicit degradation — failures return the fallback value instead of writing `error`)
168
- - 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
169
- - 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
170
- - `reload` clears old data and 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). Unmount invalidates pending effects and reloads; calling a saved reload after unmount starts no request
171
- - Session-aware: re-runs when `sessionKey` changes; same-session re-renders don't
191
+ ## React setup
172
192
 
173
- #### `RemoteErrorBoundary`
193
+ Use the same configuration structure with these changes:
174
194
 
175
- 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.
195
+ 1. Use `@vitejs/plugin-react` in `vite.config.ts`, followed by `federation(fulgurjsConfig)`.
196
+ 2. Expose `./Button` from `./src/Button.tsx`; configure the remote's address in the host.
197
+ 3. Both applications use compatible React/renderer versions and share:
176
198
 
177
- #### `createReactHostPages(options)` → `{ pages, resolve(path), component(spec) }`
199
+ ```ts
200
+ shared: {
201
+ react: { singleton: true, strictVersion: true },
202
+ 'react-dom': { singleton: true, strictVersion: true },
203
+ }
204
+ ```
178
205
 
179
- - Data options (identical to Vue): `pages`, `remotePrefixes`, `deriveSpec`, `schema`, `strict`, `base`
180
- - 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
181
- - `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
182
- - 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)
183
- - 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
206
+ Remote `src/Button.tsx`:
184
207
 
185
- ### Async shared decisions and React version isolation (5.7.1)
208
+ ```tsx
209
+ import { useState } from 'react'
186
210
 
187
- HTML entries configured with `runtimePlugins` negotiate and load shared dependencies before dynamically executing the application. Remote containers prepare shared decisions before executing exposes. Sync facades reuse the same decision and instance, including async hooks selecting a lower version or an external entry. Vite 8 consumer facades remain synchronous; the bootstrap boundary keeps negotiation waits out of consumer dependency cycles.
211
+ export default function Button({ label }: { label: string }) {
212
+ const [count, setCount] = useState(0)
213
+ return <button onClick={() => setCount(count + 1)}>{label}: {count}</button>
214
+ }
215
+ ```
188
216
 
189
- Library/custom entries without HTML, and policies registered after application startup, must `await loadShare(name, opts)` before dynamically importing new consumers. Existing evaluated static bindings cannot be changed retroactively. An unprepared synchronous consumer still reports MFU-004 with `details.syncUnsupported`; late hook rejections are handled rather than becoming additional unhandled rejections. Local singleton adoption records the instance under its actual version, preserving strict version rejection. To run React 18 and 19 together, isolate React and its renderer in separate share scopes and bridge plain props/callbacks, not React elements or Context objects. See [the runnable version isolation demo](demo/react-versions/README.md).
217
+ Host `src/App.tsx`, with a remote configured as `remote-react`:
190
218
 
191
- ### 8.2 Runtime API — `@fulgurjs/federation/runtime` (Vue apps) and common functions on `/react`
219
+ ```tsx
220
+ import { remoteComponent } from '@fulgurjs/federation/react'
192
221
 
193
- | Function | Signature | Semantics |
194
- |---|---|---|
195
- | `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 |
196
- | `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`) |
197
- | `initSharing` | `(scopeName?: string) => ShareScopeMap` (default `'default'`) | creates/returns the share scope map (usually called for you by the injected init) |
198
- | `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 |
199
- | `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>` |
200
- | `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 |
201
- | `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) |
202
- | `getContainer` | `(name: string) => Promise<any>` | acquire the initialized container |
203
- | `getRuntime` | `() => FgRuntime` | the page-level runtime singleton (`globalThis.__FULGURJS_RUNTIME__`) |
204
- | `parseSpec` | `(spec: string) => { remote, module }` | synchronous spec parsing |
205
- | `shareScopeMap` | `ShareScopeMap` | live registry (debug surface: `window.__FULGURJS_SCOPE__`) |
206
- | `unwrapDefault` | `(ns: any) => any` | ESM/CJS default-interop helper |
207
- | `version` | `string` | plugin/runtime version |
208
- | `clearSessionState` | `() => void` | invalidate all remotes' session signals and onSession dedup state (called by `clearAppContext`) |
209
-
210
- 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).
211
-
212
- ### 8.3 Plugin options — `federation(options)`
213
-
214
- | Option | Type / default | Notes |
215
- |---|---|---|
216
- | `name` | `string`, **required** | container name; unique per page; `/^[a-zA-Z][\w.-]*$/` |
217
- | `exposes` | `Record<string, string \| { import, name? }>` | key normalized to `./Key`; stable chunk name optional |
218
- | `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) |
219
- | `shared` | `string[]` or `Record<string, string \| SharedHint>` | see below |
220
- | `setup` | `string` | module path; must default-export `setup(context)`, optional named `onSession(context)` |
221
- | `shareScope` | `string`, default `'default'` | default scope for provides |
222
- | `filename` | `string`, default `'fulgurjs-remoteEntry.js'` | fixed remoteEntry filename |
223
- | `manifest` | `boolean`, default `true` | emit `fulgurjs-manifest.json` |
224
- | `dts` | `boolean \| { dir?, mode?: 'source' \| 'shim' }`, default `true` | dev type generation (see §8.6) |
225
- | `devSharedSelf` | `boolean`, default inferred | pure remotes & dual-role apps: `true` (dev shared rewriting); pure hosts: `false` |
226
- | `devCorsOrigins` | `'*'` or `string[]` | dev endpoints + server.cors share the policy; explicit user `server.cors` wins |
227
- | `devFsRoot` | `boolean`, default `true` | dev manifest carries local fsRoot for type direct-connect; `false` → host falls back to `any` stubs |
228
- | `runtimePlugins` | `string[]` | modules default-exporting a `RuntimePlugin` |
229
-
230
- `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`.
231
-
232
- ### 8.4 Lifecycle — `setup` / `onSession`
222
+ // Create once at module scope, not on every render.
223
+ const RemoteButton = remoteComponent<{ label: string }>('remote-react/Button', {
224
+ fallback: <p>Loading…</p>,
225
+ })
233
226
 
234
- ```ts
235
- // federation({ setup: './src/fulgurjs/setup.ts' })
236
- export default async function setup(ctx: { appContext: Record<string, any>; sessionKey?: string; signal: AbortSignal }) {
237
- // app-level: once per app, before the first business module is returned
238
- }
239
- export async function onSession(ctx: { appContext: any; sessionKey: string; signal: AbortSignal }) {
240
- // session-level: once per host sessionKey (login generation); re-login re-runs, logout invalidates
227
+ export default function App() {
228
+ return <RemoteButton label="Remote React button" />
241
229
  }
242
230
  ```
243
231
 
244
- - Failures reject the triggering `loadRemote` (MFU-011/012) and are retryable; already-succeeded stages are not re-run
245
- - `signal` aborts on logout/session change — check `signal.aborted` before writing async results
246
- - `preloadRemote` / `getContainer` never trigger the lifecycle
247
- - Remote declares `onSession` → the host **must** provide a non-empty `sessionKey` (MFU-013); never use a token as sessionKey
248
- - No-setup remotes (plain public components) load normally without any context
232
+ React also imports `loadRemote` and `useLoadRemote` from `/react` for ordinary modules. Complete projects: [React examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/react).
249
233
 
250
- ### 8.5 AppContext — cross-app values
234
+ ## Embed Vue and React in each other
251
235
 
252
- - `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
253
- - `getAppContext()` — read the snapshot (`CC-002` if loaded outside the host federation)
254
- - `requireAppContext(...keys)` — validated read; missing keys → `CC-001` with got/expected/example
255
- - `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
256
- - Standard fields: `user`, `getToken()`, `store` (host pinia), `hostApp` (host Vue app), `locale`, `events`, `sessionKey` — plus arbitrary extension keys. Transport snapshot + function references; not reactive
236
+ **A bridge embeds a sub-app with its own component tree. It does not convert a React component into a Vue component.**
257
237
 
258
- ### 8.6 Dev types (dual-track)
238
+ For a Vue host embedding React:
259
239
 
260
- - Zero config: ambient declarations per expose — imports resolve, exports typed `any`; setup entry never generates declarations
261
- - Precise track: add `"paths": { "<remote>/*": ["<typesDir>/<remote>.d/*"] }` to the app's **effective TS context** — `tsconfig.json` itself, its `extends` chain, or a referenced sub-project whose `include` covers the app source / types output dir. Standalone `tsconfig.test.json`, `tsconfig.node.json` (vite.config only) and other unrelated configs do not affect the decision; 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
240
+ 1. React remote `src/bridge.tsx`:
262
241
 
263
- Type generation supports string or array `extends` (later entries override earlier entries) and directory `references`; inherited paths retain their declaring directory. `baseUrl` and `paths` inherit independently. If application contexts disagree on remote `paths`, the plugin keeps loose declarations and reports a diagnostic; align application mappings to enable precise types. Lifecycle errors (`MFU-012`) retain the original setup/onSession exception in `cause`.
264
- - `devFsRoot: false` or unreachable source: degrades to resolvable `any` declarations and cleans stale precise-track files (precise → degrade → restore cycles compile cleanly)
265
- - `dts: false` stops generation without deleting existing output; `dts.dir` relocates; `mode: 'shim'` gives loose IDE-clean placeholders
266
- - 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
242
+ ```tsx
243
+ import { defineBridgeApp } from '@fulgurjs/federation/react'
267
244
 
268
- ### 8.7 Cross-framework bridge — `/bridge` (sub-app-level Vue↔React, 5.3.0+)
245
+ export default defineBridgeApp((props) => (
246
+ <section>React sub-app: {String(props.message ?? '')}</section>
247
+ ))
248
+ ```
269
249
 
270
- **Scope**: whole-app mount/unmount embedding both ways — a Vue 3 host mounts a React 18/19 sub-app, and a React host mounts a Vue 3 sub-app. Component-level conversion, Angular, SSR/RSC, JS sandbox, CSS isolation are out of scope (§12). Sub-app internal route ↔ browser URL sync is available since 5.4.0 (§8.8).
250
+ 2. Add `exposes: { './bridge': './src/bridge.tsx' }` to the remote config.
251
+ 3. Configure the remote address in the Vue host, then use:
271
252
 
272
- #### Entries & import graph
253
+ ```vue
254
+ <script setup lang="ts">
255
+ import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
256
+ const RemoteApp = createVueBridgeApp<{ message: string }>('remote-react/bridge')
257
+ </script>
273
258
 
274
- ```text
275
- build @fulgurjs/federation -> the Vite plugin (unchanged)
276
- Vue sub-app @fulgurjs/federation/runtime -> defineBridgeApp (zero React)
277
- React sub-app @fulgurjs/federation/react -> defineBridgeApp (zero Vue; react-dom/client loads at mount time)
278
- bridge host @fulgurjs/federation/bridge/vue -> createVueBridgeApp (recommended for Vue hosts; zero React)
279
- @fulgurjs/federation/bridge/react -> createReactBridgeApp (recommended for React hosts; zero Vue)
280
- @fulgurjs/federation/bridge -> aggregate (kept for compatibility; dev native ESM executes both host adapters)
259
+ <template>
260
+ <RemoteApp :app-props="{ message: 'From Vue host' }" />
261
+ </template>
281
262
  ```
282
263
 
283
- **The split entries are the recommended usage**: a Vue host that only uses `createVueBridgeApp` never executes the React host adapter — in dev native ESM and in the production bundle (asserted by e2e request graphs). The aggregate `/bridge` tree-shakes in production but has no such guarantee in dev.
264
+ A cross-framework host installs and shares `vue`, `react` and `react-dom`. The child installs and shares its own framework. React and react-dom must be compatible; multiple React majors need separate dependency groups and consumers, as shown in the isolation demo.
284
265
 
285
- **Dual-framework install contract (required)**: the bridge host installs `vue` + `react` + `react-dom` and configures all three as `singleton: true` in `shared`. Sub-apps install and share only their own framework. Pure single-framework projects are unaffected. Missing singletons is a usage violation — the plugin runs the negotiation mechanism honestly (double-instance symptoms such as Invalid hook call are documented, not intercepted).
266
+ In the other direction, use `createReactBridgeApp` in the React host. The Vue child uses `defineBridgeApp` from `/runtime` and returns a `createApp(...)` application.
286
267
 
287
- #### Sub-app side: `defineBridgeApp` (`/runtime` and `/react`, same name)
268
+ Remember:
288
269
 
289
- The remote's `./bridge` expose module **default-exports** the contract object; the plugin validates that `mount`/`unmount` are functions (`MFU-015` otherwise).
270
+ - `appProps` is a snapshot taken at mount. Replacing top-level fields later does not update the child. Pass stable callbacks/shared stores for live data, or change the component `key` to remount.
271
+ - Separate component trees do not inherit Context, provide/inject or routers. Pass or install what is needed explicitly.
272
+ - Use `remoteComponent` for a same-framework component; use a bridge for a sub-app.
290
273
 
291
- ```ts
292
- // Vue sub-app src/bridge.ts
293
- import { createApp } from 'vue'
294
- import { createMemoryHistory, createRouter } from 'vue-router'
295
- import { defineBridgeApp } from '@fulgurjs/federation/runtime'
296
- export default defineBridgeApp((props) => {
297
- const app = createApp(App, props)
298
- app.use(createRouter({ history: createMemoryHistory(), routes }))
299
- return app
300
- })
301
- ```
274
+ Complete bidirectional setup and login/cleanup flows: [bridge examples](https://github.com/chenmingye/fulgurjs-federation/tree/master/examples/bridge).
302
275
 
303
- ```tsx
304
- // React sub-app src/bridge.tsx
305
- import { MemoryRouter } from 'react-router-dom'
306
- import { defineBridgeApp } from '@fulgurjs/federation/react'
307
- export default defineBridgeApp((props) => <MemoryRouter><App {...props} /></MemoryRouter>)
276
+ ## Keep child routes in the browser URL
277
+
278
+ Bridging does not change the host URL by default. Enable URL sync to map:
279
+
280
+ ```text
281
+ Host /approval/list → Child /list
282
+ Host /approval/detail/42 → Child /detail/42
308
283
  ```
309
284
 
310
- Contract semantics (`BridgeApp`):
311
- - `mount(el, props?): void | Promise<void>` — returning `void` means the first root commit completed synchronously (Vue); a Promise keeps the host pending until the first root commit completes (React uses a built-in commit probe; `root.render()` returning does **not** count as success). Failure before the first commit must throw/reject (host turns it into `MFU-016`, `details.phase: 'mount'`) after cleaning up any created root.
312
- - `unmount(el): void` — synchronously invalidates the current generation for that container and cleans up; unknown containers are a no-op. Unmounting while pending immediately invalidates the in-flight generation: late results must not revive DOM, overwrite host state, or produce unhandled rejections. An `unmount` throw is reported as `MFU-016` (`phase: 'unmount'`); the container's cleanup state is uncertain, and the plugin **permanently blocks that container**: neither the in-page retry nor a session change will mount a new instance there (the default placeholder removes its retry button), so a full page reload is the only recovery; audit leftover resources (subscriptions/timers/global side effects) honestly.
313
- - Contract instances are keyed **per container element**; double-mount on the same container is rejected (`MFU-016`).
314
- - Errors inside the sub-app after the first commit belong to **the sub-app's own error boundary** — host boundaries cannot catch cross-root render errors.
285
+ Configure both sides:
315
286
 
316
- #### Host side: `createVueBridgeApp` / `createReactBridgeApp`
287
+ 1. The host router must handle all child paths under `/approval` without unmounting the child on each detail navigation.
288
+ 2. Pass `routing` to the host bridge component, including `basePath: '/approval'` and the host navigation adapter.
289
+ 3. The child declares `defineBridgeApp(..., { routing: true })` and connects a controlled memory router.
317
290
 
318
- ```ts
319
- // Vue host
320
- import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
321
- const RemoteReactApp = createVueBridgeApp('bridge-react-remote/bridge', {
322
- retries: 1,
323
- getContext: () => getLatestHostContext(), // your own synchronous pure getter
324
- })
325
- // <RemoteReactApp :session-key="loginKey" :app-props="{ userId, onReady }" />
326
- ```
291
+ Vue uses `createVueBridgeNavigation` / `connectVueBridgeRouter`; React uses `createReactBridgeNavigation` / `createReactBridgeRouter`. React hosts need a data router (`createBrowserRouter` or `createHashRouter`), not `BrowserRouter`. Built-in adapters support Vue Router 4 and React Router ≥6.11.
327
292
 
328
- ```tsx
329
- // React host
330
- import { createReactBridgeApp } from '@fulgurjs/federation/bridge/react'
331
- const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', { getContext: () => getLatestHostContext() })
332
- // <RemoteVueApp sessionKey={loginKey} appProps={{ userId, onReady }} />
333
- ```
293
+ Refresh, shared links and browser history restore the route, **not form contents or business data**. See [routing API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#url-sync) and the runnable [router demo](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/bridge-router).
334
294
 
335
- | Item | `createVueBridgeApp` | `createReactBridgeApp` |
336
- |---|---|---|
337
- | Factory options | `loadingComponent?` `errorComponent?` (receives `error`; full takeover) `retries?` (0–10) `timeout?` `getContext?` | `fallback?` `error?` (node or `(error, retry) => ReactNode`) `retries?` `timeout?` `getContext?` |
338
- | Component props | `appProps: P` + `sessionKey?: string \| null` (control prop, never mixed into business props) | same |
339
- | Error placeholder | Chinese diagnostic (code + root cause + fix) with retry / full-reload buttons | same |
295
+ ## User data and remote initialization
296
+
297
+ These features are optional. A plain button or utility module does not need them.
340
298
 
341
- - **`appProps` snapshot**: shallow-copied top-level fields at mount time; nested objects, reactive stores and functions keep their original references. Later top-level replacements are not tracked (use `:key` / React `key` to remount). Cross-root inheritance (Vue provide/inject, Pinia, React Context, routers) does not happen — pass what is needed explicitly.
342
- - **`getContext`**: a synchronous, side-effect-free getter called before each actual load (first load, retry, session switch). Non-object/thenable returns → `MFU-016` (`phase: 'getContext'`). The bridge validates the snapshot's `sessionKey` against the controlled value (`MFU-017` on mismatch, without writing global state), then writes `provideAppContext` itself. On generation change the bridge clears the previous account context first (zero residue).
343
- - **Controlled `sessionKey`**: accepts `undefined` (no controlled validation) / `null` (logged out: unmount immediately, keep the container empty, stop loading) / non-empty string (login generation). Illegal values → `MFU-017`.
344
- - **Multi-instance**: several same-spec instances coexist (per-el keying); `AppContext` is a page-level singleton — all controlled instances on a page must share the same session (`MFU-017` otherwise). React StrictMode double-effect is safe. Vue `<KeepAlive>` deactivation is **not** an unmount. Late results from invalidated generations are dropped by generation guards; a remote `onSession` must honor the existing `signal.aborted` contract.
299
+ | Need | API | When |
300
+ |---|---|---|
301
+ | Provide user, token getter, store, etc. | `provideAppContext` | Host supplies them before loading business modules |
302
+ | Read host values | `getAppContext` / `requireAppContext` | Called by remote business code |
303
+ | Initialize a remote once | Default export in configured `setup` file | Before the first business `loadRemote('remote/module')` returns |
304
+ | Synchronize permissions after login/account changes | Named `onSession` export in the same file | Deduplicated by `sessionKey` |
305
+ | Clear account context on logout | `clearAppContext` | Host logout flow; host also removes private pages/caches |
345
306
 
346
- ### 8.8 Bridge URL sync — `/bridge/router/*` (sub-app internal routes ↔ browser URL, 5.4.0+)
307
+ `sessionKey` identifies a login attempt; it is **not a token or authorization credential**. Generate a new value on login/account change; token refresh alone retains it.
347
308
 
348
- The bridge defaults to memory routing: internal navigation does not touch the browser URL and refresh cannot restore the sub-app's internal page. URL sync makes the **host URL express the sub-app's internal location** — deep links, refresh, bookmarks, back/forward and host-menu navigation all agree. It is opt-in; **default is off** (5.3.x behavior and legacy contracts unchanged).
309
+ A bridge can read current data using `getContext`. Controlled `sessionKey: null` means logged out: unmount and stop loading. Omitting the key disables controlled session switching.
349
310
 
350
- **Architecture**: the host router is the only writer of browser history; the sub-app uses a controlled memory router; both sides communicate over a dedicated routing channel (not appProps/Context); path/search/hash changes within an instance **do not remount the root, do not rebuild stores, do not reload the remote**.
311
+ Only a configured `setup` file participates in initialization. `preloadRemote` fetches resources without running setup/onSession. Async initialization must check `context.signal.aborted` before writing state, so late responses do not restore old-account data.
351
312
 
352
- **Host (Vue Router 4, history or hash mode)**: declare a suffix route (`/approval/:pathMatch(.*)*` — without it detail navigation unmounts the sub-app), add real guards (`beforeEach` rejecting → the channel receives `cancelled`, URL/history/sub-app position unchanged), then `createVueBridgeNavigation(router)` (Vue Router already removes its history base) and pass `routing={{ basePath: '/approval', navigation }}` to the bridge component.
313
+ See the [API reference](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#context).
353
314
 
354
- **Sub-app**: declare the protocol and wire a controlled router —
355
- Vue: `defineBridgeApp(async (props, ctx) => { const router = createRouter({ history: createMemoryHistory(), routes }); await connectVueBridgeRouter(ctx.routing!, router).ready; ... app.use(router); return app }, { routing: true })` (await ready BEFORE `app.use(router)` — the install-time initial navigation would otherwise override the deep-link location).
356
- React: `createReactBridgeRouter(ctx.routing!, routes).element` — `createMemoryRouter`-based; `Link`/`useNavigate` work unmodified.
315
+ ## Several remote pages
357
316
 
358
- **Host (React Router)**: data routers only (`createBrowserRouter`/`createHashRouter` + `RouterProvider`); `createReactBridgeNavigation(router, { basename, canNavigate })`. `canNavigate` is an optional early rejection policy. The adapter also observes the real blocker: it waits for `reset()` (cancelled) or a committed navigation after `proceed()`. Resolving `router.navigate()` alone does not imply a commit. Declarative `BrowserRouter` has no cancellation semantics and is not supported. Requires react-router ≥ 6.11.
317
+ Maintain a page table and pass it to `createHostPages` (Vue) or `createReactHostPages` (React). These helpers resolve modules, cache loading components and provide loading/error states. **They do not create your host Router.**
359
318
 
360
- **Lifecycle and navigation**: both child connectors accept an optional third argument `{ signal?: AbortSignal }`; pass `ctx.signal` to dispose on session invalidation, or call `connection.dispose()` yourself. Push and replace retain their history action; numeric navigation delegates to the host history. Concurrent requests are serialized and superseded requests are invalidated. Navigation errors reject with MFU-033 and preserve `cause`; they are not reported as cancellation. Custom host ports receive an optional third argument `{ signal }` and must check it before asynchronous commits.
319
+ The table records the host `route` and the remote expose `spec` (omit `./` and do not repeat the remote name); `remotePrefixes` selects the remote. For example, `/shop/home`, `spec: 'pages/Home'` and `remotePrefixes: { '/shop': 'shop' }` resolve to `shop/pages/Home`. Vue can use KeepAlive for component state; React has no equivalent keep-alive promise here.
361
320
 
362
- **Contract highlights**: `basePath` is a static absolute path from the host-router perspective (segment-matched; conflicting/overlapping prefixes rejected, `MFU-030`); location is compared and preserved as three raw strings (duplicate query keys, encoding, fragments survive without re-encoding); cancellation never auto-retries; session switch (`sessionKey→null`) invalidates the old channel — late navigations are rejected and never write the URL; KeepAlive-cached instances pause routing writes; enabling sync against a contract without `{ routing: true }` shows `MFU-031` instead of silently falling back to memory; escaping targets and illegal `go` arguments → `MFU-032`; redirect loops beyond 5 internal replaces → `MFU-033` with the chain attached. Router libraries are optional peers consumed only through the two opt-in entries (`/bridge/router/vue`, `/bridge/router/react`, each gated ≤ 4096B gzip); the default entries never load a router library. Not promised: SSR/RSC, cross-window, nested multi-level bridge routing proxies, TanStack Router and other libraries (extend via the `BridgeHostNavigation`/`BridgeChildRoute` ports).
321
+ See [page API](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#pages) and [page demo](https://github.com/chenmingye/fulgurjs-federation/tree/master/demo/pages-cli).
363
322
 
364
- ## 9. Artifacts, endpoints & caching
323
+ ## Build and deploy
365
324
 
366
- | Artifact | Cache policy |
367
- |---|---|
368
- | `fulgurjs-remoteEntry.js` (fixed filename, content changes every build) | **`no-cache`** |
369
- | content-hashed chunks / CSS | `immutable` long cache |
370
- | `fulgurjs-manifest.json` | `no-cache` (consumed by `preloadRemote` / `check-pages` / `doctor`) |
371
- | dev endpoints `/@fulgurjs-entry.js` / `/@fulgurjs-manifest.json` | `no-cache`, CORS per `devCorsOrigins` |
325
+ Build each application separately with its own `npm run build`. The remote produces `fulgurjs-remoteEntry.js` and `fulgurjs-manifest.json` by default. The host locates them through `prod`.
372
326
 
373
- 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.
327
+ Check these settings:
374
328
 
375
- ## 10. Debugging surfaces
329
+ - Remote deployment `/remote-vue/` → remote Vite `base: '/remote-vue/'` and host `prod: '/remote-vue'`.
330
+ - HTML, remoteEntry and manifest use `Cache-Control: no-cache`; content-hashed chunks can use long-lived caching.
331
+ - SPA routes support refresh; missing resource URLs return 404 rather than HTML.
332
+ - Cross-origin deployments need production CORS headers; dev settings do not configure the production server.
333
+ - Keep chunks still referenced by old pages available during releases, or use a deployment flow that avoids mixed versions.
376
334
 
377
- - `window.__FULGURJS_SCOPE__` — live share-scope registry
378
- - `window.__FULGURJS_INFO__` — per-remote status/latency/errors + `errors` log
379
- - `DEBUG=fulgurjs:*` — controlled pipeline diagnostics (off by default)
380
- - Runtime diagnostics are emitted in Chinese by design (language policy); codes are stable identifiers listed below
335
+ Deployment examples: [Vue](https://github.com/chenmingye/fulgurjs-federation/blob/master/examples/vue/README.md) / [React](https://github.com/chenmingye/fulgurjs-federation/blob/master/examples/react/README.md).
381
336
 
382
- ## 11. Error codes (48)
337
+ ## Handle failures
383
338
 
384
- | Segment | Code | Meaning |
339
+ | Symptom | Check | Recovery |
385
340
  |---|---|---|
386
- | CFG | `CFG-001` | name missing or invalid |
387
- | | `CFG-002` | exposes shape invalid |
388
- | | `CFG-003` | remotes shape invalid / illegal key characters |
389
- | | `CFG-004` | shared shape invalid |
390
- | | `CFG-005` | remotes key collides with a shared key |
391
- | | `CFG-006` | island config (neither provides nor consumes) |
392
- | | `CFG-007` | `name@` prefix misuse in object-form remotes |
393
- | | `CFG-008` | shared illegal combo (eager+import:false / duplicate shareKey) |
394
- | | `CFG-009` | remote runtime params invalid (timeout/retries/breaker) |
395
- | | `CFG-010` | devCorsOrigins invalid |
396
- | | `CFG-011` | removed no-op option (any value errors with migration hints) |
397
- | | `CFG-012` | setup config invalid / reserved expose key squatted |
398
- | DEV | `DEV-001` | remote dev server unreachable (manifest fetch failed) |
399
- | | `DEV-002` | remote dev manifest empty or unrecognized |
400
- | | `DEV-004` | known UMD-only dep missing from optimizeDeps.include |
401
- | | `DEV-005` | remotes dev URL port not listening |
402
- | | `DEV-006` | host/remote plugin version mismatch |
403
- | | `DEV-009` | facade/virtual module 404 (.vite cache drift — clear and restart) |
404
- | | `DEV-010` | dev cold-start pre-bundle window notice (transient) |
405
- | | `DEV-011` | non-loopback host + wildcard dev CORS reminder |
406
- | | `DEV-012` | non-loopback host + fsRoot disclosure reminder |
407
- | BLD | `BLD-001` | expose source resolution failed |
408
- | | `BLD-002` | build target below es2022 |
409
- | | `BLD-003` | expose target declares required props (documented checklist) |
410
- | | `BLD-006` | array-form output prevents automatic facade chunk isolation |
411
- | MFU | `MFU-001` | remote container/module load failure (network / timeout / retries exhausted / breaker) |
412
- | | `MFU-002` | remoteEntry self-reported name mismatch |
413
- | | `MFU-003` | strictVersion requirement not satisfied |
414
- | | `MFU-004` | shared module missing with no local fallback |
415
- | | `MFU-005` | same container re-initialized with a different share scope |
416
- | | `MFU-006` | requested module not exposed by the remote |
417
- | | `MFU-007` | preload failed (non-blocking) |
418
- | | `MFU-008` | unknown remote |
419
- | | `MFU-009` | loaded module has no exports at all |
420
- | | `MFU-010` | reused singleton version doesn't satisfy the consumer requirement (warn-once) |
421
- | | `MFU-011` | setup entry export shape invalid |
422
- | | `MFU-012` | setup/onSession threw (retryable; only the failed stage resets) |
423
- | | `MFU-013` | onSession declared but host sessionKey missing |
424
- | | `MFU-014` | setup/onSession synchronously re-loading the same remote (deadlock guard) |
425
- | | `MFU-015` | bridge contract invalid (`./bridge` default export missing non-function mount/unmount; fix points to `defineBridgeApp`) |
426
- | | `MFU-016` | bridge preparation or lifecycle failure (`details.phase` = getContext/mount/unmount; cause keeps the sub-app's original error) |
427
- | | `MFU-017` | bridge session mismatch (controlled sessionKey vs AppContext / illegal value / page-level single-session conflict) |
428
- | MFU | `MFU-030` | Bridge URL-sync config invalid / prefix conflict (illegal basePath: empty, root, query/hash/wildcard; overlapping active prefixes) |
429
- | MFU | `MFU-031` | Bridge routing protocol missing / channel destroyed (sub-app not declared with `{ routing: true }`; disposed channel reused) |
430
- | MFU | `MFU-032` | Bridge illegal navigation (target escaping its own prefix, illegal `go` argument, request on a dead channel) |
431
- | MFU | `MFU-033` | Bridge routing preparation/sync failed (redirect limit or navigation exception, chain/cause attached; no silent fallback to memory) |
432
- | CC | `CC-001` | AppContext required key missing (got/expected/example) |
433
- | | `CC-002` | runtime singleton unavailable (standalone remote page) |
434
-
435
- ## 12. Boundaries (explicitly not supported)
436
-
437
- - Support covers **browser-client** federation for Vue 3 and React 18–19. Not supported: SSR, React Server Components, Next.js full-stack, React Native, Node-side remote loading. **Cross-framework boundary (5.3.0+)**: sub-app-level embedding is supported (§8.7 `/bridge`); direct component-level Vue↔React rendering in one tree is not (that is the product of framework-conversion libraries). Pure single-framework projects keep zero cross-dependency
438
- - **Bridge isolation boundary (declared honestly in §8.7)**: bridging isolates only the mount/unmount edge of the two component trees — no browser realm isolation. Remote global CSS, `body`/`html` styles, global variables, and DOM rendered outside the container via React Portal / Vue Teleport still affect the host; `unmount` cannot revoke CSS the browser already loaded. Sub-app internal errors do not bubble into host error boundaries (cross-root). Sub-app routing defaults to memory mode; explicit URL sync exists since 5.4.0 (§8.8) — when it is not enabled, refreshing does not restore the sub-app's internal path
439
- - React side does not promise component keep-alive (`keepAliveNames` is Vue-only); re-opened pages still reuse downloaded modules
440
- - 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
441
- - Not compatible with originjs `virtual:__federation__` legacy imports
442
- - No browser DevTools extension (the `window.__FULGURJS_*` surfaces serve debugging)
443
-
444
- ## 13. Documentation & examples
445
-
446
- - [Migration guide (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/迁移指南.md) — a real qiankun → federation migration (seven steps + acceptance checklist)
447
- - [webpack MF comparison & gaps (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/webpack-mf-对照与缺口.md)
448
- - [Sandbox boundary audit (Chinese)](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/沙箱边界审计.md)
449
- - [`DESIGN.md`](https://github.com/chenmingye/fulgurjs-federation/blob/master/DESIGN.md) — architecture and alignment tables
450
- - Examples: [`examples/vue/{host,remote}`](./examples) + [`examples/react/{host,remote}`](./examples) + [`examples/bridge/*`](./examples) — copy-and-run projects, registry-installable (see the examples entry page)
451
-
452
- ## 14. Development & testing
341
+ | Remote unavailable | Server, address, CORS | Timeout/retry/error UI; optional backup entry or fallback module |
342
+ | Module missing | remotes name and exposes key | Fix the name and retry |
343
+ | Shared version incompatible | Installed versions, requiredVersion, strictVersion, scope | Align or isolate versions |
344
+ | Static dependency remains failed after service recovery | Browser may retain the failed dependency URL | User-initiated refresh preserves the current address |
345
+ | Child unmount fails | Child cleanup, timers and subscriptions | Container stays blocked; refresh and fix cleanup |
346
+
347
+ `remoteComponent` and bridge components provide default error UI. Direct `loadRemote` calls and React `useLoadRemote` require application error handling. An explicit `fallbackModule` does not repair the original remote.
348
+
349
+ Errors include a code, cause and fix. See [error codes](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#error-codes).
350
+
351
+ ## Vite 8 and support boundaries
352
+
353
+ **Supports Vite 8 development and production. The earlier large-application startup hang has been fixed and relevant regression tests pass.**
354
+
355
+ Two practical details:
356
+
357
+ - A first dev visit may reload while Vite prepares newly discovered dependencies. Wait for optimization before judging stable behavior. This is not a production behavior on every visit.
358
+ - Some shared scenarios fetch an unused local library copy. One singleton scope still uses one instance; explicitly isolated React 18/19 scopes may use one each. Downloaded file count and active instance count are different.
359
+
360
+ Not provided: SSR/RSC, Node-side federation, React Native, automatic JS/CSS isolation, webpack `script/var` artifact interoperability, component-type conversion, automatic multi-level bridge routing proxies or cross-window route sync. Global CSS/variables can affect the host; children need their own internal error handling.
361
+
362
+ See the [capability comparison](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/webpack-mf-对照与缺口.md) for detailed boundaries and differences from webpack.
363
+
364
+ ## Debugging, types and CLI
365
+
366
+ Run in the application directory:
453
367
 
454
368
  ```bash
455
- pnpm --dir packages/plugin install && pnpm --dir packages/plugin build
456
- for app in fixtures/host-vue fixtures/remote-a fixtures/remote-b fixtures/remote-auto fixtures/host-auto fixtures/remote-react fixtures/host-react e2e; do pnpm --dir "$app" install; done
457
- pnpm --dir e2e exec playwright install chromium
458
-
459
- pnpm test:unit # full unit suite
460
- pnpm test:dev # Vue + React: dev and fault (four projects)
461
- pnpm test:prod # Vue + React: prod (two projects), isolated NGINX; cleans up after tests
462
- pnpm test # unit + all dev/fault + all prod projects
463
- pnpm --dir e2e exec playwright test --list # inspect unique cases and project ownership
464
- node e2e/scripts/pack-smoke.mjs # local tarball consumer checks; not registry acceptance
465
- node e2e/scripts/react-types-check.mjs # dual-track dev types + negative matrix
466
- node e2e/scripts/react-negative-check.mjs # N08/N10/N11 negative checks
467
- bash e2e/scripts/prod-setup.sh # build all fixtures + isolated NGINX
369
+ npx fulgurjs init
370
+ npx fulgurjs explain
371
+
372
+ # Optional: validate a configured host page table
373
+ npx fulgurjs check-pages --site http://localhost:5173
374
+
375
+ # After deployment under /remote-vue/, substitute your actual site:
376
+ npx fulgurjs doctor --base https://your-site.example --apps remote-vue
468
377
  ```
469
378
 
470
- 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).
379
+ For `doctor`, `--base` is the site URL and `--apps` lists deployment subdirectories: the example checks `/remote-vue/`. It does not infer a different development port from a container name.
380
+
381
+ `init` creates a federation config template, not a full application, router or Nginx configuration. `check-pages` compares the page table with remote exposes; an unreachable remote is reported as unverified.
382
+
383
+ Remote dev types are generated by default. Accessible source provides more precise mapping; inaccessible source produces `any` declarations without precise checks/completion. Set `dts: false` to disable generation. See the reference for details.
384
+
385
+ Advanced diagnostics use `window.__FULGURJS_SCOPE__`, `window.__FULGURJS_INFO__` and `FULGURJS_DEBUG`. Normal integration does not require editing these objects.
386
+
387
+ ## API reference
388
+
389
+ Use the current reference rather than guessing signatures from old task documents:
390
+
391
+ - [Plugin options and defaults](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#plugin-options)
392
+ - [Runtime loading, registration and hooks](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#runtime)
393
+ - [Bridge props, sessions and cleanup](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#bridge)
394
+ - [URL sync and navigation](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.en.md#url-sync)
395
+ - [Chinese API reference](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/API.md)
396
+
397
+ ### When an AI implements your integration
398
+
399
+ Specify the framework, whether you need a component or sub-app, remote URLs/expose names, and whether login switching or URL sync is required. Have it read the guide and relevant API section first, preserve the existing Vite configuration, check installed versions and use the correct browser entry. It should not invent configuration fields. Verify mounting, interaction and error handling; URL sync also needs deep-link refresh, history and cancellation checks.
400
+
401
+ ## Documentation
402
+
403
+ - [Demo catalog](https://github.com/chenmingye/fulgurjs-federation/blob/master/demo/README.md): setup and runnable scenarios.
404
+ - [Copy-and-run templates](https://github.com/chenmingye/fulgurjs-federation/tree/master/templates): five pnpm-workspace templates (Vue×Vue, React×React, both cross-framework bridge directions, and a full showcase). Copy a folder, then `pnpm install && pnpm dev`.
405
+ - [Migration guide](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/迁移指南.md).
406
+ - [CHANGELOG](https://github.com/chenmingye/fulgurjs-federation/blob/master/CHANGELOG.md): changes and migration requirements.
407
+ - [Acceptance records](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/整夜全量验收报告-20261004.md): overnight acceptance on two real MES business projects (fresh SVN copies), covering dev, production, fault recovery and HMR, plus production-build notes for large Vite 6 apps (that round required disabling `manualChunks`; **fixed in 5.8.0 — keep your own `manualChunks`, shared bodies are isolated into `fulgurjs-provider-*` chunks automatically**). Historical record: [20261002 demo acceptance](https://github.com/chenmingye/fulgurjs-federation/blob/master/docs/完整Demo展示与全面复测-验收报告-20261002.md) — historical results are not a substitute for testing your application.
408
+
409
+ ## Development and testing
410
+
411
+ These commands develop **this plugin repository**; ordinary consumers do not need them:
412
+
413
+ ```bash
414
+ pnpm --dir packages/plugin install
415
+ pnpm --dir packages/plugin build
416
+ pnpm test:unit
417
+ ```
471
418
 
472
- The known dual-client error-overlay case is skipped only on the reproduced Vite 5.1.4 version and reported as skipped, never passed. Other Vite 5 versions still execute it. Fixture tests do not replace final registry-package testing in a real application's development and production environments.
419
+ See [CONTRIBUTING](https://github.com/chenmingye/fulgurjs-federation/blob/master/CONTRIBUTING.md) for fixture installation and browser test prerequisites. CI checks builds, types, unit tests, installed packages and browser scenarios across multiple Vite versions. Counts come from the corresponding run.
473
420
 
474
421
  ## License
475
422