@rollipop/plugin-module-federation 1.0.0-alpha.27 → 1.0.0-alpha.29

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
@@ -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 / polyfills are skipped because the host has already initialized the React Native runtime.
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
@@ -1,5 +1,4 @@
1
1
  import { Plugin } from "rollipop";
2
-
3
2
  //#region src/types.d.ts
4
3
  /**
5
4
  * Module Federation plugin configuration.
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 isInvalidated = this.invalidatedIds.has(id);
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
@@ -383,15 +264,6 @@ function generateRemoteProxyCode({ remoteId, reactAware }) {
383
264
  }
384
265
 
385
266
  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
267
  const mod = __getMod();
396
268
  if (mod === undefined) {
397
269
  throw __ensureLoaded();
@@ -483,26 +355,23 @@ function moduleFederationPlugin(config) {
483
355
  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
356
  let resolvedConfig = null;
485
357
  let normalized = null;
486
- let broadcast = null;
487
358
  const exposesAbsolute = {};
488
359
  const sharedRoots = new Set(collectSharedNames(federationConfig));
489
- let debounceTimer = null;
490
360
  return {
491
361
  name: PLUGIN_NAME,
492
362
  config(pluginConfig) {
493
- const serializer = pluginConfig.serializer ??= {};
494
363
  if (hasExposes) {
495
- serializer.prelude = [];
496
- serializer.polyfills = [];
497
- return { dangerously_overrideRolldownOptions: (opts) => ({
364
+ pluginConfig.prelude = [];
365
+ pluginConfig.polyfills = [];
366
+ return { rolldownOptions: (opts, context) => ({
498
367
  input: opts.input,
499
368
  output: {
500
369
  ...opts.output,
501
- format: "iife"
370
+ format: context.buildType === "serve" ? "rollipop" : "iife"
502
371
  }
503
372
  }) };
504
373
  }
505
- if (hasRemotes && config.runtime?.implement != null) (serializer.polyfills ??= []).push({
374
+ if (hasRemotes && config.runtime?.implement != null) (pluginConfig.polyfills ??= []).push({
506
375
  type: "iife",
507
376
  code: config.runtime.implement
508
377
  });
@@ -565,21 +434,7 @@ function moduleFederationPlugin(config) {
565
434
  return { code: magicString };
566
435
  }
567
436
  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
- }
437
+ } }
583
438
  };
584
439
  }
585
440
  function collectSharedNames(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rollipop/plugin-module-federation",
3
- "version": "1.0.0-alpha.27",
3
+ "version": "1.0.0-alpha.29",
4
4
  "stableVersion": "1.0.0-alpha.23",
5
5
  "homepage": "https://github.com/rollipop-dev/rollipop#readme",
6
6
  "bugs": {
@@ -37,17 +37,18 @@
37
37
  "build": "vp pack"
38
38
  },
39
39
  "dependencies": {
40
- "@module-federation/runtime": "2.5.1",
41
- "@module-federation/sdk": "2.5.1",
40
+ "@module-federation/runtime": "2.8.0",
42
41
  "dedent": "1.7.2",
43
- "es-toolkit": "1.47.0"
42
+ "es-toolkit": "1.49.0"
44
43
  },
45
44
  "devDependencies": {
46
- "rollipop": "1.0.0-alpha.27",
45
+ "@vitest/coverage-v8": "4.1.10",
46
+ "rollipop": "1.0.0-alpha.29",
47
47
  "typescript": "6.0.3",
48
- "vite-plus": "0.1.24"
48
+ "vite-plus": "0.2.5",
49
+ "vitest": "4.1.10"
49
50
  },
50
51
  "peerDependencies": {
51
- "rollipop": "1.0.0-alpha.27"
52
+ "rollipop": "1.0.0-alpha.29"
52
53
  }
53
54
  }