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/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
- async function tryLoadRemote(scopeName, url, retries, delay, sharedConfig, plugins) {
242
- let lastError;
243
- for(let i = 0; i < retries; i++)try {
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
- return {
295
+ if (extraRemotes.length > 0) mf.registerRemotes(extraRemotes, registerOptions);
296
+ const result = {
259
297
  scopeName,
260
298
  mf
261
299
  };
262
- } catch (e) {
263
- lastError = e;
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
- return {
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
- async function loadRemoteMultiVersion(options, plugins) {
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
- return await tryLoadRemote(scopeName, url, retries, delay, finalSharedConfig, plugins);
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
- class ErrorBoundary extends react.Component {
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;
@@ -9,7 +9,7 @@ export interface LoadRemoteOptions {
9
9
  localFallback?: string;
10
10
  cacheTTL?: number;
11
11
  revalidate?: boolean;
12
- shared?: Record<string, ModuleFederationRuntimePlugin>;
12
+ shared?: Record<string, any>;
13
13
  }
14
14
  export interface VersionCache {
15
15
  [pkg: string]: {
package/loadRemote.md CHANGED
@@ -168,7 +168,8 @@ async function loadMultipleComponents() {
168
168
  ```typescript
169
169
  function loadRemoteMultiVersion(
170
170
  options: LoadRemoteOptions,
171
- plugins: ModuleFederationRuntimePlugin[]
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, ModuleFederationRuntimePlugin>; // 自定义 shared 配置
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 {