@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/CHANGELOG.md +9 -0
- package/README.en.md +220 -305
- package/README.md +1 -1
- package/dist/index.cjs +171 -26
- package/dist/index.js +171 -26
- package/dist/react-adapter.cjs +8 -5
- package/dist/react-adapter.js +8 -5
- package/dist/runtime.js +1 -1
- package/examples/react-host/src/federation/pages.data.ts +2 -0
- package/package.json +1 -1
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
|
-
  
|
|
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
|
-
|
|
|
22
|
-
|
|
|
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
|
-
##
|
|
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
|
|
28
|
-
- **Automatic async boundaries** — top-level await injected automatically (es2022+)
|
|
29
|
-
- **Stable artifacts** — remoteEntry keeps a fixed filename
|
|
30
|
-
- **Fault tolerance (
|
|
31
|
-
- **
|
|
32
|
-
- **Enhancements** — dev type generation (
|
|
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);
|
|
35
|
-
- **CLI
|
|
36
|
-
- **Optional remote init lifecycle** — `federation({ setup })`:
|
|
37
|
-
- **
|
|
38
|
-
- **Cross-app
|
|
39
|
-
- **Vue direct rendering** — `remoteComponent('remote/X')` on
|
|
40
|
-
- **Full React support (browser)** — dedicated `@fulgurjs/federation/react` entry: `remoteComponent
|
|
41
|
-
- **CSP friendly** —
|
|
42
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
58
|
+
## 4. Project shape: two files per app
|
|
55
59
|
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
100
|
+
import { pages, remotePrefixes } from './src/federation/pages.data'
|
|
92
101
|
|
|
93
|
-
// ① Component
|
|
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
|
|
108
|
+
// ② Plain module — generation-guarded hook
|
|
99
109
|
type Utils = { formatMoney(v: number, currency?: string): string }
|
|
100
110
|
|
|
101
|
-
// ③ Page table
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
+
## 6. Quick start — Vue
|
|
181
121
|
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
import federation from '@fulgurjs/federation'
|
|
126
|
+
## 7. CLI
|
|
188
127
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
+
## 8. API reference
|
|
239
140
|
|
|
240
|
-
|
|
141
|
+
### 8.1 `@fulgurjs/federation/react` — React entry
|
|
241
142
|
|
|
242
|
-
|
|
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
|
-
|
|
145
|
+
#### `remoteComponent<Props>(spec, options?)` → `ComponentType<Props & { ref? }>`
|
|
245
146
|
|
|
246
|
-
| Option | Type
|
|
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
|
|
251
|
-
| `timeout` | `number` (ms), default none | wait cap for this component load;
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
#### `
|
|
175
|
+
#### `createReactHostPages(options)` → `{ pages, resolve(path), component(spec) }`
|
|
273
176
|
|
|
274
|
-
|
|
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
|
-
|
|
183
|
+
### 8.2 Runtime API — `@fulgurjs/federation/runtime` (Vue apps) and common functions on `/react`
|
|
277
184
|
|
|
278
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
+
### 8.5 AppContext — cross-app values
|
|
289
243
|
|
|
290
|
-
|
|
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
|
-
|
|
250
|
+
### 8.6 Dev types (dual-track)
|
|
293
251
|
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
297
|
-
-
|
|
298
|
-
-
|
|
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
|
-
|
|
258
|
+
## 9. Artifacts, endpoints & caching
|
|
301
259
|
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
276
|
+
## 11. Error codes (41)
|
|
318
277
|
|
|
319
278
|
| Segment | Code | Meaning |
|
|
320
279
|
|---|---|---|
|
|
321
|
-
| CFG
|
|
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
|
|
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
|
|
331
|
-
| | `CFG-011` | removed
|
|
332
|
-
| | `CFG-012` | setup config invalid
|
|
333
|
-
| DEV
|
|
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
|
|
339
|
-
| | `DEV-010` | dev cold-start pre-bundle window notice (
|
|
340
|
-
| | `DEV-011` | non-loopback host + wildcard dev CORS
|
|
341
|
-
| | `DEV-012` | non-loopback host + fsRoot
|
|
342
|
-
| BLD
|
|
343
|
-
| | `BLD-002` | build target below es2022
|
|
344
|
-
| | `BLD-003` | expose target declares required props (documented checklist
|
|
345
|
-
| | `BLD-006` | array-form output prevents automatic facade chunk isolation
|
|
346
|
-
| MFU
|
|
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` |
|
|
356
|
-
| | `MFU-011` | setup entry export shape invalid
|
|
357
|
-
| | `MFU-012` | setup/onSession threw (
|
|
358
|
-
| | `MFU-013` |
|
|
359
|
-
| | `MFU-014` | setup/onSession synchronously re-loading the same remote (
|
|
360
|
-
| CC
|
|
361
|
-
| | `CC-002` | runtime singleton unavailable (remote page
|
|
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
|
|
408
|
-
- React side does not promise component keep-alive
|
|
409
|
-
- Cross-origin Fast Refresh: remote React components update
|
|
410
|
-
- Not compatible with originjs
|
|
411
|
-
- No
|
|
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
|
|
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
|
-
- [
|
|
421
|
-
- [`
|
|
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
|
|
431
|
-
pnpm test:dev
|
|
432
|
-
pnpm test:prod
|
|
433
|
-
node e2e/scripts/react-types-check.mjs #
|
|
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
|
|
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
|
|