@fulgurjs/federation 5.9.3 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.en.md +57 -416
- package/README.md +56 -446
- package/dist/bridge-app-vue.d.cts +1 -1
- package/dist/bridge-app-vue.d.ts +1 -1
- package/dist/bridge-core.cjs +1 -1
- package/dist/bridge-core.js +2 -2
- package/dist/bridge-errors.cjs +1 -1
- package/dist/bridge-errors.js +1 -1
- package/dist/bridge-host-react.cjs +1 -1
- package/dist/bridge-host-react.js +3 -3
- package/dist/bridge-host-vue.cjs +1 -1
- package/dist/bridge-host-vue.js +3 -3
- package/dist/bridge-router-react.d.ts +4 -2
- package/dist/bridge-router-react.js +95 -9
- package/dist/bridge-router-vue.d.ts +24 -5
- package/dist/bridge-router-vue.js +4 -5
- package/dist/{chunk-YKIFZIBH.js → chunk-77JZ6U6V.js} +1 -1
- package/dist/{chunk-Q6V5O5Y6.js → chunk-ENT3BOTX.js} +1 -1
- package/dist/{chunk-WRJG2YG6.js → chunk-ZAYNS7YQ.js} +1 -1
- package/dist/cli.js +460 -109
- package/dist/index.cjs +25 -38
- package/dist/index.js +25 -38
- package/dist/react.d.ts +194 -101
- package/dist/react.js +4 -0
- package/dist/runtime-entry.d.ts +9 -174
- package/dist/runtime-entry.js +0 -1
- package/dist/runtime.js +1 -1
- package/dist/vue.d.ts +576 -0
- package/dist/vue.js +8 -0
- package/docs/en/migration.md +210 -0
- package/docs/en/reference/api.md +534 -0
- package/docs/en/reference/errors.md +86 -0
- package/docs/{P5-vite7-8 → maintainers/P5-vite7-8}/345/205/274/345/256/271/347/237/251/351/230/265.md +7 -9
- package/docs/{webpack-mf- → maintainers/webpack-mf-}/345/257/271/347/205/247/344/270/216/347/274/272/345/217/243.md +10 -10
- package/docs/maintainers//346/262/231/347/256/261/350/276/271/347/225/214/345/256/241/350/256/241.md +43 -0
- package/docs/zh/migration.md +210 -0
- package/docs/zh/reference/api.md +533 -0
- package/docs/zh/reference/errors.md +86 -0
- package/examples/templates/README.md +1 -1
- package/examples/templates/react-host-vue-remote/pnpm-workspace.yaml +1 -1
- package/examples/templates/react-host-vue-remote/react-host/README.md +2 -2
- package/examples/templates/react-host-vue-remote/react-host/package.json +1 -1
- package/examples/templates/react-host-vue-remote/react-host/src/main.tsx +2 -2
- package/examples/templates/react-host-vue-remote/scripts/dev.config.json +11 -2
- package/examples/templates/react-host-vue-remote/vue-remote/package.json +1 -1
- package/examples/templates/react-host-vue-remote/vue-remote/src/bridge.ts +1 -1
- package/examples/templates/react-react/host/package.json +1 -1
- package/examples/templates/react-react/pnpm-workspace.yaml +1 -1
- package/examples/templates/react-react/remote/package.json +1 -1
- package/examples/templates/react-react/scripts/dev.config.json +11 -2
- package/examples/templates/showcase/README.md +6 -6
- package/examples/templates/showcase/pnpm-workspace.yaml +1 -1
- package/examples/templates/showcase/react-host/package.json +1 -1
- package/examples/templates/showcase/react-host/src/pages/BridgeVuePage.tsx +1 -1
- package/examples/templates/showcase/react-host/src/routing.ts +1 -1
- package/examples/templates/showcase/react-remote/package.json +1 -1
- package/examples/templates/showcase/react-remote/src/bridge.tsx +1 -1
- package/examples/templates/showcase/scripts/dev.config.json +22 -4
- package/examples/templates/showcase/vue-host/package.json +1 -1
- package/examples/templates/showcase/vue-host/src/pages/BridgeReactPage.vue +1 -1
- package/examples/templates/showcase/vue-host/src/routing.ts +3 -13
- package/examples/templates/showcase/vue-remote/package.json +1 -1
- package/examples/templates/showcase/vue-remote/src/bridge.ts +2 -2
- package/examples/templates/vue-host-react-remote/pnpm-workspace.yaml +1 -1
- package/examples/templates/vue-host-react-remote/react-remote/package.json +1 -1
- package/examples/templates/vue-host-react-remote/scripts/dev.config.json +11 -2
- package/examples/templates/vue-host-react-remote/vue-host/README.md +2 -2
- package/examples/templates/vue-host-react-remote/vue-host/package.json +1 -1
- package/examples/templates/vue-host-react-remote/vue-host/src/App.vue +2 -2
- package/examples/templates/vue-vue/host/package.json +1 -1
- package/examples/templates/vue-vue/host/src/main.ts +28 -21
- package/examples/templates/vue-vue/host/src/pages/HomePage.vue +1 -1
- package/examples/templates/vue-vue/pnpm-workspace.yaml +1 -1
- package/examples/templates/vue-vue/remote/package.json +1 -1
- package/examples/templates/vue-vue/scripts/dev.config.json +11 -2
- package/package.json +24 -37
- package/dist/bridge-core-D37VanBl.d.ts +0 -99
- package/dist/bridge-host-react-CGymCzD2.d.ts +0 -35
- package/dist/bridge-host-vue-B3GHaQAC.d.ts +0 -32
- package/dist/bridge-react.d.ts +0 -14
- package/dist/bridge-react.js +0 -3
- package/dist/bridge-vue.d.ts +0 -8
- package/dist/bridge-vue.js +0 -3
- package/dist/bridge.d.ts +0 -18
- package/dist/bridge.js +0 -5
- package/dist/chunk-GLASM5EX.js +0 -1730
- package/dist/chunk-V6EASSCR.js +0 -249
- package/dist/chunk-X34EJB4H.js +0 -207
- package/docs/API.en.md +0 -339
- package/docs/API.md +0 -914
- package/docs//346/262/231/347/256/261/350/276/271/347/225/214/345/256/241/350/256/241.md +0 -45
- package/docs//350/277/201/347/247/273/346/214/207/345/215/227.md +0 -179
package/docs/API.en.md
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
# API reference (English)
|
|
2
|
-
|
|
3
|
-
> For 5.9.0. Start with the [usage guide](../README.en.md). This reference preserves signatures, defaults and lifecycle rules; integration fragments may use application-owned objects. Complete runnable projects are in [examples](../examples/README.en.md). Only import public entries; `/internal/*` is implementation detail.
|
|
4
|
-
|
|
5
|
-
## 8. API reference
|
|
6
|
-
|
|
7
|
-
### 8.1 `@fulgurjs/federation/react` — React entry
|
|
8
|
-
|
|
9
|
-
Re-exports the common runtime API of §8.2 **except** the Vue-only items (`remoteComponent` Vue options form, `createHostPages`, `keepAliveNames`), plus:
|
|
10
|
-
|
|
11
|
-
#### `remoteComponent<Props>(spec, options?)` → `ComponentType<Props & { ref? }>`
|
|
12
|
-
|
|
13
|
-
| Option | Type / default | Semantics |
|
|
14
|
-
|---|---|---|
|
|
15
|
-
| `fallback` | `ReactNode`, default `null` | placeholder while this load is pending (distinct from the failure placeholder) |
|
|
16
|
-
| `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 |
|
|
17
|
-
| `retries` | `number` (integer 0–10), default follows `loadRemote` (2) | passthrough; invalid values throw at factory call |
|
|
18
|
-
| `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 |
|
|
19
|
-
|
|
20
|
-
- Factory creation and page-table declaration have **zero load side effects**; loading starts on first render via `loadRemote` (container negotiation + optional setup/onSession)
|
|
21
|
-
- 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)
|
|
22
|
-
- Export validation: the default export must be a function/class/`memo`/`forwardRef` component; strings/numbers/empty namespaces fail explicitly
|
|
23
|
-
- `ref` passthrough works for `forwardRef` exports (verified on React 18 and 19)
|
|
24
|
-
- 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)
|
|
25
|
-
- **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
|
|
26
|
-
- The built-in placeholder shows error code + real cause + fix + a working 重试 (retry) button
|
|
27
|
-
|
|
28
|
-
#### `useLoadRemote<Module>(spec, options?)` → `{ data, error, loading, reload }`
|
|
29
|
-
|
|
30
|
-
- `data: Module | undefined`, `error: unknown` (always `undefined` when no error), `loading: boolean`, `reload: () => Promise<void>`
|
|
31
|
-
- Options: `shareScope`, `retries`, `fallbackModule` (explicit degradation — failures return the fallback value instead of writing `error`)
|
|
32
|
-
- 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
|
|
33
|
-
- 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
|
|
34
|
-
- `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
|
|
35
|
-
- Session-aware: re-runs when `sessionKey` changes; same-session re-renders don't
|
|
36
|
-
|
|
37
|
-
#### `RemoteErrorBoundary`
|
|
38
|
-
|
|
39
|
-
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.
|
|
40
|
-
|
|
41
|
-
<a id="pages"></a>
|
|
42
|
-
|
|
43
|
-
#### `createReactHostPages(options)` → `{ pages, resolve(path), component(spec) }`
|
|
44
|
-
|
|
45
|
-
- Data options (identical to Vue): `pages`, `remotePrefixes`, `deriveSpec`, `schema`, `strict`, `base`
|
|
46
|
-
- 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
|
|
47
|
-
- `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
|
|
48
|
-
- 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/templates/react-react/host`; route params reach remote pages as props)
|
|
49
|
-
- 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
|
|
50
|
-
|
|
51
|
-
### Async shared decisions and React version isolation (5.7.1)
|
|
52
|
-
|
|
53
|
-
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.
|
|
54
|
-
|
|
55
|
-
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](https://github.com/chenmingye/fulgurjs-federation/blob/master/examples/demos/react-versions/README.md).
|
|
56
|
-
|
|
57
|
-
<a id="runtime"></a>
|
|
58
|
-
|
|
59
|
-
### 8.2 Runtime API — `@fulgurjs/federation/runtime` (Vue apps) and common functions on `/react`
|
|
60
|
-
|
|
61
|
-
| Function | Signature | Semantics |
|
|
62
|
-
|---|---|---|
|
|
63
|
-
| `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 |
|
|
64
|
-
| `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`) |
|
|
65
|
-
| `initSharing` | `(scopeName?: string) => ShareScopeMap` (default `'default'`) | creates/returns the share scope map (usually called for you by the injected init) |
|
|
66
|
-
| `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 |
|
|
67
|
-
| `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>` |
|
|
68
|
-
| `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 |
|
|
69
|
-
| `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) |
|
|
70
|
-
| `getContainer` | `(name: string) => Promise<any>` | acquire the initialized container |
|
|
71
|
-
| `getRuntime` | `() => FgRuntime` | the page-level runtime singleton (`globalThis.__FULGURJS_RUNTIME__`) |
|
|
72
|
-
| `parseSpec` | `(spec: string) => { remote, module }` | synchronous spec parsing |
|
|
73
|
-
| `shareScopeMap` | `ShareScopeMap` | live registry (debug surface: `window.__FULGURJS_SCOPE__`) |
|
|
74
|
-
| `unwrapDefault` | `(ns: any) => any` | ESM/CJS default-interop helper |
|
|
75
|
-
| `version` | `string` | plugin/runtime version |
|
|
76
|
-
| `clearSessionState` | `() => void` | invalidate all remotes' session signals and onSession dedup state (called by `clearAppContext`) |
|
|
77
|
-
|
|
78
|
-
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).
|
|
79
|
-
|
|
80
|
-
<a id="plugin-options"></a>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#### Other exported common helpers
|
|
84
|
-
|
|
85
|
-
Import from `/runtime` in Vue or `/react` in React. Apart from page validation, these are advanced tools rather than required setup.
|
|
86
|
-
|
|
87
|
-
| Name | Signature / value | Purpose |
|
|
88
|
-
|---|---|---|
|
|
89
|
-
| `getLoadedShare` | `(name: string, opts?: LoadShareOptions) => any` | Read an already-ready instance synchronously without fetching. Undefined on a miss; strict conflicts can throw. With a resolveShare policy, reuse only an existing decision |
|
|
90
|
-
| `pinLoadedShare` | `(name, opts: LoadShareOptions, localVersion: string, instance: unknown) => void` | Register an existing local instance with version/first-instance/concurrency protection; not an override mechanism |
|
|
91
|
-
| `parseSpec` | `(spec: string) => { remote: string; module: string }` | Split a remote spec; normalize the module part to `./X`, or empty for a bare remote |
|
|
92
|
-
| `shareScopeMap` | `ShareScopeMap` | Dependency registry for diagnostics; ordinary business code should not mutate it |
|
|
93
|
-
| `clearSessionState` | `() => void` | Invalidate onSession signals/dedup, not the account context. Normal logout calls clearAppContext, which also performs this cleanup |
|
|
94
|
-
| `validatePages` | `(pages: PageRouteLike[], options?: PagesOptions) => PageViolation[]` | Return R1–R5 page violations without throwing for them |
|
|
95
|
-
|
|
96
|
-
### 8.3 Plugin options — `federation(options)`
|
|
97
|
-
|
|
98
|
-
| Option | Type / default | Notes |
|
|
99
|
-
|---|---|---|
|
|
100
|
-
| `name` | `string`, **required** | container name; unique per page; `/^[a-zA-Z][\w.-]*$/` |
|
|
101
|
-
| `exposes` | `Record<string, string \| { import, name? }>` | key normalized to `./Key`; stable chunk name optional |
|
|
102
|
-
| `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) |
|
|
103
|
-
| `shared` | `string[]` or `Record<string, string \| SharedHint>` | see below |
|
|
104
|
-
| `setup` | `string` | module path; must default-export `setup(context)`, optional named `onSession(context)` |
|
|
105
|
-
| `shareScope` | `string`, default `'default'` | default scope for provides |
|
|
106
|
-
| `runtime` | `string \| false`, default built-in runtime | Custom runtime module path; false disables the built-in runtime |
|
|
107
|
-
| `runtimeChunk` | `boolean \| 'single'`, no explicit default | Request a separate runtime chunk |
|
|
108
|
-
| `filename` | `string`, default `'fulgurjs-remoteEntry.js'` | fixed remoteEntry filename |
|
|
109
|
-
| `manifest` | `boolean \| Record<string, unknown>`, default `true` | false disables; other values enable output. Object form has no additional option fields |
|
|
110
|
-
| `dts` | `boolean \| { dir?, mode?: 'source' \| 'shim' }`, default `true` | dev type generation (see §8.6) |
|
|
111
|
-
| `devSharedSelf` | `boolean`, default inferred | pure remotes & dual-role apps: `true` (dev shared rewriting); pure hosts: `false`. In production builds, shared package bodies (with their static closure) are isolated into `fulgurjs-provider-<key>` groups (since 5.8.0, taking precedence over user `manualChunks` groups — prevents self-waiting cycles and cross-chunk TDZ), so **your own `manualChunks` rules can stay as-is** |
|
|
112
|
-
| `devCorsOrigins` | `'*'` or `string[]`, default `'*'` | dev endpoints + server.cors share the policy; explicit user `server.cors` wins |
|
|
113
|
-
| `devFsRoot` | `boolean`, default `true` | dev manifest carries local fsRoot for type direct-connect; `false` → host falls back to `any` stubs |
|
|
114
|
-
| `runtimePlugins` | `string[]`, default `[]` | modules default-exporting a `RuntimePlugin` |
|
|
115
|
-
|
|
116
|
-
`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`. Import defaults to the config key; requiredVersion is inferred from package.json; singleton/eager default false; strictVersion defaults true with local fallback and non-singleton, otherwise false. shareKey defaults to the config key; shareScope inherits the plugin scope; version is read from the installed package when not specified.
|
|
117
|
-
|
|
118
|
-
### 8.4 Lifecycle — `setup` / `onSession`
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
// federation({ setup: './src/fulgurjs/setup.ts' })
|
|
122
|
-
export default async function setup(ctx: { appContext: Record<string, any>; sessionKey?: string; signal: AbortSignal }) {
|
|
123
|
-
// app-level: once per app, before the first business module is returned
|
|
124
|
-
}
|
|
125
|
-
export async function onSession(ctx: { appContext: any; sessionKey: string; signal: AbortSignal }) {
|
|
126
|
-
// session-level: once per host sessionKey (login generation); re-login re-runs, logout invalidates
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
- Failures reject the triggering `loadRemote` (MFU-011/012) and are retryable; already-succeeded stages are not re-run
|
|
131
|
-
- `signal` aborts on logout/session change — check `signal.aborted` before writing async results
|
|
132
|
-
- `preloadRemote` / `getContainer` never trigger the lifecycle
|
|
133
|
-
- Remote declares `onSession` → the host **must** provide a non-empty `sessionKey` (MFU-013); never use a token as sessionKey
|
|
134
|
-
- No-setup remotes (plain public components) load normally without any context
|
|
135
|
-
|
|
136
|
-
<a id="context"></a>
|
|
137
|
-
|
|
138
|
-
### 8.5 AppContext — cross-app values
|
|
139
|
-
|
|
140
|
-
- `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
|
|
141
|
-
- `getAppContext()` — read the snapshot (`CC-002` if loaded outside the host federation)
|
|
142
|
-
- `requireAppContext(...keys)` — validated read; missing keys → `CC-001` with got/expected/example
|
|
143
|
-
- `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
|
|
144
|
-
- Standard fields: `user`, `getToken()`, `store` (host pinia), `hostApp` (host Vue app), `locale`, `events`, `sessionKey` — plus arbitrary extension keys. Transport snapshot + function references; not reactive
|
|
145
|
-
|
|
146
|
-
### 8.6 Dev types (dual-track)
|
|
147
|
-
|
|
148
|
-
- Zero config: ambient declarations per expose — imports resolve, exports typed `any`; setup entry never generates declarations
|
|
149
|
-
- 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
|
|
150
|
-
|
|
151
|
-
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`.
|
|
152
|
-
- `devFsRoot: false` or unreachable source: degrades to resolvable `any` declarations and cleans stale precise-track files (precise → degrade → restore cycles compile cleanly)
|
|
153
|
-
- `dts: false` stops generation without deleting existing output; `dts.dir` relocates; `mode: 'shim'` gives loose IDE-clean placeholders
|
|
154
|
-
- 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
|
|
155
|
-
|
|
156
|
-
<a id="bridge"></a>
|
|
157
|
-
|
|
158
|
-
### 8.7 Cross-framework bridge — `/bridge` (sub-app-level Vue↔React, 5.3.0+)
|
|
159
|
-
|
|
160
|
-
**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).
|
|
161
|
-
|
|
162
|
-
#### Entries & import graph
|
|
163
|
-
|
|
164
|
-
```text
|
|
165
|
-
build @fulgurjs/federation -> the Vite plugin (unchanged)
|
|
166
|
-
Vue sub-app @fulgurjs/federation/runtime -> defineBridgeApp (zero React)
|
|
167
|
-
React sub-app @fulgurjs/federation/react -> defineBridgeApp (zero Vue; react-dom/client loads at mount time)
|
|
168
|
-
bridge host @fulgurjs/federation/bridge/vue -> createVueBridgeApp (recommended for Vue hosts; zero React)
|
|
169
|
-
@fulgurjs/federation/bridge/react -> createReactBridgeApp (recommended for React hosts; zero Vue)
|
|
170
|
-
@fulgurjs/federation/bridge -> aggregate (kept for compatibility; dev native ESM executes both host adapters)
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**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.
|
|
174
|
-
|
|
175
|
-
**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).
|
|
176
|
-
|
|
177
|
-
#### Sub-app side: `defineBridgeApp` (`/runtime` and `/react`, same name)
|
|
178
|
-
|
|
179
|
-
The remote's `./bridge` expose module **default-exports** the contract object; the plugin validates that `mount`/`unmount` are functions (`MFU-015` otherwise).
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
// Vue sub-app src/bridge.ts
|
|
183
|
-
import { createApp } from 'vue'
|
|
184
|
-
import { createMemoryHistory, createRouter } from 'vue-router'
|
|
185
|
-
import { defineBridgeApp } from '@fulgurjs/federation/runtime'
|
|
186
|
-
export default defineBridgeApp((props) => {
|
|
187
|
-
const app = createApp(App, props)
|
|
188
|
-
app.use(createRouter({ history: createMemoryHistory(), routes }))
|
|
189
|
-
return app
|
|
190
|
-
})
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
```tsx
|
|
194
|
-
// React sub-app src/bridge.tsx
|
|
195
|
-
import { MemoryRouter } from 'react-router-dom'
|
|
196
|
-
import { defineBridgeApp } from '@fulgurjs/federation/react'
|
|
197
|
-
export default defineBridgeApp((props) => <MemoryRouter><App {...props} /></MemoryRouter>)
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Contract semantics (`BridgeApp`):
|
|
201
|
-
- `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.
|
|
202
|
-
- `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.
|
|
203
|
-
- Contract instances are keyed **per container element**; double-mount on the same container is rejected (`MFU-016`).
|
|
204
|
-
- 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.
|
|
205
|
-
|
|
206
|
-
#### Host side: `createVueBridgeApp` / `createReactBridgeApp`
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
// Vue host
|
|
210
|
-
import { createVueBridgeApp } from '@fulgurjs/federation/bridge/vue'
|
|
211
|
-
const RemoteReactApp = createVueBridgeApp('bridge-react-remote/bridge', {
|
|
212
|
-
retries: 1,
|
|
213
|
-
getContext: () => getLatestHostContext(), // your own synchronous pure getter
|
|
214
|
-
})
|
|
215
|
-
// <RemoteReactApp :session-key="loginKey" :app-props="{ userId, onReady }" />
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
```tsx
|
|
219
|
-
// React host
|
|
220
|
-
import { createReactBridgeApp } from '@fulgurjs/federation/bridge/react'
|
|
221
|
-
const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', { getContext: () => getLatestHostContext() })
|
|
222
|
-
// <RemoteVueApp sessionKey={loginKey} appProps={{ userId, onReady }} />
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
| Item | `createVueBridgeApp` | `createReactBridgeApp` |
|
|
226
|
-
|---|---|---|
|
|
227
|
-
| Factory options | `loadingComponent?` `errorComponent?` (receives `error`; full takeover) `retries?` (0–10) `timeout?` `getContext?` | `fallback?` `error?` (node or `(error, retry) => ReactNode`) `retries?` `timeout?` `getContext?` |
|
|
228
|
-
| Component props | `appProps: P` + `sessionKey?: string \| null` (control prop, never mixed into business props) | same |
|
|
229
|
-
| Error placeholder | Chinese diagnostic (code + root cause + fix) with retry / full-reload buttons | same |
|
|
230
|
-
|
|
231
|
-
- **`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.
|
|
232
|
-
- **`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).
|
|
233
|
-
- **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`.
|
|
234
|
-
- **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.
|
|
235
|
-
|
|
236
|
-
<a id="url-sync"></a>
|
|
237
|
-
|
|
238
|
-
### 8.8 Bridge URL sync — `/bridge/router/*` (sub-app internal routes ↔ browser URL, 5.4.0+)
|
|
239
|
-
|
|
240
|
-
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).
|
|
241
|
-
|
|
242
|
-
**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**.
|
|
243
|
-
|
|
244
|
-
**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.
|
|
245
|
-
|
|
246
|
-
**Sub-app**: declare the protocol and wire a controlled router —
|
|
247
|
-
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).
|
|
248
|
-
React: `createReactBridgeRouter(ctx.routing!, routes).element` — `createMemoryRouter`-based; `Link`/`useNavigate` work unmodified.
|
|
249
|
-
|
|
250
|
-
**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.
|
|
251
|
-
|
|
252
|
-
**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.
|
|
253
|
-
|
|
254
|
-
**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).
|
|
255
|
-
|
|
256
|
-
## 8.9 CLI commands
|
|
257
|
-
|
|
258
|
-
| Command | Purpose |
|
|
259
|
-
|---|---|
|
|
260
|
-
| `fulgurjs create [--list]` | **Full-project wizard (new projects)**: copies a complete template workspace (lockfile + startup script included) from the installed npm package and runs `pnpm install --frozen-lockfile` by default. Interactive on a TTY; non-interactive form: `fulgurjs create <template> [--dir <path>] [--no-install] [--force] [--json]` with templates `vue-vue` / `react-react` / `vue-host-react-remote` / `react-host-vue-remote` / `showcase`. Refuses a non-empty target unless `--force` (merge mode: only fills in missing files; same-name conflicts are listed one by one and your files are kept verbatim — never overwritten or deleted). A target path that is a file, a mid-copy failure, or an install failure all exit non-zero and keep the generated project for inspection. With `--json`, stdout carries only the final result JSON while progress and install logs go to stderr. Does not rename apps/ports (four-place checklist in the template README) |
|
|
261
|
-
| `fulgurjs init [--template <path>] [--force]` | Writes a single-project `fulgurjs.config.ts` starter (default export = `federation()` options) for an **existing** project; `--template` is the output file path, not a template id. Refuses to overwrite unless `--force` |
|
|
262
|
-
| `fulgurjs init --config <path>` | Validates a config (CFG three-part errors) and prints the `federation(fulgurjsConfig)` wiring snippet plus checklist |
|
|
263
|
-
| `fulgurjs explain [--config <path>] [--json]` | Explains the effective federation shape (role, remotes, exposes, setup, shared, page mapping, `devSharedSelf`, load chain) purely offline; adds bridge-completeness WARNs (`./bridge` sub-apps must singleton their own framework — React needs react+react-dom; hosts sharing both vue and react need all three keys singleton). No new error codes |
|
|
264
|
-
| `fulgurjs check-pages [--config <path>] [--site <URL>] [--manifest <r>=<p\|URL>]... [--require-verified] [--json]` | Compares the host page table with remote manifests. Manifest source priority: `--manifest` > `--site`/prod derivation; explicit sources never fall back. Deterministic errors exit 1; unreachable remotes report "unverified" (non-zero only with `--require-verified`) |
|
|
265
|
-
| `fulgurjs doctor --base <URL> --apps <a,b,c> [--dev] [--json] [--chunk-sample N]` | Deployment check of `<base>/<app>/` (`--apps` are **deployment subdirectories**, not container names): remoteEntry/manifest/index.html 200 + no-cache + JS shape, CORS, chunk sampling, shared-version skew rehearsal. Exit 1 on any FAIL |
|
|
266
|
-
|
|
267
|
-
## 9. Artifacts, endpoints & caching
|
|
268
|
-
|
|
269
|
-
| Artifact | Cache policy |
|
|
270
|
-
|---|---|
|
|
271
|
-
| `fulgurjs-remoteEntry.js` (fixed filename, content changes every build) | **`no-cache`** |
|
|
272
|
-
| content-hashed chunks / CSS | `immutable` long cache |
|
|
273
|
-
| `fulgurjs-manifest.json` | `no-cache` (consumed by `preloadRemote` / `check-pages` / `doctor`) |
|
|
274
|
-
| dev endpoints `/@fulgurjs-entry.js` / `/@fulgurjs-manifest.json` | `no-cache`, CORS per `devCorsOrigins` |
|
|
275
|
-
|
|
276
|
-
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.
|
|
277
|
-
|
|
278
|
-
## 10. Debugging surfaces
|
|
279
|
-
|
|
280
|
-
- `window.__FULGURJS_SCOPE__` — live share-scope registry
|
|
281
|
-
- `window.__FULGURJS_INFO__` — per-remote status/latency/errors + `errors` log
|
|
282
|
-
- `DEBUG=fulgurjs:*` — controlled pipeline diagnostics (off by default)
|
|
283
|
-
- Runtime diagnostics are emitted in Chinese by design (language policy); codes are stable identifiers listed below
|
|
284
|
-
|
|
285
|
-
<a id="error-codes"></a>
|
|
286
|
-
|
|
287
|
-
## 11. Error codes (48)
|
|
288
|
-
|
|
289
|
-
| Segment | Code | Meaning |
|
|
290
|
-
|---|---|---|
|
|
291
|
-
| CFG | `CFG-001` | name missing or invalid |
|
|
292
|
-
| | `CFG-002` | exposes shape invalid |
|
|
293
|
-
| | `CFG-003` | remotes shape invalid / illegal key characters |
|
|
294
|
-
| | `CFG-004` | shared shape invalid |
|
|
295
|
-
| | `CFG-005` | remotes key collides with a shared key |
|
|
296
|
-
| | `CFG-006` | island config (neither provides nor consumes) |
|
|
297
|
-
| | `CFG-007` | `name@` prefix misuse in object-form remotes |
|
|
298
|
-
| | `CFG-008` | shared illegal combo (eager+import:false / duplicate shareKey) |
|
|
299
|
-
| | `CFG-009` | remote runtime params invalid (timeout/retries/breaker) |
|
|
300
|
-
| | `CFG-010` | devCorsOrigins invalid |
|
|
301
|
-
| | `CFG-011` | removed no-op option (any value errors with migration hints) |
|
|
302
|
-
| | `CFG-012` | setup config invalid / reserved expose key squatted |
|
|
303
|
-
| DEV | `DEV-001` | remote dev server unreachable (manifest fetch failed) |
|
|
304
|
-
| | `DEV-002` | remote dev manifest empty or unrecognized |
|
|
305
|
-
| | `DEV-004` | known UMD-only dep missing from optimizeDeps.include |
|
|
306
|
-
| | `DEV-005` | remotes dev URL port not listening |
|
|
307
|
-
| | `DEV-006` | host/remote plugin version mismatch |
|
|
308
|
-
| | `DEV-009` | facade/virtual module 404 (.vite cache drift — clear and restart) |
|
|
309
|
-
| | `DEV-010` | dev cold-start pre-bundle window notice (transient) |
|
|
310
|
-
| | `DEV-011` | non-loopback host + wildcard dev CORS reminder |
|
|
311
|
-
| | `DEV-012` | non-loopback host + fsRoot disclosure reminder |
|
|
312
|
-
| BLD | `BLD-001` | expose source resolution failed |
|
|
313
|
-
| | `BLD-002` | build target below es2022 |
|
|
314
|
-
| | `BLD-003` | expose target declares required props (documented checklist) |
|
|
315
|
-
| | `BLD-006` | array-form output prevents automatic facade chunk isolation |
|
|
316
|
-
| MFU | `MFU-001` | remote container/module load failure (network / timeout / retries exhausted / breaker) |
|
|
317
|
-
| | `MFU-002` | remoteEntry self-reported name mismatch |
|
|
318
|
-
| | `MFU-003` | strictVersion requirement not satisfied |
|
|
319
|
-
| | `MFU-004` | shared module missing with no local fallback |
|
|
320
|
-
| | `MFU-005` | same container re-initialized with a different share scope |
|
|
321
|
-
| | `MFU-006` | requested module not exposed by the remote |
|
|
322
|
-
| | `MFU-007` | preload failed (non-blocking) |
|
|
323
|
-
| | `MFU-008` | unknown remote |
|
|
324
|
-
| | `MFU-009` | loaded module has no exports at all |
|
|
325
|
-
| | `MFU-010` | reused singleton version doesn't satisfy the consumer requirement (warn-once) |
|
|
326
|
-
| | `MFU-011` | setup entry export shape invalid |
|
|
327
|
-
| | `MFU-012` | setup/onSession threw (retryable; only the failed stage resets) |
|
|
328
|
-
| | `MFU-013` | onSession declared but host sessionKey missing |
|
|
329
|
-
| | `MFU-014` | setup/onSession synchronously re-loading the same remote (deadlock guard) |
|
|
330
|
-
| | `MFU-015` | bridge contract invalid (`./bridge` default export missing non-function mount/unmount; fix points to `defineBridgeApp`) |
|
|
331
|
-
| | `MFU-016` | bridge preparation or lifecycle failure (`details.phase` = getContext/mount/unmount; cause keeps the sub-app's original error) |
|
|
332
|
-
| | `MFU-017` | bridge session mismatch (controlled sessionKey vs AppContext / illegal value / page-level single-session conflict) |
|
|
333
|
-
| MFU | `MFU-030` | Bridge URL-sync config invalid / prefix conflict (illegal basePath: empty, root, query/hash/wildcard; overlapping active prefixes) |
|
|
334
|
-
| MFU | `MFU-031` | Bridge routing protocol missing / channel destroyed (sub-app not declared with `{ routing: true }`; disposed channel reused) |
|
|
335
|
-
| MFU | `MFU-032` | Bridge illegal navigation (target escaping its own prefix, illegal `go` argument, request on a dead channel) |
|
|
336
|
-
| MFU | `MFU-033` | Bridge routing preparation/sync failed (redirect limit or navigation exception, chain/cause attached; no silent fallback to memory) |
|
|
337
|
-
| CC | `CC-001` | AppContext required key missing (got/expected/example) |
|
|
338
|
-
| | `CC-002` | runtime singleton unavailable (standalone remote page) |
|
|
339
|
-
|