@rollipop/plugin-module-federation 1.0.0-alpha.32 → 1.0.0-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rollipop/plugin-module-federation
2
2
 
3
- Module Federation for Rollipop. Wires `@module-federation/runtime` into the host bundle and emits a self-contained IIFE for each remote, with shared dependencies resolved through a host-owned global registry.
3
+ Module Federation for Rollipop. Wires `@module-federation/runtime` into the host bundle and registers each remote's container on `globalThis`, with shared dependencies resolved through a host-owned global registry.
4
4
 
5
5
  ## Status
6
6
 
@@ -19,13 +19,17 @@ Module Federation for Rollipop. Wires `@module-federation/runtime` into the host
19
19
 
20
20
  A single Rollipop config takes one role. Defining both `remotes` and `exposes` throws.
21
21
 
22
- | Role | Has | Bundle output |
23
- | ------ | --------- | ---------------------------------------------------------------------------- |
24
- | Host | `remotes` | Normal Rollipop bundle. `import('<remote>/<expose>')` is rewritten at build. |
25
- | Remote | `exposes` | IIFE that registers a federation container on `globalThis`. |
22
+ | Role | Has | Bundle output |
23
+ | ------ | --------- | ------------------------------------------------------------- |
24
+ | Host | `remotes` | Normal Rollipop bundle that consumes remotes at runtime. |
25
+ | Remote | `exposes` | IIFE for static builds; Rollipop format with HMR when served. |
26
26
 
27
27
  ## Host
28
28
 
29
+ The script loader below uses the native `ScriptManager` supplied by the
30
+ [`module-federation` example](../../examples/module-federation/mf-script-loader.ts).
31
+ Provide a loader for your own app's native implementation.
32
+
29
33
  ```ts
30
34
  // rollipop.host.config.ts
31
35
  import { federation } from '@rollipop/plugin-module-federation';
@@ -47,6 +51,7 @@ export default defineConfig({
47
51
  // Register a `ModuleFederationScriptLoader` on `globalThis.__rollipop_script_loader__`.
48
52
  // Typically delegates to a native TurboModule that does fetch + JSI evaluate.
49
53
  implement: `
