@rollipop/plugin-module-federation 1.0.0-alpha.28 → 1.0.0-alpha.30
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 +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +20 -214
- package/dist/runtime.d.ts +3 -4
- package/package.json +14 -9
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ export default defineConfig({
|
|
|
93
93
|
});
|
|
94
94
|
```
|
|
95
95
|
|
|
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
|
|
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.
|
|
97
97
|
|
|
98
98
|
## Script loader contract
|
|
99
99
|
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,8 +15,6 @@ const VIRTUAL_REMOTE_PROXY_PREFIX = `${VIRTUAL_PREFIX}remote:`;
|
|
|
15
15
|
const SCRIPT_LOADER_GLOBAL = "__rollipop_script_loader__";
|
|
16
16
|
const SHARED_REGISTRY_GLOBAL = "__rollipop_shared__";
|
|
17
17
|
const REMOTE_CACHE_GLOBAL = "__rollipop_module_federation_cache__";
|
|
18
|
-
const HMR_HOT_PATH = "/hot";
|
|
19
|
-
const HMR_EVENT = "mf:remote-update";
|
|
20
18
|
//#endregion
|
|
21
19
|
//#region src/virtual/_dedent.ts
|
|
22
20
|
const dedent = baseDedent.withOptions({ escapeSpecialCharacters: false });
|
|
@@ -64,18 +62,10 @@ function generateHostInitCode(config) {
|
|
|
64
62
|
shareStrategy: ${JSON.stringify(config.shareStrategy)},
|
|
65
63
|
});
|
|
66
64
|
|
|
67
|
-
const remoteList = ${JSON.stringify(remotesArray)};
|
|
68
|
-
|
|
69
65
|
if (globalThis.${REMOTE_CACHE_GLOBAL} == null) {
|
|
70
66
|
const cache = {
|
|
71
67
|
modules: Object.create(null),
|
|
72
68
|
pending: Object.create(null),
|
|
73
|
-
// Per-id invalidation: only ids actually present in the cache when
|
|
74
|
-
// HMR fired need to bypass the federation runtime. Cleared after
|
|
75
|
-
// each successful bypass load — new ids fall back to the normal
|
|
76
|
-
// loadRemote flow with full shared-module negotiation.
|
|
77
|
-
invalidatedIds: new Set(),
|
|
78
|
-
subscribers: new Set(),
|
|
79
69
|
load(id) {
|
|
80
70
|
if (this.modules[id] !== undefined) {
|
|
81
71
|
return Promise.resolve(this.modules[id]);
|
|
@@ -83,126 +73,17 @@ function generateHostInitCode(config) {
|
|
|
83
73
|
if (this.pending[id]) {
|
|
84
74
|
return this.pending[id];
|
|
85
75
|
}
|
|
86
|
-
const
|
|
87
|
-
let fetcher;
|
|
88
|
-
if (isInvalidated) {
|
|
89
|
-
const slash = id.indexOf('/');
|
|
90
|
-
const remoteName = slash === -1 ? id : id.slice(0, slash);
|
|
91
|
-
const exposePath = slash === -1 ? '.' : './' + id.slice(slash + 1);
|
|
92
|
-
fetcher = Promise.resolve().then(() => {
|
|
93
|
-
const container = globalThis[remoteName];
|
|
94
|
-
if (container == null) {
|
|
95
|
-
throw new Error('[rollipop:module-federation] container ${Q}' + remoteName + '${Q} not registered');
|
|
96
|
-
}
|
|
97
|
-
return container.get(exposePath).then((factory) => {
|
|
98
|
-
return typeof factory === 'function' ? factory() : factory;
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
} else {
|
|
102
|
-
fetcher = instance.loadRemote(id);
|
|
103
|
-
}
|
|
76
|
+
const fetcher = instance.loadRemote(id);
|
|
104
77
|
this.pending[id] = fetcher.then((mod) => {
|
|
105
78
|
this.modules[id] = mod;
|
|
106
79
|
delete this.pending[id];
|
|
107
|
-
if (isInvalidated) {
|
|
108
|
-
this.invalidatedIds.delete(id);
|
|
109
|
-
}
|
|
110
80
|
return mod;
|
|
111
81
|
});
|
|
112
82
|
return this.pending[id];
|
|
113
83
|
},
|
|
114
|
-
invalidate(remoteName) {
|
|
115
|
-
for (const key of Object.keys(this.modules)) {
|
|
116
|
-
if (key === remoteName || key.startsWith(remoteName + '/')) {
|
|
117
|
-
this.invalidatedIds.add(key);
|
|
118
|
-
delete this.modules[key];
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
for (const cb of this.subscribers) {
|
|
122
|
-
try {
|
|
123
|
-
cb(remoteName);
|
|
124
|
-
} catch (_e) {}
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
84
|
};
|
|
128
85
|
globalThis.${REMOTE_CACHE_GLOBAL} = cache;
|
|
129
86
|
}
|
|
130
|
-
|
|
131
|
-
const __cache = globalThis.${REMOTE_CACHE_GLOBAL};
|
|
132
|
-
|
|
133
|
-
async function applyRemoteUpdate(remote) {
|
|
134
|
-
const generation = (remote.__mfHmrGen = (remote.__mfHmrGen || 0) + 1);
|
|
135
|
-
const cacheBust = (remote.entry.indexOf('?') === -1 ? '?' : '&') + '_mf_hmr=' + generation;
|
|
136
|
-
try {
|
|
137
|
-
await globalThis.${SCRIPT_LOADER_GLOBAL}.loadScript({
|
|
138
|
-
scriptId: remote.name + '@' + generation,
|
|
139
|
-
url: remote.entry + cacheBust,
|
|
140
|
-
});
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error(
|
|
143
|
-
'[rollipop:module-federation] failed to reload remote ${Q}' + remote.name + '${Q} during HMR. App is running stale code until next successful update.',
|
|
144
|
-
error,
|
|
145
|
-
);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
__cache.invalidate(remote.name);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function subscribeRemoteHmr(remote) {
|
|
152
|
-
let wsUrl;
|
|
153
|
-
try {
|
|
154
|
-
const url = new URL(remote.entry);
|
|
155
|
-
const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
156
|
-
wsUrl = wsProtocol + '//' + url.host + ${JSON.stringify(HMR_HOT_PATH)};
|
|
157
|
-
} catch (_e) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const connect = () => {
|
|
162
|
-
let socket;
|
|
163
|
-
try {
|
|
164
|
-
socket = new WebSocket(wsUrl);
|
|
165
|
-
} catch (_e) {
|
|
166
|
-
setTimeout(connect, 1000);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
socket.onmessage = (event) => {
|
|
170
|
-
let parsed;
|
|
171
|
-
try {
|
|
172
|
-
parsed = JSON.parse(typeof event.data === 'string' ? event.data : '');
|
|
173
|
-
} catch (_e) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (parsed == null || parsed.type !== ${JSON.stringify(HMR_EVENT)}) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
if (parsed.payload != null && parsed.payload.name !== remote.name) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
applyRemoteUpdate(remote);
|
|
183
|
-
};
|
|
184
|
-
socket.onclose = () => setTimeout(connect, 1000);
|
|
185
|
-
socket.onerror = () => {
|
|
186
|
-
try {
|
|
187
|
-
socket.close();
|
|
188
|
-
} catch (_e) {}
|
|
189
|
-
};
|
|
190
|
-
};
|
|
191
|
-
connect();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Defer to the next tick so React Native's InitializeCore has set up
|
|
195
|
-
// WebSocket / setTimeout before the subscription starts.
|
|
196
|
-
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
197
|
-
Promise.resolve().then(() => {
|
|
198
|
-
if (typeof WebSocket === 'undefined') {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
for (const r of remoteList) {
|
|
202
|
-
subscribeRemoteHmr(r);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
87
|
`;
|
|
207
88
|
}
|
|
208
89
|
//#endregion
|
|
@@ -339,11 +220,13 @@ function validateConfig(config) {
|
|
|
339
220
|
//#region src/virtual/remote-entry.ts
|
|
340
221
|
function generateRemoteEntryCode(options) {
|
|
341
222
|
const exposeEntries = Object.entries(options.exposes);
|
|
223
|
+
const importLines = exposeEntries.map(([, filePath], index) => `import * as __expose_${index} from ${JSON.stringify(filePath)};`).join("\n");
|
|
224
|
+
const moduleMapLines = exposeEntries.map(([key], index) => ` ${JSON.stringify(key)}: () => __expose_${index},`).join("\n");
|
|
342
225
|
return dedent`
|
|
343
|
-
${
|
|
226
|
+
${importLines}
|
|
344
227
|
|
|
345
228
|
const moduleMap = {
|
|
346
|
-
${
|
|
229
|
+
${moduleMapLines}
|
|
347
230
|
};
|
|
348
231
|
|
|
349
232
|
const container = {
|
|
@@ -363,68 +246,11 @@ function generateRemoteEntryCode(options) {
|
|
|
363
246
|
}
|
|
364
247
|
//#endregion
|
|
365
248
|
//#region src/virtual/remote-proxy.ts
|
|
366
|
-
function generateRemoteProxyCode({ remoteId
|
|
249
|
+
function generateRemoteProxyCode({ remoteId }) {
|
|
367
250
|
const idLiteral = JSON.stringify(remoteId);
|
|
368
|
-
if (reactAware) return dedent`
|
|
369
|
-
import * as __mfReact from 'react';
|
|
370
|
-
|
|
371
|
-
const __cache = globalThis.${REMOTE_CACHE_GLOBAL};
|
|
372
|
-
const __id = ${idLiteral};
|
|
373
|
-
|
|
374
|
-
function __ensureLoaded() {
|
|
375
|
-
if (__cache.modules[__id] !== undefined) {
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
return __cache.load(__id);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function __getMod() {
|
|
382
|
-
return __cache.modules[__id];
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function __FederatedProxy(props) {
|
|
386
|
-
const [, setVersion] = __mfReact.useState(0);
|
|
387
|
-
__mfReact.useEffect(() => {
|
|
388
|
-
const listener = () => setVersion((v) => v + 1);
|
|
389
|
-
__cache.subscribers.add(listener);
|
|
390
|
-
return () => {
|
|
391
|
-
__cache.subscribers.delete(listener);
|
|
392
|
-
};
|
|
393
|
-
}, []);
|
|
394
|
-
|
|
395
|
-
const mod = __getMod();
|
|
396
|
-
if (mod === undefined) {
|
|
397
|
-
throw __ensureLoaded();
|
|
398
|
-
}
|
|
399
|
-
const fn = mod.default ?? mod;
|
|
400
|
-
return __mfReact.createElement(fn, props);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const __proxy = new Proxy(__FederatedProxy, {
|
|
404
|
-
get(target, prop) {
|
|
405
|
-
if (prop === '__esModule') {
|
|
406
|
-
return true;
|
|
407
|
-
}
|
|
408
|
-
if (prop === 'then') {
|
|
409
|
-
return undefined;
|
|
410
|
-
}
|
|
411
|
-
const mod = __getMod();
|
|
412
|
-
if (mod === undefined) {
|
|
413
|
-
throw __ensureLoaded();
|
|
414
|
-
}
|
|
415
|
-
if (prop in mod) {
|
|
416
|
-
return mod[prop];
|
|
417
|
-
}
|
|
418
|
-
if (mod.default != null && prop in mod.default) {
|
|
419
|
-
return mod.default[prop];
|
|
420
|
-
}
|
|
421
|
-
return target[prop];
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
export default __proxy;
|
|
426
|
-
`;
|
|
427
251
|
return dedent`
|
|
252
|
+
import * as __mfReact from 'react';
|
|
253
|
+
|
|
428
254
|
const __cache = globalThis.${REMOTE_CACHE_GLOBAL};
|
|
429
255
|
const __id = ${idLiteral};
|
|
430
256
|
|
|
@@ -435,13 +261,12 @@ function generateRemoteProxyCode({ remoteId, reactAware }) {
|
|
|
435
261
|
throw __cache.load(__id);
|
|
436
262
|
}
|
|
437
263
|
|
|
438
|
-
function
|
|
264
|
+
function __FederatedProxy(props) {
|
|
439
265
|
const mod = __getMod();
|
|
440
|
-
|
|
441
|
-
return fn.apply(this, args);
|
|
266
|
+
return __mfReact.createElement(mod.default ?? mod, props);
|
|
442
267
|
}
|
|
443
268
|
|
|
444
|
-
const __proxy = new Proxy(
|
|
269
|
+
const __proxy = new Proxy(__FederatedProxy, {
|
|
445
270
|
get(target, prop) {
|
|
446
271
|
if (prop === '__esModule') {
|
|
447
272
|
return true;
|
|
@@ -466,8 +291,9 @@ function generateRemoteProxyCode({ remoteId, reactAware }) {
|
|
|
466
291
|
//#endregion
|
|
467
292
|
//#region src/virtual/shared-shim.ts
|
|
468
293
|
function generateSharedShimCode(sharedName) {
|
|
294
|
+
const key = JSON.stringify(sharedName);
|
|
469
295
|
return dedent`
|
|
470
|
-
const __mod = globalThis.${SHARED_REGISTRY_GLOBAL} && globalThis.${SHARED_REGISTRY_GLOBAL}[${
|
|
296
|
+
const __mod = globalThis.${SHARED_REGISTRY_GLOBAL} && globalThis.${SHARED_REGISTRY_GLOBAL}[${key}];
|
|
471
297
|
if (__mod == null) {
|
|
472
298
|
throw new Error('[${PLUGIN_NAME}] shared module ${Q}${sharedName}${Q} is not registered on the host. Add it to the host config${Q}s ${Q}shared${Q} field.');
|
|
473
299
|
}
|
|
@@ -483,26 +309,23 @@ function moduleFederationPlugin(config) {
|
|
|
483
309
|
if (hasRemotes && hasExposes) throw new Error(`[${PLUGIN_NAME}] A single config cannot define both 'remotes' and 'exposes'. Split into two configs (one host, one remote) and run them as separate Rollipop processes.`);
|
|
484
310
|
let resolvedConfig = null;
|
|
485
311
|
let normalized = null;
|
|
486
|
-
let broadcast = null;
|
|
487
312
|
const exposesAbsolute = {};
|
|
488
313
|
const sharedRoots = new Set(collectSharedNames(federationConfig));
|
|
489
|
-
let debounceTimer = null;
|
|
490
314
|
return {
|
|
491
315
|
name: PLUGIN_NAME,
|
|
492
316
|
config(pluginConfig) {
|
|
493
|
-
const serializer = pluginConfig.serializer ??= {};
|
|
494
317
|
if (hasExposes) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return {
|
|
318
|
+
pluginConfig.prelude = [];
|
|
319
|
+
pluginConfig.polyfills = [];
|
|
320
|
+
return { rolldownOptions: (opts, context) => ({
|
|
498
321
|
input: opts.input,
|
|
499
322
|
output: {
|
|
500
323
|
...opts.output,
|
|
501
|
-
format: "iife"
|
|
324
|
+
format: context.buildType === "serve" ? "rollipop" : "iife"
|
|
502
325
|
}
|
|
503
326
|
}) };
|
|
504
327
|
}
|
|
505
|
-
if (hasRemotes && config.runtime?.implement != null) (
|
|
328
|
+
if (hasRemotes && config.runtime?.implement != null) (pluginConfig.polyfills ??= []).push({
|
|
506
329
|
type: "iife",
|
|
507
330
|
code: config.runtime.implement
|
|
508
331
|
});
|
|
@@ -530,10 +353,7 @@ function moduleFederationPlugin(config) {
|
|
|
530
353
|
moduleType: "js"
|
|
531
354
|
};
|
|
532
355
|
if (id.startsWith(VIRTUAL_REMOTE_PROXY_PREFIX)) return {
|
|
533
|
-
code: generateRemoteProxyCode({
|
|
534
|
-
remoteId: id.slice(VIRTUAL_REMOTE_PROXY_PREFIX.length),
|
|
535
|
-
reactAware: resolvedConfig?.mode !== "production"
|
|
536
|
-
}),
|
|
356
|
+
code: generateRemoteProxyCode({ remoteId: id.slice(VIRTUAL_REMOTE_PROXY_PREFIX.length) }),
|
|
537
357
|
moduleType: "js"
|
|
538
358
|
};
|
|
539
359
|
if (normalized == null) return null;
|
|
@@ -565,21 +385,7 @@ function moduleFederationPlugin(config) {
|
|
|
565
385
|
return { code: magicString };
|
|
566
386
|
}
|
|
567
387
|
return null;
|
|
568
|
-
} }
|
|
569
|
-
watchChange() {
|
|
570
|
-
if (!hasExposes || broadcast == null) return;
|
|
571
|
-
if (debounceTimer != null) clearTimeout(debounceTimer);
|
|
572
|
-
debounceTimer = setTimeout(() => {
|
|
573
|
-
debounceTimer = null;
|
|
574
|
-
broadcast?.();
|
|
575
|
-
}, 100);
|
|
576
|
-
},
|
|
577
|
-
configureServer(server) {
|
|
578
|
-
if (!hasExposes) return;
|
|
579
|
-
broadcast = () => {
|
|
580
|
-
server.hot.sendAll(HMR_EVENT, { name: federationConfig.name });
|
|
581
|
-
};
|
|
582
|
-
}
|
|
388
|
+
} }
|
|
583
389
|
};
|
|
584
390
|
}
|
|
585
391
|
function collectSharedNames(config) {
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
//#region src/runtime.d.ts
|
|
2
|
-
interface ModuleFederationScriptLoader {
|
|
2
|
+
export interface ModuleFederationScriptLoader {
|
|
3
3
|
loadScript(args: {
|
|
4
4
|
scriptId: string;
|
|
5
5
|
url: string;
|
|
6
6
|
parentUrl?: string;
|
|
7
7
|
}): Promise<void>;
|
|
8
8
|
}
|
|
9
|
-
interface RemoteEntryExports {
|
|
9
|
+
export interface RemoteEntryExports {
|
|
10
10
|
init(shareScope: unknown, initScope?: unknown[]): void | Promise<void>;
|
|
11
11
|
get(modulePath: string): () => Promise<unknown>;
|
|
12
12
|
}
|
|
13
13
|
declare global {
|
|
14
14
|
var __rollipop_script_loader__: ModuleFederationScriptLoader | undefined;
|
|
15
15
|
}
|
|
16
|
-
//#endregion
|
|
17
|
-
export { ModuleFederationScriptLoader, RemoteEntryExports };
|
|
16
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rollipop/plugin-module-federation",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
4
|
-
"stableVersion": "1.0.0-alpha.23",
|
|
3
|
+
"version": "1.0.0-alpha.30",
|
|
5
4
|
"homepage": "https://github.com/rollipop-dev/rollipop#readme",
|
|
6
5
|
"bugs": {
|
|
7
6
|
"url": "https://github.com/rollipop-dev/rollipop/issues"
|
|
@@ -37,17 +36,23 @@
|
|
|
37
36
|
"build": "vp pack"
|
|
38
37
|
},
|
|
39
38
|
"dependencies": {
|
|
40
|
-
"@module-federation/runtime": "2.
|
|
41
|
-
"@module-federation/sdk": "2.5.1",
|
|
39
|
+
"@module-federation/runtime": "2.9.1",
|
|
42
40
|
"dedent": "1.7.2",
|
|
43
|
-
"es-toolkit": "1.
|
|
41
|
+
"es-toolkit": "1.52.0"
|
|
44
42
|
},
|
|
45
43
|
"devDependencies": {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
44
|
+
"@types/react": "19.2.14",
|
|
45
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
46
|
+
"react": "19.2.3",
|
|
47
|
+
"rollipop": "1.0.0-alpha.30",
|
|
48
|
+
"typescript": "7.0.2",
|
|
49
|
+
"vite-plus": "0.3.3",
|
|
50
|
+
"vitest": "4.1.11"
|
|
49
51
|
},
|
|
50
52
|
"peerDependencies": {
|
|
51
|
-
"rollipop": "1.0.0-alpha.
|
|
53
|
+
"rollipop": "1.0.0-alpha.30"
|
|
54
|
+
},
|
|
55
|
+
"installConfig": {
|
|
56
|
+
"hoistingLimits": "none"
|
|
52
57
|
}
|
|
53
58
|
}
|