remote-reload-utils 0.0.16 → 1.0.0

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 9306c65: changeset cli 集成 & 变更日志记录
8
+
3
9
  所有重要的项目变更都将记录在此文件中。
4
10
 
5
11
  格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
@@ -7,63 +13,62 @@
7
13
 
8
14
  ## [未发布]
9
15
 
16
+ ## [0.0.17] - 2026-03-19
17
+
18
+ ### Release
19
+
20
+ - Published version 0.0.17 with patch bump
21
+
10
22
  ## [0.0.16] - 2026-03-18
11
23
 
12
24
  ### Release
13
25
 
14
26
  - Published version 0.0.16 with patch bump
15
27
 
16
-
17
28
  ## [0.0.15] - 2026-03-17
18
29
 
19
30
  ### Release
20
31
 
21
32
  - Published version 0.0.15 with patch bump
22
33
 
23
-
24
34
  ## [0.0.14] - 2026-03-17
25
35
 
26
36
  ### Release
27
37
 
28
38
  - Published version 0.0.14 with patch bump
29
39
 
30
-
31
40
  ## [0.0.13] - 2026-03-17
32
41
 
33
42
  ### Release
34
43
 
35
44
  - Published version 0.0.13 with patch bump
36
45
 
37
-
38
46
  ## [0.0.12] - 2026-03-16
39
47
 
40
48
  ### Release
41
49
 
42
50
  - Published version 0.0.12 with patch bump
43
51
 
44
-
45
52
  ## [0.0.11] - 2026-03-16
46
53
 
47
54
  ### Release
48
55
 
49
56
  - Published version 0.0.11 with patch bump
50
57
 
51
-
52
58
  ## [0.0.10] - 2026-03-15
53
59
 
54
60
  ### Release
55
61
 
56
62
  - Published version 0.0.10 with patch bump
57
63
 
58
-
59
64
  ## [0.0.9] - 2026-03-15
60
65
 
61
66
  ### Release
62
67
 
63
68
  - Published version 0.0.9 with patch bump
64
69
 
65
-
66
70
  ### Refactor
71
+
67
72
  - 重构项目为模块化目录结构
68
73
  - `src/loader/` - 核心加载模块
69
74
  - `src/version/` - 版本管理模块
@@ -77,6 +82,7 @@
77
82
  - 更新 package.json types 路径为 `./dist/index.d.ts`
78
83
 
79
84
  ### Changed
85
+
80
86
  - 修复 preloadRemote 引用从 loadRemote2 改为 loadRemote
81
87
  - 移除 App.tsx 中未使用的 React 导入
82
88
  - 添加 release 脚本到 package.json
@@ -84,6 +90,7 @@
84
90
  ## [0.0.8] - 2026-03-15
85
91
 
86
92
  ### Refactor
93
+
87
94
  - 抽离公共工具函数到 `loadRemoteUtils.ts`
88
95
  - 新增 `fetchLatestVersion()` - 从 npm registry 获取最新版本
89
96
  - 新增 `getVersionCache()` / `setVersionCache()` - 缓存管理
@@ -96,14 +103,17 @@
96
103
  - 简化 `loadRemote.ts`,使用公共工具函数
97
104
 
98
105
  ### Changed
106
+
99
107
  - 更新 `index.ts` 导出,新增工具函数导出
100
108
 
101
109
  ### Docs
110
+
102
111
  - 更新 `loadRemote.md`,添加工具函数 API 文档
103
112
 
104
113
  ## [0.0.7] - 2026-03-15
105
114
 
106
115
  ### Features
116
+
107
117
  - 新增微前端工具函数集
108
118
  - 远程模块加载
109
119
  - React 多版本支持
@@ -114,15 +124,18 @@
114
124
  ## [0.0.6] - 2026-03-14
115
125
 
116
126
  ### Docs
127
+
117
128
  - 更新 README 和 loadRemote.md 文档
118
129
  - 添加使用指南和 API 参考
119
130
 
120
131
  ### Chore
132
+
121
133
  - 更新 remote-reload-utils 版本至 0.0.8
122
134
 
123
135
  ## [0.0.5] - 2026-03-14
124
136
 
125
137
  ### Features
138
+
126
139
  - 添加多版本远程加载功能
127
140
  - 支持自定义 shared 配置
128
141
  - 改进错误处理机制