54
+ const NativeScriptManager = globalThis.nativeModuleProxy.ScriptManager;
50
55
  globalThis.__rollipop_script_loader__ = {
51
56
  async loadScript({ scriptId, url }) {
52
57
  await NativeScriptManager.loadScript(scriptId, { url });
@@ -62,12 +67,29 @@ export default defineConfig({
62
67
  In your app, consume the remote with dynamic import:
63
68
 
64
69
  ```ts
70
+ import * as React from 'react';
71
+
65
72
  const RemoteNavigator = React.lazy(() =>
66
73
  import('remote_app/RemoteNavigator').then((m) => ({ default: m.default ?? m })),
67
74
  );
68
75
  ```
69
76
 
70
- The plugin rewrites the `import()` call into a `loadRemote` invocation against the federation runtime — no native `import()` evaluation happens at runtime.
77
+ The imported module's default export is a React component proxy. Rendering it loads the remote
78
+ through the federation runtime and suspends while the request is pending, so render it inside a
79
+ React `Suspense` boundary. The bundled code does not use native `import()` to fetch the remote.
80
+
81
+ Failed remote loads are thrown to the nearest React Error Boundary. From its retry action,
82
+ reload the remote before resetting the boundary:
83
+
84
+ ```ts
85
+ import { retryRemote } from '@rollipop/plugin-module-federation/runtime';
86
+
87
+ await retryRemote('remote_app/RemoteNavigator');
88
+ resetErrorBoundary();
89
+ ```
90
+
91
+ Concurrent loads share one request. If a retry fails, the promise rejects and the boundary
92
+ can keep showing its fallback until the user retries again.
71
93
 
72
94
  ## Remote
73
95
 
@@ -93,7 +115,11 @@ export default defineConfig({
93
115
  });
94
116
  ```
95
117
 
96
- The bundle is emitted as IIFE. Each shared dep import (e.g. `import RN from 'react-native'`) is replaced with a stub that reads from the host's `globalThis.__rollipop_shared__` registry and throws if the dep is not registered. Rollipop's prelude and polyfills are skipped because the host has already initialized the React Native runtime.
118
+ Static builds emit an IIFE; the dev server uses Rollipop's format so each remote has its own HMR
119
+ runtime. Each shared dependency import (e.g. `import RN from 'react-native'`) is replaced with a
120
+ stub that reads from the host's `globalThis.__rollipop_shared__` registry and throws if the
121
+ dependency is not registered. Rollipop's prelude and polyfills are skipped because the host has
122
+ already initialized the React Native runtime.
97
123
 
98
124
  ## Script loader contract
99
125
 
@@ -109,11 +135,13 @@ The host registers an implementation on `globalThis.__rollipop_script_loader__`.
109
135
 
110
136
  ## Globals exposed at runtime
111
137
 
112
- | Global | Owner | Purpose |
113
- | ---------------------------- | ------ | ----------------------------------------------------------------------- |
114
- | `__rollipop_script_loader__` | user | `ModuleFederationScriptLoader` implementation. |
115
- | `__rollipop_shared__` | host | Shared dependency registry. Lazily populated; missing keys throw. |
116
- | `__rollipop_load_remote__` | plugin | `(id: string) => Promise<unknown>` — wraps the federation `loadRemote`. |
138
+ | Global | Owner | Purpose |
139
+ | -------------------------------------- | ------ | ------------------------------------------------------------------------------------ |
140
+ | `__rollipop_script_loader__` | user | `ModuleFederationScriptLoader` implementation. |
141
+ | `__rollipop_shared__` | host | Shared dependency registry populated during host initialization; missing keys throw. |
142
+ | `__rollipop_module_federation_cache__` | plugin | Internal cache of loaded modules, pending requests, and load errors. |
143
+
144
+ Use `retryRemote` from `@rollipop/plugin-module-federation/runtime` to retry a failed request.
117
145
 
118
146
  ## Constraints
119
147
 
@@ -121,7 +149,8 @@ The host registers an implementation on `globalThis.__rollipop_script_loader__`.
121
149
  current-implementation limitation, not an inherent Module Federation rule.
122
150
  Split a host and a remote into two configs and run them as separate Rollipop
123
151
  processes. See [Status](#status).
124
- - React Native does not support native `import()`. Static imports of remote modules are intentionally not supported — use dynamic `import('<remote>/<expose>')`.
152
+ - Remote imports expose a default React component proxy. Dynamic `import('<remote>/<expose>')`
153
+ returns that proxy, rather than the remote module's namespace directly.
125
154
  - Subpath imports of shared deps (`react/jsx-dev-runtime`, etc.) are bundled into the remote and consume the parent shared instance through `__rollipop_shared__`.
126
155
 
127
156
  See `examples/module-federation` for a working RN 0.84 host + remote setup with a Pure C++/Obj-C++ TurboModule script loader.
package/dist/index.js CHANGED
@@ -22,8 +22,9 @@ const Q = "\\u0027";
22
22
  //#endregion
23
23
  //#region src/virtual/host-init.ts
24
24
  function generateHostInitCode(config) {
25
- const remotesArray = Object.values(config.remotes).map((remote) => ({
25
+ const remotesArray = Object.entries(config.remotes).map(([alias, remote]) => ({
26
26
  name: remote.name,
27
+ alias: alias === remote.name ? void 0 : alias,
27
28
  entry: remote.entry,
28
29
  type: remote.type,
29
30
  entryGlobalName: remote.entryGlobalName
@@ -66,6 +67,7 @@ function generateHostInitCode(config) {
66
67
  const cache = {
67
68
  modules: Object.create(null),
68
69
  pending: Object.create(null),
70
+ errors: Object.create(null),
69
71
  load(id) {
70
72
  if (this.modules[id] !== undefined) {
71
73
  return Promise.resolve(this.modules[id]);
@@ -73,12 +75,20 @@ function generateHostInitCode(config) {
73
75
  if (this.pending[id]) {
74
76
  return this.pending[id];
75
77
  }
78
+ delete this.errors[id];
76
79
  const fetcher = instance.loadRemote(id);
77
- this.pending[id] = fetcher.then((mod) => {
78
- this.modules[id] = mod;
79
- delete this.pending[id];
80
- return mod;
81
- });
80
+ this.pending[id] = fetcher.then(
81
+ (mod) => {
82
+ this.modules[id] = mod;
83
+ delete this.pending[id];
84
+ return mod;
85
+ },
86
+ (error) => {
87
+ this.errors[id] = error;
88
+ delete this.pending[id];
89
+ throw error;
90
+ },
91
+ );
82
92
  return this.pending[id];
83
93
  },
84
94
  };
@@ -258,6 +268,9 @@ function generateRemoteProxyCode({ remoteId }) {
258
268
  if (__cache.modules[__id] !== undefined) {
259
269
  return __cache.modules[__id];
260
270
  }
271
+ if (__id in __cache.errors) {
272
+ throw __cache.errors[__id];
273
+ }
261
274
  throw __cache.load(__id);
262
275
  }
263
276
 
package/dist/runtime.d.ts CHANGED
@@ -12,5 +12,10 @@ export interface RemoteEntryExports {
12
12
  }
13
13
  declare global {
14
14
  var __rollipop_script_loader__: ModuleFederationScriptLoader | undefined;
15
+ var __rollipop_module_federation_cache__: {
16
+ load(id: string): Promise<unknown>;
17
+ } | undefined;
15
18
  }
19
+ /** Retry a failed remote load before resetting the component's Error Boundary. */
20
+ export declare function retryRemote(id: string): Promise<unknown>;
16
21
  //#endregion
package/dist/runtime.js CHANGED
@@ -1 +1,9 @@
1
- export {};
1
+ //#region src/runtime.ts
2
+ /** Retry a failed remote load before resetting the component's Error Boundary. */
3
+ async function retryRemote(id) {
4
+ const cache = globalThis.__rollipop_module_federation_cache__;
5
+ if (cache == null) throw new Error("[rollipop:module-federation] The host runtime is not initialized.");
6
+ return cache.load(id);
7
+ }
8
+ //#endregion
9
+ export { retryRemote };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rollipop/plugin-module-federation",
3
- "version": "1.0.0-alpha.32",
3
+ "version": "1.0.0-alpha.34",
4
4
  "homepage": "https://github.com/rollipop-dev/rollipop#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/rollipop-dev/rollipop/issues"
@@ -44,13 +44,13 @@
44
44
  "@types/react": "19.2.14",
45
45
  "@vitest/coverage-v8": "4.1.11",
46
46
  "react": "19.2.3",
47
- "rollipop": "1.0.0-alpha.32",
47
+ "rollipop": "1.0.0-alpha.34",
48
48
  "typescript": "7.0.2",
49
49
  "vite-plus": "0.3.3",
50
50
  "vitest": "4.1.11"
51
51
  },
52
52
  "peerDependencies": {
53
- "rollipop": "1.0.0-alpha.32"
53
+ "rollipop": "1.0.0-alpha.34"
54
54
  },
55
55
  "installConfig": {
56
56
  "hoistingLimits": "none"