@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 +42 -13
- package/dist/index.js +19 -6
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.js +9 -1
- package/package.json +3 -3
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
|
|
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
|
|
25
|
-
| Remote | `exposes` | IIFE
|
|
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
|
|
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
|
-
|
|
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
|
|
113
|
-
|
|
|
114
|
-
| `__rollipop_script_loader__`
|
|
115
|
-
| `__rollipop_shared__`
|
|
116
|
-
| `
|
|
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
|
-
-
|
|
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.
|
|
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(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
53
|
+
"rollipop": "1.0.0-alpha.34"
|
|
54
54
|
},
|
|
55
55
|
"installConfig": {
|
|
56
56
|
"hoistingLimits": "none"
|