@@ -130,33 +143,39 @@
130
143
  ## [0.0.4] - 2026-03-14
131
144
 
132
145
  ### Chore
146
+
133
147
  - 更新错误处理逻辑
134
148
  - 优化加载流程
135
149
 
136
150
  ## [0.0.3] - 2026-03-14
137
151
 
138
152
  ### Docs
153
+
139
154
  - 添加项目描述和 git 仓库信息
140
155
  - 更新 README 中的示例代码
141
156
  - 将 loadRemote.md 从 src 移动到根目录
142
157
 
143
158
  ### Chore
159
+
144
160
  - 更新 remote-reload-utils 的版本号至 0.0.3
145
161
 
146
162
  ## [0.0.2] - 2026-03-14
147
163
 
148
164
  ### Features
165
+
149
166
  - 添加 React 多版本加载功能(v17/v18/v19)
150
167
  - 添加共享配置支持
151
168
  - 默认共享 react 和 react-dom
152
169
  - 支持自定义共享模块
153
170
 
154
171
  ### Docs
172
+
155
173
  - 更新示例代码展示如何使用 loadRemoteMultiVersion
156
174
 
157
175
  ## [0.0.1] - 2026-03-14
158
176
 
159
177
  ### Features
178
+
160
179
  - 实现多版本远程模块加载功能
161
180
  - 支持 CDN 故障转移(jsdelivr、unpkg)
162
181
  - 支持本地 fallback 兜底
@@ -170,30 +189,32 @@
170
189
 
171
190
  ## 版本说明
172
191
 
173
- | 版本 | 日期 | 主要变更 |
174
- |------|------|----------|
175
- | 0.0.16 | 2026-03-18 | patch 版本发布 |
176
- |------|------|----------|
177
- | 0.0.15 | 2026-03-17 | patch 版本发布 |
178
- |------|------|----------|
179
- | 0.0.14 | 2026-03-17 | patch 版本发布 |
180
- |------|------|----------|
181
- | 0.0.13 | 2026-03-17 | patch 版本发布 |
182
- |------|------|----------|
183
- | 0.0.12 | 2026-03-16 | patch 版本发布 |
184
- |------|------|----------|
185
- | 0.0.11 | 2026-03-16 | patch 版本发布 |
186
- |------|------|----------|
187
- | 0.0.10 | 2026-03-15 | patch 版本发布 |
188
- |------|------|----------|
189
- | 0.0.9 | 2026-03-15 | patch 版本发布 |
190
- |------|------|----------|
191
- | 0.0.9 | 2026-03-15 | 重构为模块化目录结构 |
192
- | 0.0.8 | 2026-03-15 | 重构代码结构,抽离公共工具函数 |
193
- | 0.0.7 | 2026-03-15 | 新增微前端工具函数集 |
194
- | 0.0.6 | 2026-03-14 | 文档更新 |
195
- | 0.0.5 | 2026-03-14 | 多版本远程加载 |
196
- | 0.0.4 | 2026-03-14 | 错误处理优化 |
197
- | 0.0.3 | 2026-03-14 | 文档和配置更新 |
198
- | 0.0.2 | 2026-03-14 | React 多版本支持 |
199
- | 0.0.1 | 2026-03-14 | 初始版本 |
192
+ | 版本 | 日期 | 主要变更 |
193
+ | ------ | ---------- | ------------------------------ |
194
+ | 0.0.17 | 2026-03-19 | patch 版本发布 |
195
+ | ------ | ------ | ---------- |
196
+ | 0.0.16 | 2026-03-18 | patch 版本发布 |
197
+ | ------ | ------ | ---------- |
198
+ | 0.0.15 | 2026-03-17 | patch 版本发布 |
199
+ | ------ | ------ | ---------- |
200
+ | 0.0.14 | 2026-03-17 | patch 版本发布 |
201
+ | ------ | ------ | ---------- |
202
+ | 0.0.13 | 2026-03-17 | patch 版本发布 |
203
+ | ------ | ------ | ---------- |
204
+ | 0.0.12 | 2026-03-16 | patch 版本发布 |
205
+ | ------ | ------ | ---------- |
206
+ | 0.0.11 | 2026-03-16 | patch 版本发布 |
207
+ | ------ | ------ | ---------- |
208
+ | 0.0.10 | 2026-03-15 | patch 版本发布 |
209
+ | ------ | ------ | ---------- |
210
+ | 0.0.9 | 2026-03-15 | patch 版本发布 |
211
+ | ------ | ------ | ---------- |
212
+ | 0.0.9 | 2026-03-15 | 重构为模块化目录结构 |
213
+ | 0.0.8 | 2026-03-15 | 重构代码结构,抽离公共工具函数 |
214
+ | 0.0.7 | 2026-03-15 | 新增微前端工具函数集 |
215
+ | 0.0.6 | 2026-03-14 | 文档更新 |
216
+ | 0.0.5 | 2026-03-14 | 多版本远程加载 |
217
+ | 0.0.4 | 2026-03-14 | 错误处理优化 |
218
+ | 0.0.3 | 2026-03-14 | 文档和配置更新 |
219
+ | 0.0.2 | 2026-03-14 | React 多版本支持 |
220
+ | 0.0.1 | 2026-03-14 | 初始版本 |
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { loadReactVersion } from './version/react';
2
2
  export { checkVersionCompatibility, satisfiesVersion, findCompatibleVersion, getCompatibleReactVersions, fetchAvailableVersions, sortVersions, getLatestVersion, getStableVersions, extractMajorVersion, isPrerelease, compareVersions, parseVersion, } from './version';
