remote-reload-utils 0.0.14 → 0.0.17
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 +27 -0
- package/dist/index.d.ts +2 -5
- package/dist/loader/index.d.ts +4 -2
- package/dist/loader/remote-source/index.d.ts +28 -0
- package/dist/loader/utils.d.ts +6 -2
- package/dist/main.cjs +850 -1007
- package/dist/main.js +199 -321
- package/dist/plugins/register-shared-react.d.ts +6 -0
- package/dist/types/index.d.ts +1 -1
- package/loadRemote.md +67 -2
- package/package.json +28 -2
- package/dist/components/ErrorBoundary.d.ts +0 -34
- package/dist/components/RemoteModuleProvider.d.ts +0 -95
- package/dist/components/SuspenseLoader.d.ts +0 -131
- package/dist/components/index.d.ts +0 -6
package/dist/main.js
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
import { createInstance } from "@module-federation/enhanced/runtime";
|
|
2
|
-
import react, { Suspense, lazy, useCallback, useEffect, useState } from "react";
|
|
3
|
-
var __webpack_modules__ = {
|
|
4
|
-
"./src/styles/index.css": function() {}
|
|
5
|
-
};
|
|
6
|
-
var __webpack_module_cache__ = {};
|
|
7
|
-
function __webpack_require__(moduleId) {
|
|
8
|
-
var cachedModule = __webpack_module_cache__[moduleId];
|
|
9
|
-
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
10
|
-
var module = __webpack_module_cache__[moduleId] = {
|
|
11
|
-
exports: {}
|
|
12
|
-
};
|
|
13
|
-
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
14
|
-
return module.exports;
|
|
15
|
-
}
|
|
16
|
-
__webpack_require__("./src/styles/index.css");
|
|
17
2
|
async function loadReactVersion(version) {
|
|
18
3
|
const runtime = await createInstance({
|
|
19
4
|
name: `react_${version}_runtime`,
|
|
@@ -195,15 +180,46 @@ const DEFAULT_SHARED_CONFIG = {
|
|
|
195
180
|
shareConfig: {
|
|
196
181
|
singleton: true,
|
|
197
182
|
eager: true,
|
|
198
|
-
requiredVersion: false
|
|
199
|
-
|
|
183
|
+
requiredVersion: false,
|
|
184
|
+
strictVersion: false
|
|
185
|
+
},
|
|
186
|
+
strategy: 'loaded-first'
|
|
200
187
|
},
|
|
201
188
|
'react-dom': {
|
|
202
189
|
shareConfig: {
|
|
203
190
|
singleton: true,
|
|
204
191
|
eager: true,
|
|
205
|
-
requiredVersion: false
|
|
206
|
-
|
|
192
|
+
requiredVersion: false,
|
|
193
|
+
strictVersion: false
|
|
194
|
+
},
|
|
195
|
+
strategy: 'loaded-first'
|
|
196
|
+
},
|
|
197
|
+
'react-dom/client': {
|
|
198
|
+
shareConfig: {
|
|
199
|
+
singleton: true,
|
|
200
|
+
eager: true,
|
|
201
|
+
requiredVersion: false,
|
|
202
|
+
strictVersion: false
|
|
203
|
+
},
|
|
204
|
+
strategy: 'loaded-first'
|
|
205
|
+
},
|
|
206
|
+
'react/jsx-runtime': {
|
|
207
|
+
shareConfig: {
|
|
208
|
+
singleton: true,
|
|
209
|
+
eager: true,
|
|
210
|
+
requiredVersion: false,
|
|
211
|
+
strictVersion: false
|
|
212
|
+
},
|
|
213
|
+
strategy: 'loaded-first'
|
|
214
|
+
},
|
|
215
|
+
'react/jsx-dev-runtime': {
|
|
216
|
+
shareConfig: {
|
|
217
|
+
singleton: true,
|
|
218
|
+
eager: true,
|
|
219
|
+
requiredVersion: false,
|
|
220
|
+
strictVersion: false
|
|
221
|
+
},
|
|
222
|
+
strategy: 'loaded-first'
|
|
207
223
|
}
|
|
208
224
|
};
|
|
209
225
|
async function fetchLatestVersion(pkg) {
|
|
@@ -238,9 +254,30 @@ function setVersionCache(pkg, version) {
|
|
|
238
254
|
function buildCdnUrls(pkg, version) {
|
|
239
255
|
return DEFAULT_CDN_TEMPLATES.map((template)=>template.replace('{pkg}', pkg).replace('{version}', version));
|
|
240
256
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
257
|
+
const mfInstanceCache = new Map();
|
|
258
|
+
const mfInstanceLoadingCache = new Map();
|
|
259
|
+
function buildRemotesIdentity(remotes) {
|
|
260
|
+
if (0 === remotes.length) return '';
|
|
261
|
+
return remotes.map((remote)=>JSON.stringify({
|
|
262
|
+
name: remote.name,
|
|
263
|
+
entry: 'entry' in remote ? remote.entry : '',
|
|
264
|
+
version: 'version' in remote ? remote.version : '',
|
|
265
|
+
alias: remote.alias || '',
|
|
266
|
+
type: remote.type || '',
|
|
267
|
+
entryGlobalName: remote.entryGlobalName || ''
|
|
268
|
+
})).sort().join('|');
|
|
269
|
+
}
|
|
270
|
+
async function tryLoadRemote(scopeName, url, _retries, _delay, sharedConfig, plugins, extraRemotes = [], registerOptions = {}) {
|
|
271
|
+
const remotesIdentity = buildRemotesIdentity(extraRemotes);
|
|
272
|
+
const cacheKey = `${scopeName}::${url}::${remotesIdentity}::${registerOptions.force ? 'force' : 'normal'}`;
|
|
273
|
+
const cachedMfs = mfInstanceCache.get(cacheKey);
|
|
274
|
+
if (cachedMfs) return {
|
|
275
|
+
scopeName,
|
|
276
|
+
mf: cachedMfs
|
|
277
|
+
};
|
|
278
|
+
const loadingMfs = mfInstanceLoadingCache.get(cacheKey);
|
|
279
|
+
if (loadingMfs) return loadingMfs;
|
|
280
|
+
const loadPromise = Promise.resolve().then(()=>{
|
|
244
281
|
const mf = createInstance({
|
|
245
282
|
name: 'host',
|
|
246
283
|
remotes: [
|
|
@@ -255,24 +292,111 @@ async function tryLoadRemote(scopeName, url, retries, delay, sharedConfig, plugi
|
|
|
255
292
|
fallbackPlugin()
|
|
256
293
|
]
|
|
257
294
|
});
|
|
258
|
-
|
|
295
|
+
if (extraRemotes.length > 0) mf.registerRemotes(extraRemotes, registerOptions);
|
|
296
|
+
const result = {
|
|
259
297
|
scopeName,
|
|
260
298
|
mf
|
|
261
299
|
};
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
console.warn(`[MF] URL ${url} 加载失败,第 ${i + 1} 次重试...`);
|
|
265
|
-
if (i < retries - 1) await new Promise((res)=>setTimeout(res, delay));
|
|
266
|
-
}
|
|
267
|
-
throw new Error(`[MF] URL ${url} 经过 ${retries} 次重试仍加载失败。`, {
|
|
268
|
-
cause: lastError
|
|
300
|
+
mfInstanceCache.set(cacheKey, mf);
|
|
301
|
+
return result;
|
|
269
302
|
});
|
|
303
|
+
mfInstanceLoadingCache.set(cacheKey, loadPromise);
|
|
304
|
+
try {
|
|
305
|
+
return await loadPromise;
|
|
306
|
+
} finally{
|
|
307
|
+
mfInstanceLoadingCache.delete(cacheKey);
|
|
308
|
+
}
|
|
270
309
|
}
|
|
271
310
|
function getFinalSharedConfig(customShared) {
|
|
272
|
-
|
|
311
|
+
const globalReact = window.React;
|
|
312
|
+
const globalReactDOM = window.ReactDOM;
|
|
313
|
+
const globalShared = {};
|
|
314
|
+
if (globalReact && globalReactDOM) {
|
|
315
|
+
const isValidReact = 'object' == typeof globalReact && 'function' == typeof globalReact.useCallback;
|
|
316
|
+
if (isValidReact) {
|
|
317
|
+
globalShared.react = {
|
|
318
|
+
version: globalReact.version || '18.0.0',
|
|
319
|
+
lib: ()=>globalReact,
|
|
320
|
+
shareConfig: {
|
|
321
|
+
singleton: true,
|
|
322
|
+
eager: true,
|
|
323
|
+
requiredVersion: false,
|
|
324
|
+
strictVersion: false
|
|
325
|
+
},
|
|
326
|
+
strategy: 'loaded-first'
|
|
327
|
+
};
|
|
328
|
+
globalShared['react-dom'] = {
|
|
329
|
+
version: globalReactDOM.version || '18.0.0',
|
|
330
|
+
lib: ()=>globalReactDOM,
|
|
331
|
+
shareConfig: {
|
|
332
|
+
singleton: true,
|
|
333
|
+
eager: true,
|
|
334
|
+
requiredVersion: false,
|
|
335
|
+
strictVersion: false
|
|
336
|
+
},
|
|
337
|
+
strategy: 'loaded-first'
|
|
338
|
+
};
|
|
339
|
+
globalShared['react-dom/client'] = {
|
|
340
|
+
version: globalReactDOM.version || '18.0.0',
|
|
341
|
+
lib: ()=>globalReactDOM,
|
|
342
|
+
shareConfig: {
|
|
343
|
+
singleton: true,
|
|
344
|
+
eager: true,
|
|
345
|
+
requiredVersion: false,
|
|
346
|
+
strictVersion: false
|
|
347
|
+
},
|
|
348
|
+
strategy: 'loaded-first'
|
|
349
|
+
};
|
|
350
|
+
console.log('[getFinalSharedConfig] Using global React instance', {
|
|
351
|
+
version: globalReact.version
|
|
352
|
+
});
|
|
353
|
+
} else console.warn('[getFinalSharedConfig] Global React found but is invalid', {
|
|
354
|
+
type: typeof globalReact,
|
|
355
|
+
useCallback: typeof globalReact?.useCallback
|
|
356
|
+
});
|
|
357
|
+
} else console.log('[getFinalSharedConfig] No global React found, using default shared config');
|
|
358
|
+
const mergedShared = {
|
|
273
359
|
...DEFAULT_SHARED_CONFIG,
|
|
360
|
+
...globalShared,
|
|
274
361
|
...customShared || {}
|
|
275
362
|
};
|
|
363
|
+
const keepSingletonPackages = [
|
|
364
|
+
'react',
|
|
365
|
+
'react-dom',
|
|
366
|
+
'react-dom/client',
|
|
367
|
+
'react/jsx-runtime',
|
|
368
|
+
'react/jsx-dev-runtime'
|
|
369
|
+
];
|
|
370
|
+
for (const pkgName of keepSingletonPackages){
|
|
371
|
+
const base = mergedShared[pkgName] || {};
|
|
372
|
+
mergedShared[pkgName] = {
|
|
373
|
+
...base,
|
|
374
|
+
strategy: 'loaded-first',
|
|
375
|
+
shareConfig: {
|
|
376
|
+
singleton: true,
|
|
377
|
+
eager: true,
|
|
378
|
+
requiredVersion: false,
|
|
379
|
+
strictVersion: false,
|
|
380
|
+
...base.shareConfig || {}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if ('function' == typeof globalShared.react?.lib) mergedShared.react = {
|
|
385
|
+
...mergedShared.react || {},
|
|
386
|
+
lib: globalShared.react.lib,
|
|
387
|
+
version: globalShared.react.version
|
|
388
|
+
};
|
|
389
|
+
if ('function' == typeof globalShared['react-dom']?.lib) mergedShared['react-dom'] = {
|
|
390
|
+
...mergedShared['react-dom'] || {},
|
|
391
|
+
lib: globalShared['react-dom'].lib,
|
|
392
|
+
version: globalShared['react-dom'].version
|
|
393
|
+
};
|
|
394
|
+
if ('function' == typeof globalShared['react-dom/client']?.lib) mergedShared['react-dom/client'] = {
|
|
395
|
+
...mergedShared['react-dom/client'] || {},
|
|
396
|
+
lib: globalShared['react-dom/client'].lib,
|
|
397
|
+
version: globalShared['react-dom/client'].version
|
|
398
|
+
};
|
|
399
|
+
return mergedShared;
|
|
276
400
|
}
|
|
277
401
|
async function resolveFinalVersion(pkg, version, cacheTTL, revalidate) {
|
|
278
402
|
let finalVersion = version;
|
|
@@ -300,14 +424,55 @@ function buildFinalUrls(pkg, version, localFallback) {
|
|
|
300
424
|
if (localFallback) urls.push(localFallback);
|
|
301
425
|
return urls;
|
|
302
426
|
}
|
|
303
|
-
|
|
427
|
+
function dedupeRemotes(remotes) {
|
|
428
|
+
const map = new Map();
|
|
429
|
+
for (const remote of remotes){
|
|
430
|
+
if (!remote?.name) continue;
|
|
431
|
+
const key = [
|
|
432
|
+
remote.name,
|
|
433
|
+
'entry' in remote ? remote.entry || '' : '',
|
|
434
|
+
'version' in remote ? remote.version || '' : '',
|
|
435
|
+
remote.alias || ''
|
|
436
|
+
].join('::');
|
|
437
|
+
if (!map.has(key)) map.set(key, remote);
|
|
438
|
+
}
|
|
439
|
+
return Array.from(map.values());
|
|
440
|
+
}
|
|
441
|
+
async function resolveRegisteredRemotes(context, baseRemotes, remoteSourcePlugins) {
|
|
442
|
+
const remoteList = [
|
|
443
|
+
...baseRemotes
|
|
444
|
+
];
|
|
445
|
+
for (const plugin of remoteSourcePlugins)if (plugin.registerRemotes) try {
|
|
446
|
+
const pluginRemotes = await plugin.registerRemotes(context);
|
|
447
|
+
if (pluginRemotes?.length) remoteList.push(...pluginRemotes);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
throw new Error(`[MF] remote 来源插件 ${plugin.name} 执行失败: ${error.message}`);
|
|
450
|
+
}
|
|
451
|
+
return dedupeRemotes(remoteList).filter((remote)=>!(remote.name === context.scopeName && 'entry' in remote && remote.entry === context.currentEntry));
|
|
452
|
+
}
|
|
453
|
+
function createRemoteSourcePlugin(name, remotes) {
|
|
454
|
+
return {
|
|
455
|
+
name,
|
|
456
|
+
registerRemotes: ()=>remotes
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
async function loadRemoteMultiVersion(options, plugins = [], extraOptions = {}) {
|
|
304
460
|
const { name, pkg, version = 'latest', retries = 3, delay = 1000, localFallback, cacheTTL = 86400000, revalidate = true, shared: customShared } = options;
|
|
461
|
+
const { remoteSourcePlugins = [], baseRemotes = [], registerOptions = {} } = extraOptions;
|
|
305
462
|
const finalVersion = await resolveFinalVersion(pkg, version, cacheTTL, revalidate);
|
|
306
463
|
const scopeName = `${name}`;
|
|
307
464
|
const urls = buildFinalUrls(pkg, finalVersion, localFallback);
|
|
308
465
|
const finalSharedConfig = getFinalSharedConfig(customShared);
|
|
309
466
|
for (const url of urls)try {
|
|
310
|
-
|
|
467
|
+
const registeredRemotes = await resolveRegisteredRemotes({
|
|
468
|
+
options,
|
|
469
|
+
scopeName,
|
|
470
|
+
pkg,
|
|
471
|
+
finalVersion,
|
|
472
|
+
currentEntry: url,
|
|
473
|
+
allEntries: urls
|
|
474
|
+
}, baseRemotes, remoteSourcePlugins);
|
|
475
|
+
return await tryLoadRemote(scopeName, url, retries, delay, finalSharedConfig, plugins, registeredRemotes, registerOptions);
|
|
311
476
|
} catch (e) {
|
|
312
477
|
console.warn(`[MF] 切换 CDN 路径:${url} 失败,尝试下一个...`, e);
|
|
313
478
|
}
|
|
@@ -699,291 +864,4 @@ const eventBus = EventBusClass.create();
|
|
|
699
864
|
function createEventBus() {
|
|
700
865
|
return EventBusClass.create();
|
|
701
866
|
}
|
|
702
|
-
|
|
703
|
-
constructor(props){
|
|
704
|
-
super(props);
|
|
705
|
-
this.state = {
|
|
706
|
-
hasError: false,
|
|
707
|
-
error: null,
|
|
708
|
-
errorInfo: null
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
static getDerivedStateFromError(error) {
|
|
712
|
-
return {
|
|
713
|
-
hasError: true,
|
|
714
|
-
error,
|
|
715
|
-
errorInfo: null
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
componentDidCatch(error, errorInfo) {
|
|
719
|
-
this.setState({
|
|
720
|
-
errorInfo
|
|
721
|
-
});
|
|
722
|
-
this.props.onError?.(error, errorInfo);
|
|
723
|
-
console.error('[RemoteReloadUtils] ErrorBoundary caught error:', error, errorInfo);
|
|
724
|
-
}
|
|
725
|
-
handleReset = ()=>{
|
|
726
|
-
this.setState({
|
|
727
|
-
hasError: false,
|
|
728
|
-
error: null,
|
|
729
|
-
errorInfo: null
|
|
730
|
-
});
|
|
731
|
-
this.props.onReset?.();
|
|
732
|
-
};
|
|
733
|
-
render() {
|
|
734
|
-
if (this.state.hasError && this.state.error) {
|
|
735
|
-
if ('function' == typeof this.props.fallback) return this.props.fallback(this.state.error, this.handleReset);
|
|
736
|
-
if (void 0 !== this.props.fallback) return this.props.fallback;
|
|
737
|
-
return /*#__PURE__*/ react.createElement("div", {
|
|
738
|
-
role: "alert",
|
|
739
|
-
className: "p-4 border border-red-200 bg-red-50 rounded-lg"
|
|
740
|
-
}, /*#__PURE__*/ react.createElement("h3", {
|
|
741
|
-
className: "text-lg font-semibold mb-2"
|
|
742
|
-
}, "Something went wrong"), /*#__PURE__*/ react.createElement("p", {
|
|
743
|
-
className: "text-red-700"
|
|
744
|
-
}, this.state.error.message), /*#__PURE__*/ react.createElement("button", {
|
|
745
|
-
onClick: this.handleReset,
|
|
746
|
-
className: "mt-3 px-4 py-2 text-sm font-medium text-white bg-red-600 rounded hover:bg-red-700 cursor-pointer transition-colors"
|
|
747
|
-
}, "Try again"));
|
|
748
|
-
}
|
|
749
|
-
return this.props.children;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
function useRemoteModule({ pkg, version, moduleName, scopeName, onError, onLoad, retryKey = 0 }) {
|
|
753
|
-
const [moduleState, setModuleState] = useState({
|
|
754
|
-
loading: true,
|
|
755
|
-
error: null,
|
|
756
|
-
component: null
|
|
757
|
-
});
|
|
758
|
-
useEffect(()=>{
|
|
759
|
-
let mounted = true;
|
|
760
|
-
async function loadModule() {
|
|
761
|
-
try {
|
|
762
|
-
setModuleState((prev)=>({
|
|
763
|
-
...prev,
|
|
764
|
-
loading: true,
|
|
765
|
-
error: null
|
|
766
|
-
}));
|
|
767
|
-
const { mf } = await loadRemoteMultiVersion({
|
|
768
|
-
name: scopeName,
|
|
769
|
-
pkg,
|
|
770
|
-
version
|
|
771
|
-
}, []);
|
|
772
|
-
if (!mf || !mounted) return;
|
|
773
|
-
const mod = await mf.loadRemote(`${scopeName}/${moduleName}`);
|
|
774
|
-
if (!mounted) return;
|
|
775
|
-
if (mod && 'object' == typeof mod && 'default' in mod) {
|
|
776
|
-
const Component = mod.default;
|
|
777
|
-
setModuleState({
|
|
778
|
-
loading: false,
|
|
779
|
-
error: null,
|
|
780
|
-
component: Component
|
|
781
|
-
});
|
|
782
|
-
onLoad?.(Component);
|
|
783
|
-
} else throw new Error(`Module "${scopeName}/${moduleName}" does not export a default component`);
|
|
784
|
-
} catch (err) {
|
|
785
|
-
if (mounted) {
|
|
786
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
787
|
-
setModuleState({
|
|
788
|
-
loading: false,
|
|
789
|
-
error,
|
|
790
|
-
component: null
|
|
791
|
-
});
|
|
792
|
-
onError?.(error);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
loadModule();
|
|
797
|
-
return ()=>{
|
|
798
|
-
mounted = false;
|
|
799
|
-
};
|
|
800
|
-
}, [
|
|
801
|
-
pkg,
|
|
802
|
-
version,
|
|
803
|
-
moduleName,
|
|
804
|
-
scopeName,
|
|
805
|
-
onError,
|
|
806
|
-
onLoad,
|
|
807
|
-
retryKey
|
|
808
|
-
]);
|
|
809
|
-
return moduleState;
|
|
810
|
-
}
|
|
811
|
-
function RemoteModuleRenderer({ pkg, version, moduleName, scopeName, loadingFallback, errorFallback, componentProps, className, style, onError, onLoad }) {
|
|
812
|
-
const moduleState = useRemoteModule({
|
|
813
|
-
pkg,
|
|
814
|
-
version,
|
|
815
|
-
moduleName,
|
|
816
|
-
scopeName,
|
|
817
|
-
onError,
|
|
818
|
-
onLoad
|
|
819
|
-
});
|
|
820
|
-
const [retryKey, setRetryKey] = useState(0);
|
|
821
|
-
const handleRetry = useCallback(()=>{
|
|
822
|
-
setRetryKey((prev)=>prev + 1);
|
|
823
|
-
}, []);
|
|
824
|
-
useEffect(()=>{}, [
|
|
825
|
-
retryKey
|
|
826
|
-
]);
|
|
827
|
-
if (moduleState.loading) return /*#__PURE__*/ react.createElement("div", {
|
|
828
|
-
className: className,
|
|
829
|
-
style: style,
|
|
830
|
-
role: "status",
|
|
831
|
-
"aria-live": "polite"
|
|
832
|
-
}, loadingFallback || /*#__PURE__*/ react.createElement("div", {
|
|
833
|
-
className: "module-card module-card--loading"
|
|
834
|
-
}, /*#__PURE__*/ react.createElement("div", {
|
|
835
|
-
className: "loading-spinner",
|
|
836
|
-
"aria-hidden": "true"
|
|
837
|
-
}), /*#__PURE__*/ react.createElement("span", {
|
|
838
|
-
className: "text-gray-600"
|
|
839
|
-
}, "Loading ", moduleName, "...")));
|
|
840
|
-
if (moduleState.error) {
|
|
841
|
-
if ('function' == typeof errorFallback) return /*#__PURE__*/ react.createElement(react.Fragment, null, errorFallback(moduleState.error, handleRetry));
|
|
842
|
-
if (void 0 !== errorFallback) return /*#__PURE__*/ react.createElement(react.Fragment, null, errorFallback);
|
|
843
|
-
return /*#__PURE__*/ react.createElement("div", {
|
|
844
|
-
className: className,
|
|
845
|
-
style: style,
|
|
846
|
-
role: "alert"
|
|
847
|
-
}, /*#__PURE__*/ react.createElement("div", {
|
|
848
|
-
className: "module-card module-card--error"
|
|
849
|
-
}, /*#__PURE__*/ react.createElement("span", {
|
|
850
|
-
className: "error-icon",
|
|
851
|
-
"aria-hidden": "true"
|
|
852
|
-
}, "!"), /*#__PURE__*/ react.createElement("span", null, "Failed to load ", moduleName), /*#__PURE__*/ react.createElement("p", {
|
|
853
|
-
className: "error-message"
|
|
854
|
-
}, moduleState.error.message), /*#__PURE__*/ react.createElement("button", {
|
|
855
|
-
onClick: handleRetry,
|
|
856
|
-
className: "retry-button",
|
|
857
|
-
type: "button"
|
|
858
|
-
}, "Retry")));
|
|
859
|
-
}
|
|
860
|
-
if (!moduleState.component) return null;
|
|
861
|
-
const Component = moduleState.component;
|
|
862
|
-
return /*#__PURE__*/ react.createElement("div", {
|
|
863
|
-
className: className,
|
|
864
|
-
style: style
|
|
865
|
-
}, /*#__PURE__*/ react.createElement(Component, componentProps));
|
|
866
|
-
}
|
|
867
|
-
function RemoteModuleProvider(props) {
|
|
868
|
-
const { disableErrorBoundary, errorFallback, loadingFallback, errorBoundaryOptions } = props;
|
|
869
|
-
if (disableErrorBoundary) return /*#__PURE__*/ react.createElement(Suspense, {
|
|
870
|
-
fallback: loadingFallback || /*#__PURE__*/ react.createElement("div", null, "Loading...")
|
|
871
|
-
}, /*#__PURE__*/ react.createElement(RemoteModuleRenderer, props));
|
|
872
|
-
return /*#__PURE__*/ react.createElement(ErrorBoundary, {
|
|
873
|
-
fallback: errorFallback,
|
|
874
|
-
onError: errorBoundaryOptions?.onError,
|
|
875
|
-
onReset: errorBoundaryOptions?.onReset
|
|
876
|
-
}, /*#__PURE__*/ react.createElement(Suspense, {
|
|
877
|
-
fallback: loadingFallback || /*#__PURE__*/ react.createElement("div", null, "Loading...")
|
|
878
|
-
}, /*#__PURE__*/ react.createElement(RemoteModuleRenderer, props)));
|
|
879
|
-
}
|
|
880
|
-
function lazyRemote(options) {
|
|
881
|
-
const { pkg, version = 'latest', moduleName, scopeName, maxRetries = 3, retryDelay = 1000 } = options;
|
|
882
|
-
let retryCount = 0;
|
|
883
|
-
const loadComponent = async ()=>{
|
|
884
|
-
try {
|
|
885
|
-
const { mf } = await loadRemoteMultiVersion({
|
|
886
|
-
name: scopeName,
|
|
887
|
-
pkg,
|
|
888
|
-
version
|
|
889
|
-
}, []);
|
|
890
|
-
if (!mf) throw new Error(`[RemoteReloadUtils] Failed to get Module Federation instance for ${scopeName}`);
|
|
891
|
-
const mod = await mf.loadRemote(`${scopeName}/${moduleName}`);
|
|
892
|
-
if (!mod || 'object' != typeof mod || !('default' in mod)) throw new Error(`[RemoteReloadUtils] Module "${scopeName}/${moduleName}" does not export a default component`);
|
|
893
|
-
return {
|
|
894
|
-
default: mod.default
|
|
895
|
-
};
|
|
896
|
-
} catch (error) {
|
|
897
|
-
if (retryCount < maxRetries) {
|
|
898
|
-
retryCount++;
|
|
899
|
-
await new Promise((resolve)=>setTimeout(resolve, retryDelay * retryCount));
|
|
900
|
-
return loadComponent();
|
|
901
|
-
}
|
|
902
|
-
throw error;
|
|
903
|
-
}
|
|
904
|
-
};
|
|
905
|
-
return /*#__PURE__*/ lazy(loadComponent);
|
|
906
|
-
}
|
|
907
|
-
function SuspenseRemote({ fallback, children }) {
|
|
908
|
-
return /*#__PURE__*/ react.createElement(Suspense, {
|
|
909
|
-
fallback: fallback || /*#__PURE__*/ react.createElement("div", null, "Loading...")
|
|
910
|
-
}, children);
|
|
911
|
-
}
|
|
912
|
-
function SuspenseRemoteLoader({ pkg, version = 'latest', moduleName, scopeName, fallback, errorFallback, componentProps }) {
|
|
913
|
-
const RemoteComponent = lazyRemote({
|
|
914
|
-
pkg,
|
|
915
|
-
version,
|
|
916
|
-
moduleName,
|
|
917
|
-
scopeName
|
|
918
|
-
});
|
|
919
|
-
if (errorFallback) return /*#__PURE__*/ react.createElement(ErrorBoundary, {
|
|
920
|
-
fallback: errorFallback
|
|
921
|
-
}, /*#__PURE__*/ react.createElement(Suspense, {
|
|
922
|
-
fallback: fallback || /*#__PURE__*/ react.createElement("div", null, "Loading...")
|
|
923
|
-
}, /*#__PURE__*/ react.createElement(RemoteComponent, componentProps)));
|
|
924
|
-
return /*#__PURE__*/ react.createElement(Suspense, {
|
|
925
|
-
fallback: fallback || /*#__PURE__*/ react.createElement("div", null, "Loading...")
|
|
926
|
-
}, /*#__PURE__*/ react.createElement(RemoteComponent, componentProps));
|
|
927
|
-
}
|
|
928
|
-
function withRemote(WrappedComponent, options) {
|
|
929
|
-
const RemoteComponent = lazyRemote(options);
|
|
930
|
-
return function(props) {
|
|
931
|
-
return /*#__PURE__*/ react.createElement(Suspense, {
|
|
932
|
-
fallback: /*#__PURE__*/ react.createElement("div", null, "Loading...")
|
|
933
|
-
}, /*#__PURE__*/ react.createElement(RemoteComponent, props));
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
function useRemoteModuleHook(options) {
|
|
937
|
-
const { pkg, version = 'latest', moduleName, scopeName } = options;
|
|
938
|
-
const [state, setState] = useState({
|
|
939
|
-
component: null,
|
|
940
|
-
loading: true,
|
|
941
|
-
error: null
|
|
942
|
-
});
|
|
943
|
-
useEffect(()=>{
|
|
944
|
-
let mounted = true;
|
|
945
|
-
async function load() {
|
|
946
|
-
try {
|
|
947
|
-
setState((prev)=>({
|
|
948
|
-
...prev,
|
|
949
|
-
loading: true,
|
|
950
|
-
error: null
|
|
951
|
-
}));
|
|
952
|
-
const { mf } = await loadRemoteMultiVersion({
|
|
953
|
-
name: scopeName,
|
|
954
|
-
pkg,
|
|
955
|
-
version
|
|
956
|
-
}, []);
|
|
957
|
-
if (!mf) throw new Error(`[RemoteReloadUtils] Failed to get Module Federation instance for ${scopeName}`);
|
|
958
|
-
const mod = await mf.loadRemote(`${scopeName}/${moduleName}`);
|
|
959
|
-
if (mounted) if (mod && 'object' == typeof mod && 'default' in mod) setState({
|
|
960
|
-
component: mod.default,
|
|
961
|
-
loading: false,
|
|
962
|
-
error: null
|
|
963
|
-
});
|
|
964
|
-
else throw new Error(`[RemoteReloadUtils] Module "${scopeName}/${moduleName}" does not export a default component`);
|
|
965
|
-
} catch (err) {
|
|
966
|
-
if (mounted) setState({
|
|
967
|
-
component: null,
|
|
968
|
-
loading: false,
|
|
969
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
load();
|
|
974
|
-
return ()=>{
|
|
975
|
-
mounted = false;
|
|
976
|
-
};
|
|
977
|
-
}, [
|
|
978
|
-
pkg,
|
|
979
|
-
version,
|
|
980
|
-
moduleName,
|
|
981
|
-
scopeName
|
|
982
|
-
]);
|
|
983
|
-
return {
|
|
984
|
-
component: state.component,
|
|
985
|
-
loading: state.loading,
|
|
986
|
-
error: state.error
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
export { ErrorBoundary, RemoteModuleProvider, RemoteModuleRenderer, SuspenseRemote, SuspenseRemoteLoader, buildCdnUrls, buildFinalUrls, cancelPreload, checkModuleLoadable, checkRemoteHealth, checkVersionCompatibility, clearPreloadCache, compareVersions, createEventBus, eventBus, extractMajorVersion, fallbackPlugin, fetchAvailableVersions, fetchLatestVersion, findCompatibleVersion, formatHealthStatus, getCompatibleReactVersions, getFinalSharedConfig, getLatestVersion, getLoadedRemotes, getPreloadStatus, getRemoteHealthReport, getStableVersions, getVersionCache, isPrerelease, isRemoteLoaded, lazyRemote, loadReactVersion, loadRemoteMultiVersion, parseVersion, preloadRemote, preloadRemoteList, registerLoadedModule, registerRemoteInstance, resolveFinalVersion, satisfiesVersion, setVersionCache, sortVersions, tryLoadRemote, unloadAll, unloadRemote, useRemoteModule, useRemoteModuleHook, withRemote };
|
|
867
|
+
export { buildCdnUrls, buildFinalUrls, cancelPreload, checkModuleLoadable, checkRemoteHealth, checkVersionCompatibility, clearPreloadCache, compareVersions, createEventBus, createRemoteSourcePlugin, eventBus, extractMajorVersion, fallbackPlugin, fetchAvailableVersions, fetchLatestVersion, findCompatibleVersion, formatHealthStatus, getCompatibleReactVersions, getFinalSharedConfig, getLatestVersion, getLoadedRemotes, getPreloadStatus, getRemoteHealthReport, getStableVersions, getVersionCache, isPrerelease, isRemoteLoaded, loadReactVersion, loadRemoteMultiVersion, parseVersion, preloadRemote, preloadRemoteList, registerLoadedModule, registerRemoteInstance, resolveFinalVersion, satisfiesVersion, setVersionCache, sortVersions, tryLoadRemote, unloadAll, unloadRemote };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';
|
|
2
|
+
export declare function initSharedScope(): void;
|
|
3
|
+
/**
|
|
4
|
+
* 注册全局 React/ReactDOM 到 Module Federation 共享作用域
|
|
5
|
+
*/
|
|
6
|
+
export declare const registerSharedReact: () => ModuleFederationRuntimePlugin;
|
package/dist/types/index.d.ts
CHANGED
package/loadRemote.md
CHANGED
|
@@ -168,7 +168,8 @@ async function loadMultipleComponents() {
|
|
|
168
168
|
```typescript
|
|
169
169
|
function loadRemoteMultiVersion(
|
|
170
170
|
options: LoadRemoteOptions,
|
|
171
|
-
plugins
|
|
171
|
+
plugins?: ModuleFederationRuntimePlugin[],
|
|
172
|
+
extraOptions?: LoadRemoteExtraOptions,
|
|
172
173
|
): Promise<LoadResult>
|
|
173
174
|
```
|
|
174
175
|
|
|
@@ -192,6 +193,14 @@ function loadRemoteMultiVersion(
|
|
|
192
193
|
|
|
193
194
|
Module Federation 运行时插件数组,默认会添加 `fallbackPlugin()`。
|
|
194
195
|
|
|
196
|
+
**extraOptions**: `LoadRemoteExtraOptions`
|
|
197
|
+
|
|
198
|
+
| 属性 | 类型 | 必填 | 默认值 | 描述 |
|
|
199
|
+
|------|------|------|--------|------|
|
|
200
|
+
| `baseRemotes` | `RuntimeRemote[]` | ❌ | `[]` | 直接注册的附加 remote 列表 |
|
|
201
|
+
| `remoteSourcePlugins` | `RemoteSourcePlugin[]` | ❌ | `[]` | 通过插件动态返回并注册 remote 列表 |
|
|
202
|
+
| `registerOptions` | `{ force?: boolean }` | ❌ | `{}` | 透传给 `registerRemotes` 的配置 |
|
|
203
|
+
|
|
195
204
|
#### 返回值
|
|
196
205
|
|
|
197
206
|
```typescript
|
|
@@ -869,6 +878,38 @@ const { mf } = await loadRemoteMultiVersion(options, [
|
|
|
869
878
|
]);
|
|
870
879
|
```
|
|
871
880
|
|
|
881
|
+
#### 4. 通过 `registerRemotes` 动态注册多个来源
|
|
882
|
+
|
|
883
|
+
```typescript
|
|
884
|
+
import { loadRemoteMultiVersion, createRemoteSourcePlugin } from 'remote-reload-utils';
|
|
885
|
+
|
|
886
|
+
const remoteSourcePlugin = createRemoteSourcePlugin('multi-remote-source', [
|
|
887
|
+
{
|
|
888
|
+
name: 'remote_ui_v2',
|
|
889
|
+
entry: 'https://cdn.example.com/remote-ui-v2/dist/remoteEntry.js',
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
name: 'remote_widget',
|
|
893
|
+
entry: 'https://cdn.example.com/remote-widget/dist/remoteEntry.js',
|
|
894
|
+
},
|
|
895
|
+
]);
|
|
896
|
+
|
|
897
|
+
const { scopeName, mf } = await loadRemoteMultiVersion(
|
|
898
|
+
{
|
|
899
|
+
name: 'react_mf_lib',
|
|
900
|
+
pkg: 'test-mf-unpkg',
|
|
901
|
+
version: 'latest',
|
|
902
|
+
},
|
|
903
|
+
[],
|
|
904
|
+
{
|
|
905
|
+
remoteSourcePlugins: [remoteSourcePlugin],
|
|
906
|
+
},
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
const Button = await mf.loadRemote(`${scopeName}/Button`);
|
|
910
|
+
const Widget = await mf.loadRemote('remote_widget/Widget');
|
|
911
|
+
```
|
|
912
|
+
|
|
872
913
|
#### 2. 监控加载状态
|
|
873
914
|
|
|
874
915
|
```typescript
|
|
@@ -1238,7 +1279,31 @@ interface LoadRemoteOptions {
|
|
|
1238
1279
|
localFallback?: string; // 本地兜底
|
|
1239
1280
|
cacheTTL?: number; // 缓存时间
|
|
1240
1281
|
revalidate?: boolean; // 灰度更新
|
|
1241
|
-
shared?: Record<string,
|
|
1282
|
+
shared?: Record<string, any>; // 自定义 shared 配置
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
interface LoadRemoteExtraOptions {
|
|
1286
|
+
remoteSourcePlugins?: RemoteSourcePlugin[];
|
|
1287
|
+
baseRemotes?: RuntimeRemote[];
|
|
1288
|
+
registerOptions?: {
|
|
1289
|
+
force?: boolean;
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
interface RemoteSourcePluginContext {
|
|
1294
|
+
options: LoadRemoteOptions;
|
|
1295
|
+
scopeName: string;
|
|
1296
|
+
pkg: string;
|
|
1297
|
+
finalVersion: string;
|
|
1298
|
+
currentEntry: string;
|
|
1299
|
+
allEntries: string[];
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
interface RemoteSourcePlugin {
|
|
1303
|
+
name: string;
|
|
1304
|
+
registerRemotes?: (
|
|
1305
|
+
context: RemoteSourcePluginContext
|
|
1306
|
+
) => RuntimeRemote[] | void | Promise<RuntimeRemote[] | void>;
|
|
1242
1307
|
}
|
|
1243
1308
|
|
|
1244
1309
|
interface VersionCache {
|