3
- export { loadRemoteMultiVersion } from './loader';
4
- export { fetchLatestVersion, getVersionCache, setVersionCache, buildCdnUrls, tryLoadRemote, getFinalSharedConfig, resolveFinalVersion, buildFinalUrls, type LoadResult, } from './loader/utils';
3
+ export { loadRemoteMultiVersion, createRemoteSourcePlugin, type RemoteSourcePlugin, type RemoteSourcePluginContext, type LoadRemoteExtraOptions, } from './loader';
4
+ export { fetchLatestVersion, getVersionCache, setVersionCache, buildCdnUrls, tryLoadRemote, getFinalSharedConfig, resolveFinalVersion, buildFinalUrls, type LoadResult, type RuntimeRemote, } from './loader/utils';
5
5
  export { preloadRemote, preloadRemoteList, cancelPreload, clearPreloadCache, getPreloadStatus, } from './preload';
6
6
  export { unloadRemote, unloadAll, registerRemoteInstance, registerLoadedModule, getLoadedRemotes, isRemoteLoaded, } from './unload';
7
7
  export { checkRemoteHealth, checkModuleLoadable, getRemoteHealthReport, formatHealthStatus, } from './health';
@@ -1,6 +1,8 @@
1
- import { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';
1
+ import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';
2
2
  import type { LoadRemoteOptions } from '../types';
3
+ import { createRemoteSourcePlugin, type LoadRemoteExtraOptions, type RemoteSourcePlugin, type RemoteSourcePluginContext } from './remote-source';
4
+ export { createRemoteSourcePlugin, type RemoteSourcePlugin, type RemoteSourcePluginContext, type LoadRemoteExtraOptions, };
3
5
  /**
4
6
  * 多版本共存的 loadRemote
5
7
  */
6
- export declare function loadRemoteMultiVersion(options: LoadRemoteOptions, plugins: ModuleFederationRuntimePlugin[]): Promise<import("./utils").LoadResult>;
8
+ export declare function loadRemoteMultiVersion(options: LoadRemoteOptions, plugins?: ModuleFederationRuntimePlugin[], extraOptions?: LoadRemoteExtraOptions): Promise<import("./utils").LoadResult>;
@@ -0,0 +1,28 @@
1
+ import type { LoadRemoteOptions } from '../../types';
2
+ import type { RuntimeRemote } from '../utils';
3
+ type MaybePromise<T> = T | Promise<T>;
4
+ export interface RemoteSourcePluginContext {
5
+ options: LoadRemoteOptions;
6
+ scopeName: string;
7
+ pkg: string;
8
+ finalVersion: string;
9
+ currentEntry: string;
10
+ allEntries: string[];
11
+ }
12
+ export interface RemoteSourcePlugin {
13
+ name: string;
14
+ registerRemotes?: (context: RemoteSourcePluginContext) => MaybePromise<RuntimeRemote[] | void>;
15
+ }
16
+ export interface LoadRemoteExtraOptions {
17
+ remoteSourcePlugins?: RemoteSourcePlugin[];
18
+ baseRemotes?: RuntimeRemote[];
19
+ registerOptions?: {
20
+ force?: boolean;
21
+ };
22
+ }
23
+ export declare function resolveRegisteredRemotes(context: RemoteSourcePluginContext, baseRemotes: RuntimeRemote[], remoteSourcePlugins: RemoteSourcePlugin[]): Promise<RuntimeRemote[]>;
24
+ /**
25
+ * 创建静态 remote 来源插件
26
+ */
27
+ export declare function createRemoteSourcePlugin(name: string, remotes: RuntimeRemote[]): RemoteSourcePlugin;
28
+ export {};
@@ -20,10 +20,14 @@ export interface LoadResult {
20
20
  scopeName: string;
21
21
  mf: ReturnType<typeof createInstance>;
22
22
  }
23
+ export type RuntimeRemote = Parameters<ReturnType<typeof createInstance>['registerRemotes']>[0][number];
23
24
  /**
24
- * 尝试加载单个远程模块 URL,包含重试逻辑
25
+ * 尝试加载单个远程模块 URL
26
+ * 注意:实际的重试机制在外层 loadRemoteMultiVersion 的 URL 遍历中实现
25
27
  */
26
- export declare function tryLoadRemote(scopeName: string, url: string, retries: number, delay: number, sharedConfig: Record<string, any>, plugins: ModuleFederationRuntimePlugin[]): Promise<LoadResult>;
28
+ export declare function tryLoadRemote(scopeName: string, url: string, _retries: number, _delay: number, sharedConfig: Record<string, any>, plugins: ModuleFederationRuntimePlugin[], extraRemotes?: RuntimeRemote[], registerOptions?: {
29
+ force?: boolean;
30
+ }): Promise<LoadResult>;
27
31
  /**
28
32
  * 获取最终的共享配置
29
33
  */
package/dist/main.cjs CHANGED
@@ -60,6 +60,7 @@ __webpack_require__.d(__webpack_exports__, {
60
60
  satisfiesVersion: ()=>satisfiesVersion,
61
61
  formatHealthStatus: ()=>formatHealthStatus,
62
62
  unloadAll: ()=>unloadAll,
63
+ createRemoteSourcePlugin: ()=>createRemoteSourcePlugin,
63
64
  setVersionCache: ()=>setVersionCache,
64
65
  isPrerelease: ()=>isPrerelease,
65
66
  checkRemoteHealth: ()=>checkRemoteHealth,
@@ -323,8 +324,20 @@ function buildCdnUrls(pkg, version) {
323
324
  }
324
325
  const mfInstanceCache = new Map();
325
326
  const mfInstanceLoadingCache = new Map();
326
- async function tryLoadRemote(scopeName, url, retries, delay, sharedConfig, plugins) {
327
- const cacheKey = `${scopeName}::${url}`;
327
+ function buildRemotesIdentity(remotes) {
328
+ if (0 === remotes.length) return '';
329
+ return remotes.map((remote)=>JSON.stringify({
330
+ name: remote.name,
331
+ entry: 'entry' in remote ? remote.entry : '',
332
+ version: 'version' in remote ? remote.version : '',
333
+ alias: remote.alias || '',
334
+ type: remote.type || '',
335
+ entryGlobalName: remote.entryGlobalName || ''
336
+ })).sort().join('|');
337
+ }
338
+ async function tryLoadRemote(scopeName, url, _retries, _delay, sharedConfig, plugins, extraRemotes = [], registerOptions = {}) {
339
+ const remotesIdentity = buildRemotesIdentity(extraRemotes);
340
+ const cacheKey = `${scopeName}::${url}::${remotesIdentity}::${registerOptions.force ? 'force' : 'normal'}`;
328
341
  const cachedMfs = mfInstanceCache.get(cacheKey);
329
342
  if (cachedMfs) return {
330
343
  scopeName,
@@ -332,41 +345,32 @@ async function tryLoadRemote(scopeName, url, retries, delay, sharedConfig, plugi
332
345
  };
333
346
  const loadingMfs = mfInstanceLoadingCache.get(cacheKey);
334
347
  if (loadingMfs) return loadingMfs;
335
- const createProcess = (async ()=>{
336
- let lastError;
337
- for(let i = 0; i < retries; i++)try {
338
- const mf = (0, runtime_namespaceObject.createInstance)({
339
- name: 'host',
340
- remotes: [
341
- {
342
- name: scopeName,
343
- entry: url
344
- }
345
- ],
346
- shared: sharedConfig,
347
- plugins: [
348
- ...plugins,
349
- fallbackPlugin()
350
- ]
351
- });
352
- const result = {
353
- scopeName,
354
- mf
355
- };
356
- mfInstanceCache.set(cacheKey, mf);
357
- return result;
358
- } catch (e) {
359
- lastError = e;
360
- console.warn(`[MF] URL ${url} 加载失败,第 ${i + 1} 次重试...`);
361
- if (i < retries - 1) await new Promise((res)=>setTimeout(res, delay));
362
- }
363
- throw new Error(`[MF] URL ${url} 经过 ${retries} 次重试仍加载失败。`, {
364
- cause: lastError
348
+ const loadPromise = Promise.resolve().then(()=>{
349
+ const mf = (0, runtime_namespaceObject.createInstance)({
350
+ name: 'host',
351
+ remotes: [
352
+ {
353
+ name: scopeName,
354
+ entry: url
355
+ }
356
+ ],
357
+ shared: sharedConfig,
358
+ plugins: [
359
+ ...plugins,
360
+ fallbackPlugin()
361
+ ]
365
362
  });
366
- })();
367
- mfInstanceLoadingCache.set(cacheKey, createProcess);
363
+ if (extraRemotes.length > 0) mf.registerRemotes(extraRemotes, registerOptions);
364
+ const result = {
365
+ scopeName,
366
+ mf
367
+ };
368
+ mfInstanceCache.set(cacheKey, mf);
369
+ return result;
370
+ });
371
+ mfInstanceLoadingCache.set(cacheKey, loadPromise);
368
372
  try {
369
- return await createProcess;
373
+ return await loadPromise;
370
374
  } finally{
371
375
  mfInstanceLoadingCache.delete(cacheKey);
372
376
  }
@@ -488,15 +492,55 @@ function buildFinalUrls(pkg, version, localFallback) {
488
492
  if (localFallback) urls.push(localFallback);
489
493
  return urls;
490
494
  }
491
- async function loadRemoteMultiVersion(options, plugins) {
495
+ function dedupeRemotes(remotes) {
496
+ const map = new Map();
497
+ for (const remote of remotes){
498
+ if (!remote?.name) continue;
499
+ const key = [
500
+ remote.name,
501
+ 'entry' in remote ? remote.entry || '' : '',
502
+ 'version' in remote ? remote.version || '' : '',
503
+ remote.alias || ''
504
+ ].join('::');
505
+ if (!map.has(key)) map.set(key, remote);
506
+ }
507
+ return Array.from(map.values());
508
+ }
509
+ async function resolveRegisteredRemotes(context, baseRemotes, remoteSourcePlugins) {
510
+ const remoteList = [
511
+ ...baseRemotes
512
+ ];
513
+ for (const plugin of remoteSourcePlugins)if (plugin.registerRemotes) try {
514
+ const pluginRemotes = await plugin.registerRemotes(context);
515
+ if (pluginRemotes?.length) remoteList.push(...pluginRemotes);
516
+ } catch (error) {
517
+ throw new Error(`[MF] remote 来源插件 ${plugin.name} 执行失败: ${error.message}`);
518
+ }
519
+ return dedupeRemotes(remoteList).filter((remote)=>!(remote.name === context.scopeName && 'entry' in remote && remote.entry === context.currentEntry));
520
+ }
521
+ function createRemoteSourcePlugin(name, remotes) {
522
+ return {
523
+ name,
524
+ registerRemotes: ()=>remotes
525
+ };
526
+ }
527
+ async function loadRemoteMultiVersion(options, plugins = [], extraOptions = {}) {
492
528
  const { name, pkg, version = 'latest', retries = 3, delay = 1000, localFallback, cacheTTL = 86400000, revalidate = true, shared: customShared } = options;
529
+ const { remoteSourcePlugins = [], baseRemotes = [], registerOptions = {} } = extraOptions;
493
530
  const finalVersion = await resolveFinalVersion(pkg, version, cacheTTL, revalidate);
494
531
  const scopeName = `${name}`;
495
532
  const urls = buildFinalUrls(pkg, finalVersion, localFallback);
496
533
  const finalSharedConfig = getFinalSharedConfig(customShared);
497
- console.log(finalSharedConfig, 'finalSharedConfig');
498
534
  for (const url of urls)try {
499
- return await tryLoadRemote(scopeName, url, retries, delay, finalSharedConfig, plugins);
535
+ const registeredRemotes = await resolveRegisteredRemotes({
536
+ options,
537
+ scopeName,
538
+ pkg,
539
+ finalVersion,
540
+ currentEntry: url,
541
+ allEntries: urls
542
+ }, baseRemotes, remoteSourcePlugins);
543
+ return await tryLoadRemote(scopeName, url, retries, delay, finalSharedConfig, plugins, registeredRemotes, registerOptions);
500
544
  } catch (e) {
501
545
  console.warn(`[MF] 切换 CDN 路径:${url} 失败,尝试下一个...`, e);
502
546
  }
@@ -897,6 +941,7 @@ exports.checkVersionCompatibility = __webpack_exports__.checkVersionCompatibilit
897
941
  exports.clearPreloadCache = __webpack_exports__.clearPreloadCache;
898
942
  exports.compareVersions = __webpack_exports__.compareVersions;
899
943
  exports.createEventBus = __webpack_exports__.createEventBus;
944
+ exports.createRemoteSourcePlugin = __webpack_exports__.createRemoteSourcePlugin;
900
945
  exports.eventBus = __webpack_exports__.eventBus;
901
946
  exports.extractMajorVersion = __webpack_exports__.extractMajorVersion;
902
947
  exports.fallbackPlugin = __webpack_exports__.fallbackPlugin;
@@ -938,6 +983,7 @@ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
938
983
  "clearPreloadCache",
939
984
  "compareVersions",
940
985
  "createEventBus",
986
+ "createRemoteSourcePlugin",
941
987
  "eventBus",
942
988
  "extractMajorVersion",
943
989
  "fallbackPlugin",
package/dist/main.js CHANGED
@@ -256,8 +256,20 @@ function buildCdnUrls(pkg, version) {
256
256
  }
257
257
  const mfInstanceCache = new Map();
258
258
  const mfInstanceLoadingCache = new Map();
259
- async function tryLoadRemote(scopeName, url, retries, delay, sharedConfig, plugins) {
260
- const cacheKey = `${scopeName}::${url}`;
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'}`;
261
273
  const cachedMfs = mfInstanceCache.get(cacheKey);
262
274
  if (cachedMfs) return {
263
275
  scopeName,
@@ -265,41 +277,32 @@ async function tryLoadRemote(scopeName, url, retries, delay, sharedConfig, plugi
265
277
  };
266
278
  const loadingMfs = mfInstanceLoadingCache.get(cacheKey);
267
279
  if (loadingMfs) return loadingMfs;
268
- const createProcess = (async ()=>{
269
- let lastError;
270
- for(let i = 0; i < retries; i++)try {
271
- const mf = createInstance({
272
- name: 'host',
273
- remotes: [
274
- {
275
- name: scopeName,
276
- entry: url
277
- }
278
- ],
279
- shared: sharedConfig,
280
- plugins: [
281
- ...plugins,
282
- fallbackPlugin()
283
- ]
284
- });
285
- const result = {
286
- scopeName,
287
- mf
288
- };
289
- mfInstanceCache.set(cacheKey, mf);
290
- return result;
291
- } catch (e) {
292
- lastError = e;
293
- console.warn(`[MF] URL ${url} 加载失败,第 ${i + 1} 次重试...`);
294
- if (i < retries - 1) await new Promise((res)=>setTimeout(res, delay));
295
- }
296
- throw new Error(`[MF] URL ${url} 经过 ${retries} 次重试仍加载失败。`, {
297
- cause: lastError
280
+ const loadPromise = Promise.resolve().then(()=>{
281
+ const mf = createInstance({
282
+ name: 'host',
283
+ remotes: [
284
+ {
285
+ name: scopeName,
286
+ entry: url
287
+ }
288
+ ],
289
+ shared: sharedConfig,
290
+ plugins: [
291
+ ...plugins,
292
+ fallbackPlugin()
293
+ ]
298
294
  });
299
- })();
300
- mfInstanceLoadingCache.set(cacheKey, createProcess);
295
+ if (extraRemotes.length > 0) mf.registerRemotes(extraRemotes, registerOptions);
296
+ const result = {
297
+ scopeName,
298
+ mf
299
+ };
300
+ mfInstanceCache.set(cacheKey, mf);
301
+ return result;
302
+ });
303
+ mfInstanceLoadingCache.set(cacheKey, loadPromise);
301
304
  try {
302
- return await createProcess;
305
+ return await loadPromise;
303
306
  } finally{
304
307
  mfInstanceLoadingCache.delete(cacheKey);
305
308
  }
@@ -421,15 +424,55 @@ function buildFinalUrls(pkg, version, localFallback) {
421
424
  if (localFallback) urls.push(localFallback);
422
425
  return urls;
423
426
  }
424
- 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 = {}) {
425
460
  const { name, pkg, version = 'latest', retries = 3, delay = 1000, localFallback, cacheTTL = 86400000, revalidate = true, shared: customShared } = options;
461
+ const { remoteSourcePlugins = [], baseRemotes = [], registerOptions = {} } = extraOptions;
426
462
  const finalVersion = await resolveFinalVersion(pkg, version, cacheTTL, revalidate);
427
463
  const scopeName = `${name}`;
428
464
  const urls = buildFinalUrls(pkg, finalVersion, localFallback);
429
465
  const finalSharedConfig = getFinalSharedConfig(customShared);
430
- console.log(finalSharedConfig, 'finalSharedConfig');
431
466
  for (const url of urls)try {
432
- 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);
433
476
  } catch (e) {
434
477
  console.warn(`[MF] 切换 CDN 路径:${url} 失败,尝试下一个...`, e);
435
478
  }
@@ -821,4 +864,4 @@ const eventBus = EventBusClass.create();
821
864
  function createEventBus() {
822
865
  return EventBusClass.create();
823
866
  }
824
- export { 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, loadReactVersion, loadRemoteMultiVersion, parseVersion, preloadRemote, preloadRemoteList, registerLoadedModule, registerRemoteInstance, resolveFinalVersion, satisfiesVersion, setVersionCache, sortVersions, tryLoadRemote, unloadAll, unloadRemote };
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 };
@@ -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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-reload-utils",
3
- "version": "0.0.16",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "Utilities for remote reload in Module Federation & React Component",
6
6
  "exports": {
@@ -57,23 +57,6 @@
57
57
  "optional": true
58
58
  }
59
59
  },
60
- "scripts": {
61
- "build": "rslib build",
62
- "dev": "rslib build --watch",
63
- "prepublishOnly": "npm run build",
64
- "format": "biome format --write",
65
- "check": "biome check --write",
66
- "test": "vitest run",
67
- "test:watch": "vitest",
68
- "lint": "biome format ./src --write",
69
- "release:prepare": "node scripts/generateReleasePr.mjs",
70
- "release:finalize": "node scripts/finalizeRelease.mjs",
71
- "release:patch": "pnpm release:prepare -t patch",
72
- "release:minor": "pnpm release:prepare -t minor",
73
- "release:major": "pnpm release:prepare -t major",
74
- "publish:dry": "npm publish --dry-run",
75
- "publish:live": "npm publish"
76
- },
77
60
  "devDependencies": {
78
61
  "@biomejs/biome": "2.0.6",
79
62
  "@rslib/core": "0.17.0",
@@ -90,7 +73,26 @@
90
73
  "typescript": "^5.9.2",
91
74
  "vitest": "^2.1.8"
92
75
  },
76
+ "publishConfig": {
77
+ "access": "public"
78
+ },
93
79
  "dependencies": {
94
80
  "@module-federation/enhanced": "0.18.3"
81
+ },
82
+ "scripts": {
83
+ "build": "rslib build",
84
+ "dev": "rslib build --watch",
85
+ "format": "biome format --write",
86
+ "check": "biome check --write",
87
+ "test": "vitest run",
88
+ "test:watch": "vitest",
89
+ "lint": "biome format ./src --write",
90
+ "release:prepare": "node scripts/generateReleasePr.mjs",
91
+ "release:finalize": "node scripts/finalizeRelease.mjs",
92
+ "release:patch": "pnpm release:prepare -t patch",
93
+ "release:minor": "pnpm release:prepare -t minor",
94
+ "release:major": "pnpm release:prepare -t major",
95
+ "publish:dry": "npm publish --dry-run",
96
+ "publish:live": "npm publish"
95
97
  }
96
- }
98
+ }