remote-reload-utils 0.0.8 → 0.0.10

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/loadRemote.md CHANGED
@@ -1,43 +1,1371 @@
1
- ```ts
2
- import './App.css';
1
+ # loadRemote 使用文档
2
+
3
+ `remote-reload-utils` 是一个用于运行时动态加载远程 React 组件的工具库。本文档提供详细的使用指南和 API 参考。
4
+
5
+ ## 目录
6
+
7
+ - [简介](#简介)
8
+ - [核心功能](#核心功能)
9
+ - [安装](#安装)
10
+ - [快速开始](#快速开始)
11
+ - [API 参考](#api-参考)
12
+ - [loadRemoteMultiVersion](#loadremotemultiversion)
13
+ - [工具函数](#工具函数)
14
+ - [loadReactVersion](#loadreactversion)
15
+ - [高级功能](#高级功能)
16
+ - [预加载远程模块](#预加载远程模块)
17
+ - [卸载远程模块](#卸载远程模块)
18
+ - [健康检查](#健康检查)
19
+ - [React Hooks](#react-hooks)
20
+ - [跨模块共享状态](#跨模块共享状态)
21
+ - [事件总线](#事件总线)
22
+ - [版本兼容性检查](#版本兼容性检查)
23
+ - [React 组件适配器](#react-组件适配器)
24
+ - [配置选项](#配置选项)
25
+ - [使用示例](#使用示例)
26
+ - [最佳实践](#最佳实践)
27
+ - [故障排查](#故障排查)
28
+ - [类型定义](#类型定义)
29
+
30
+ ## 简介
31
+
32
+ 该工具库基于 `@module-federation/enhanced/runtime`,提供了以下增强功能:
33
+
34
+ - **多版本支持**: 可以同时加载同一个包的不同版本
35
+ - **CDN 故障转移**: 自动在多个 CDN 之间切换
36
+ - **智能缓存**: 本地缓存版本信息,减少网络请求
37
+ - **重试机制**: 加载失败时自动重试
38
+ - **TypeScript 支持**: 完整的类型定义
39
+
40
+ ## 核心功能
41
+
42
+ ### 1. 多版本共存
43
+
44
+ ```typescript
45
+ // 同时加载 1.0.0 和 2.0.0 版本
46
+ const v1 = await loadRemoteMultiVersion({
47
+ name: 'component_v1',
48
+ pkg: 'my-ui-lib',
49
+ version: '1.0.0',
50
+ });
51
+
52
+ const v2 = await loadRemoteMultiVersion({
53
+ name: 'component_v2',
54
+ pkg: 'my-ui-lib',
55
+ version: '2.0.0',
56
+ });
57
+
58
+ // 两个版本可以同时使用
59
+ ```
60
+
61
+ ### 2. CDN 故障转移
62
+
63
+ ```typescript
64
+ // 自动尝试多个 CDN
65
+ const { mf } = await loadRemoteMultiVersion({
66
+ name: 'my_app',
67
+ pkg: 'my-component',
68
+ version: '1.0.0',
69
+ // 内置顺序:
70
+ // 1. cdn.jsdelivr.net
71
+ // 2. unpkg.com
72
+ // 3. localFallback (如果提供)
73
+ });
74
+ ```
75
+
76
+ ### 3. 智能缓存
77
+
78
+ ```typescript
79
+ // 使用 latest 时,优先从缓存读取
80
+ const { mf } = await loadRemoteMultiVersion({
81
+ name: 'my_app',
82
+ pkg: 'my-component',
83
+ version: 'latest',
84
+ cacheTTL: 24 * 60 * 60 * 1000, // 24 小时缓存
85
+ revalidate: true, // 后台异步验证最新版本
86
+ });
87
+ ```
88
+
89
+ ## 安装
90
+
91
+ ```bash
92
+ pnpm add remote-reload-utils
93
+ ```
94
+
95
+ 或从 workspace 安装:
96
+
97
+ ```bash
98
+ pnpm add remote-reload-utils --workspace
99
+ ```
100
+
101
+ ## 快速开始
102
+
103
+ ### 1. 基础使用
104
+
105
+ ```typescript
3
106
  import { loadRemoteMultiVersion } from 'remote-reload-utils';
4
107
  import { useEffect, useState } from 'react';
5
108
 
6
- const App = () => {
7
- const [comp, setComp] = useState(null);
109
+ function App() {
110
+ const [Button, setButton] = useState(null);
111
+
8
112
  useEffect(() => {
9
- async function init() {
113
+ async function loadComponent() {
114
+ // 加载远程模块
10
115
  const { scopeName, mf } = await loadRemoteMultiVersion({
11
- name: 'react_mf_lib',
12
- pkg: 'test-mf-unpkg',
13
- version: '1.0.5',
14
- // version: 'latest',
116
+ name: 'ui_lib',
117
+ pkg: 'my-ui-components',
118
+ version: '1.0.0',
15
119
  });
16
- if (!mf) {
17
- return;
18
- }
19
- if (mf) {
20
- console.log(mf);
21
- const mod = await mf.loadRemote(`${scopeName}/Button`);
22
- console.log(mod.default); // 这里就是远程组件
23
- setComp(mod.default);
24
- }
25
- // 用 mf 实例加载暴露的模块
26
- // const mod = await mf.loadRemote(`${scopeName}/Button`);
27
- // const mod = await mf.loadRemote(`react_mf_lib/Button`);
28
- // console.log(mod.default);
120
+
121
+ // 加载具体的组件
122
+ const mod = await mf.loadRemote(`${scopeName}/Button`);
123
+ setButton(mod.default);
29
124
  }
30
- init();
125
+
126
+ loadComponent();
31
127
  }, []);
32
128
 
33
129
  return (
34
- <div className="content">
35
- <h1>Rsbuild with React</h1>
36
- {comp}
37
- <p>Start building amazing things with Rsbuild.</p>
130
+ <div>
131
+ {Button && <Button>Click me</Button>}
38
132
  </div>
39
133
  );
134
+ }
135
+ ```
136
+
137
+ ### 2. 加载多个组件
138
+
139
+ ```typescript
140
+ async function loadMultipleComponents() {
141
+ const { scopeName, mf } = await loadRemoteMultiVersion({
142
+ name: 'ui_lib',
143
+ pkg: 'my-ui-components',
144
+ version: '1.0.0',
145
+ });
146
+
147
+ // 并行加载多个组件
148
+ const [Button, Card, Modal] = await Promise.all([
149
+ mf.loadRemote(`${scopeName}/Button`),
150
+ mf.loadRemote(`${scopeName}/Card`),
151
+ mf.loadRemote(`${scopeName}/Modal`),
152
+ ]);
153
+
154
+ return {
155
+ Button: Button.default,
156
+ Card: Card.default,
157
+ Modal: Modal.default,
158
+ };
159
+ }
160
+ ```
161
+
162
+ ## API 参考
163
+
164
+ ### loadRemoteMultiVersion
165
+
166
+ 核心函数,加载远程模块的多版本实例。
167
+
168
+ ```typescript
169
+ function loadRemoteMultiVersion(
170
+ options: LoadRemoteOptions,
171
+ plugins: ModuleFederationRuntimePlugin[]
172
+ ): Promise<LoadResult>
173
+ ```
174
+
175
+ #### 参数
176
+
177
+ **options**: `LoadRemoteOptions`
178
+
179
+ | 属性 | 类型 | 必填 | 默认值 | 描述 |
180
+ |------|------|------|--------|------|
181
+ | `name` | `string` | ✅ | - | Module Federation 的名称 |
182
+ | `pkg` | `string` | ✅ | - | npm 包名 |
183
+ | `version` | `string` | ❌ | `'latest'` | 版本号或 `'latest'` |
184
+ | `retries` | `number` | ❌ | `3` | 每个 CDN 的重试次数 |
185
+ | `delay` | `number` | ❌ | `1000` | 重试间隔(毫秒) |
186
+ | `localFallback` | `string` | ❌ | - | 本地兜底 URL |
187
+ | `cacheTTL` | `number` | ❌ | `86400000` | 缓存有效期(毫秒) |
188
+ | `revalidate` | `boolean` | ❌ | `true` | 是否异步验证最新版本 |
189
+ | `shared` | `Record<string, any>` | ❌ | - | 自定义共享模块配置 |
190
+
191
+ **plugins**: `ModuleFederationRuntimePlugin[]`
192
+
193
+ Module Federation 运行时插件数组,默认会添加 `fallbackPlugin()`。
194
+
195
+ #### 返回值
196
+
197
+ ```typescript
198
+ interface LoadResult {
199
+ scopeName: string; // 远程模块作用域名称
200
+ mf: ReturnType<typeof createInstance>; // Module Federation 实例
201
+ }
202
+ ```
203
+
204
+ #### 使用 MF 实例
205
+
206
+ ```typescript
207
+ const { scopeName, mf } = await loadRemoteMultiVersion(options);
208
+
209
+ // 加载暴露的模块
210
+ const module = await mf.loadRemote(`${scopeName}/Button`);
211
+
212
+ // module.default 就是导出的组件或函数
213
+ const Button = module.default;
214
+
215
+ // 也可以直接使用完整的模块路径
216
+ const Button2 = await mf.loadRemote('ui_lib/Button');
217
+ ```
218
+
219
+ ### 工具函数
220
+
221
+ 以下工具函数从 `loadRemoteUtils` 模块导出,可用于更细粒度的控制或自定义加载逻辑:
222
+
223
+ #### fetchLatestVersion
224
+
225
+ 从 npm registry 获取包的最新版本。
226
+
227
+ ```typescript
228
+ function fetchLatestVersion(pkg: string): Promise<string>
229
+ ```
230
+
231
+ **示例**:
232
+ ```typescript
233
+ const latest = await fetchLatestVersion('react');
234
+ console.log(`React 最新版本:${latest}`);
235
+ ```
236
+
237
+ #### getVersionCache / setVersionCache
238
+
239
+ 读取和写入版本缓存。
240
+
241
+ ```typescript
242
+ function getVersionCache(): VersionCache
243
+ function setVersionCache(pkg: string, version: string): void
244
+ ```
245
+
246
+ **示例**:
247
+ ```typescript
248
+ // 读取缓存
249
+ const cache = getVersionCache();
250
+ console.log(cache['my-pkg']);
251
+
252
+ // 写入缓存
253
+ setVersionCache('my-pkg', '1.0.0');
254
+ ```
255
+
256
+ #### buildCdnUrls
257
+
258
+ 根据包名和版本构建 CDN 地址列表。
259
+
260
+ ```typescript
261
+ function buildCdnUrls(pkg: string, version: string): string[]
262
+ ```
263
+
264
+ **示例**:
265
+ ```typescript
266
+ const urls = buildCdnUrls('my-lib', '1.0.0');
267
+ // [
268
+ // 'https://cdn.jsdelivr.net/npm/my-lib@1.0.0/dist/remoteEntry.js',
269
+ // 'https://unpkg.com/my-lib@1.0.0/dist/remoteEntry.js'
270
+ // ]
271
+ ```
272
+
273
+ #### tryLoadRemote
274
+
275
+ 尝试加载单个远程模块 URL,包含重试逻辑。
276
+
277
+ ```typescript
278
+ function tryLoadRemote(
279
+ scopeName: string,
280
+ url: string,
281
+ retries: number,
282
+ delay: number,
283
+ sharedConfig: Record<string, any>,
284
+ plugins: ModuleFederationRuntimePlugin[],
285
+ ): Promise<LoadResult>
286
+ ```
287
+
288
+ #### getFinalSharedConfig
289
+
290
+ 合并默认共享配置和自定义配置。
291
+
292
+ ```typescript
293
+ function getFinalSharedConfig(customShared?: Record<string, any>): Record<string, any>
294
+ ```
295
+
296
+ **示例**:
297
+ ```typescript
298
+ // 使用默认配置(包含 react 和 react-dom 的 singleton 配置)
299
+ const config = getFinalSharedConfig();
300
+
301
+ // 合并自定义配置
302
+ const customConfig = getFinalSharedConfig({
303
+ lodash: {
304
+ shareConfig: {
305
+ singleton: false,
306
+ requiredVersion: '^4.17.0',
307
+ },
308
+ },
309
+ });
310
+ ```
311
+
312
+ #### resolveFinalVersion
313
+
314
+ 解析最终版本号(处理 'latest' 情况,使用缓存)。
315
+
316
+ ```typescript
317
+ function resolveFinalVersion(
318
+ pkg: string,
319
+ version: string,
320
+ cacheTTL: number,
321
+ revalidate: boolean,
322
+ ): Promise<string>
323
+ ```
324
+
325
+ **示例**:
326
+ ```typescript
327
+ // 解析版本号,如果 version 为 'latest' 则使用缓存或请求最新
328
+ const finalVersion = await resolveFinalVersion('my-pkg', 'latest', 24 * 60 * 60 * 1000, true);
329
+ ```
330
+
331
+ #### buildFinalUrls
332
+
333
+ 构建最终的 URL 列表(包含本地 fallback)。
334
+
335
+ ```typescript
336
+ function buildFinalUrls(
337
+ pkg: string,
338
+ version: string,
339
+ localFallback?: string,
340
+ ): string[]
341
+ ```
342
+
343
+ **示例**:
344
+ ```typescript
345
+ const urls = buildFinalUrls('my-lib', '1.0.0', 'http://localhost:3001/remoteEntry.js');
346
+ // [
347
+ // 'https://cdn.jsdelivr.net/npm/my-lib@1.0.0/dist/remoteEntry.js',
348
+ // 'https://unpkg.com/my-lib@1.0.0/dist/remoteEntry.js',
349
+ // 'http://localhost:3001/remoteEntry.js'
350
+ // ]
351
+ ```
352
+
353
+ ### loadReactVersion
354
+
355
+ 加载特定版本的 React 和 ReactDOM,用于多版本 React 场景。
356
+
357
+ ```typescript
358
+ function loadReactVersion(version: '17' | '18' | '19'): Promise<{
359
+ React: any;
360
+ ReactDOM: any;
361
+ }>
362
+ ```
363
+
364
+ #### 参数
365
+
366
+ - `version`: React 版本号,可选 `'17'`、`'18'` 或 `'19'`
367
+
368
+ #### 返回值
369
+
370
+ 返回包含 React 和 ReactDOM 的对象。
371
+
372
+ #### 示例
373
+
374
+ ```typescript
375
+ const { React, ReactDOM } = await loadReactVersion('18');
376
+
377
+ // 使用特定版本的 React
378
+ const App = () => {
379
+ const [count, setCount] = React.useState(0);
380
+ return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
381
+ };
382
+ ```
383
+
384
+ ## 配置选项
385
+
386
+ ### LoadRemoteOptions 详细说明
387
+
388
+ #### name
389
+
390
+ Module Federation 的名称,用于标识远程模块。
391
+
392
+ ```typescript
393
+ {
394
+ name: 'my_ui_lib', // 在加载组件时使用
395
+ pkg: 'my-ui-components',
396
+ }
397
+ ```
398
+
399
+ #### pkg
400
+
401
+ npm 包名,用于从 CDN 加载。
402
+
403
+ ```typescript
404
+ {
405
+ name: 'my_ui_lib',
406
+ pkg: '@company/ui-components', // 支持作用域包
407
+ }
408
+ ```
409
+
410
+ #### version
411
+
412
+ 指定要加载的版本号。
413
+
414
+ ```typescript
415
+ // 固定版本
416
+ { version: '1.0.0' }
417
+
418
+ // 最新版本(使用缓存)
419
+ { version: 'latest' }
420
+
421
+ // 版本范围(需要预先知道具体版本)
422
+ { version: '2.1.0' }
423
+ ```
424
+
425
+ #### retries 和 delay
426
+
427
+ 控制重试行为。
428
+
429
+ ```typescript
430
+ {
431
+ retries: 5, // 每个 CDN 重试 5 次
432
+ delay: 2000, // 每次重试间隔 2 秒
433
+ }
434
+ ```
435
+
436
+ #### localFallback
437
+
438
+ 本地开发时的兜底地址。
439
+
440
+ ```typescript
441
+ {
442
+ localFallback: 'http://localhost:3001/remoteEntry.js',
443
+ }
444
+ ```
445
+
446
+ #### cacheTTL
447
+
448
+ 缓存有效期。
449
+
450
+ ```typescript
451
+ {
452
+ cacheTTL: 12 * 60 * 60 * 1000, // 12 小时
453
+ }
454
+ ```
455
+
456
+ #### revalidate
457
+
458
+ 是否异步验证最新版本。
459
+
460
+ ```typescript
461
+ {
462
+ version: 'latest',
463
+ revalidate: true, // 后台检查,不阻塞加载
464
+ }
465
+ ```
466
+
467
+ #### shared
468
+
469
+ 自定义共享模块配置。
470
+
471
+ ```typescript
472
+ {
473
+ shared: {
474
+ react: {
475
+ shareConfig: {
476
+ singleton: true,
477
+ eager: true,
478
+ requiredVersion: '^18.0.0',
479
+ },
480
+ },
481
+ lodash: {
482
+ shareConfig: {
483
+ singleton: false,
484
+ },
485
+ },
486
+ },
487
+ }
488
+ ```
489
+
490
+ ## 使用示例
491
+
492
+ ### 示例 1: React Hook 封装
493
+
494
+ ```typescript
495
+ import { loadRemoteMultiVersion } from 'remote-reload-utils';
496
+
497
+ function useRemoteComponent(
498
+ pkg: string,
499
+ version: string,
500
+ componentName: string
501
+ ) {
502
+ const [Component, setComponent] = useState(null);
503
+ const [loading, setLoading] = useState(true);
504
+ const [error, setError] = useState(null);
505
+
506
+ useEffect(() => {
507
+ async function load() {
508
+ try {
509
+ setLoading(true);
510
+ const { scopeName, mf } = await loadRemoteMultiVersion({
511
+ name: pkg.replace(/[^a-z0-9]/gi, '_'),
512
+ pkg,
513
+ version,
514
+ });
515
+
516
+ const mod = await mf.loadRemote(`${scopeName}/${componentName}`);
517
+ setComponent(mod.default);
518
+ } catch (err) {
519
+ setError(err);
520
+ } finally {
521
+ setLoading(false);
522
+ }
523
+ }
524
+
525
+ load();
526
+ }, [pkg, version, componentName]);
527
+
528
+ return { Component, loading, error };
529
+ }
530
+
531
+ // 使用
532
+ function App() {
533
+ const { Component: Button, loading, error } = useRemoteComponent(
534
+ 'my-ui-components',
535
+ '1.0.0',
536
+ 'Button'
537
+ );
538
+
539
+ if (loading) return <div>Loading...</div>;
540
+ if (error) return <div>Error: {error.message}</div>;
541
+
542
+ return <Button>Remote Button</Button>;
543
+ }
544
+ ```
545
+
546
+ ### 示例 2: 动态加载配置
547
+
548
+ ```typescript
549
+ interface RemoteComponentConfig {
550
+ pkg: string;
551
+ version: string;
552
+ component: string;
553
+ }
554
+
555
+ const configs: RemoteComponentConfig[] = [
556
+ { pkg: 'my-ui-components', version: '1.0.0', component: 'Button' },
557
+ { pkg: 'my-ui-components', version: '1.0.0', component: 'Card' },
558
+ { pkg: 'another-lib', version: '2.1.0', component: 'Modal' },
559
+ ];
560
+
561
+ function loadComponentsFromConfig(configs: RemoteComponentConfig[]) {
562
+ return Promise.all(
563
+ configs.map(async (config) => {
564
+ const { scopeName, mf } = await loadRemoteMultiVersion({
565
+ name: config.pkg.replace(/[^a-z0-9]/gi, '_'),
566
+ pkg: config.pkg,
567
+ version: config.version,
568
+ });
569
+
570
+ const mod = await mf.loadRemote(`${scopeName}/${config.component}`);
571
+ return {
572
+ name: config.component,
573
+ Component: mod.default,
574
+ };
575
+ })
576
+ );
577
+ }
578
+
579
+ // 使用
580
+ const components = await loadComponentsFromConfig(configs);
581
+ const Button = components.find(c => c.name === 'Button')?.Component;
582
+ ```
583
+
584
+ ### 示例 3: 错误处理和降级
585
+
586
+ ```typescript
587
+ async function loadRemoteWithFallback(
588
+ pkg: string,
589
+ version: string,
590
+ componentName: string,
591
+ FallbackComponent: React.ComponentType
592
+ ) {
593
+ try {
594
+ const { scopeName, mf } = await loadRemoteMultiVersion({
595
+ name: pkg.replace(/[^a-z0-9]/gi, '_'),
596
+ pkg,
597
+ version,
598
+ retries: 2,
599
+ delay: 500,
600
+ });
601
+
602
+ const mod = await mf.loadRemote(`${scopeName}/${componentName}`);
603
+ return mod.default;
604
+ } catch (error) {
605
+ console.warn(`Failed to load ${componentName}, using fallback:`, error);
606
+ return FallbackComponent;
607
+ }
608
+ }
609
+
610
+ // 使用
611
+ const Button = await loadRemoteWithFallback(
612
+ 'my-ui-components',
613
+ '1.0.0',
614
+ 'Button',
615
+ () => <button>Fallback Button</button>
616
+ );
617
+ ```
618
+
619
+ ### 示例 4: 预加载
620
+
621
+ ```typescript
622
+ // 在应用启动时预加载组件
623
+ const preloadComponents = async () => {
624
+ const components = [
625
+ { pkg: 'my-ui-components', version: '1.0.0', name: 'Button' },
626
+ { pkg: 'my-ui-components', version: '1.0.0', name: 'Card' },
627
+ ];
628
+
629
+ for (const comp of components) {
630
+ loadRemoteMultiVersion({
631
+ name: comp.pkg.replace(/[^a-z0-9]/gi, '_'),
632
+ pkg: comp.pkg,
633
+ version: comp.version,
634
+ }).catch(console.warn); // 预加载失败不影响启动
635
+ }
40
636
  };
41
637
 
42
- export default App;
638
+ // App 组件中调用
639
+ function App() {
640
+ useEffect(() => {
641
+ preloadComponents();
642
+ }, []);
643
+
644
+ return <div>...</div>;
645
+ }
43
646
  ```
647
+
648
+ ### 示例 5: 多版本对比
649
+
650
+ ```typescript
651
+ function VersionComparison() {
652
+ const [v1Button, setV1Button] = useState(null);
653
+ const [v2Button, setV2Button] = useState(null);
654
+
655
+ useEffect(() => {
656
+ async function load() {
657
+ const [{ mf: mf1 }, { mf: mf2 }] = await Promise.all([
658
+ loadRemoteMultiVersion({
659
+ name: 'ui_v1',
660
+ pkg: 'my-ui-components',
661
+ version: '1.0.0',
662
+ }),
663
+ loadRemoteMultiVersion({
664
+ name: 'ui_v2',
665
+ pkg: 'my-ui-components',
666
+ version: '2.0.0',
667
+ }),
668
+ ]);
669
+
670
+ const [mod1, mod2] = await Promise.all([
671
+ mf1.loadRemote('ui_v1/Button'),
672
+ mf2.loadRemote('ui_v2/Button'),
673
+ ]);
674
+
675
+ setV1Button(mod1.default);
676
+ setV2Button(mod2.default);
677
+ }
678
+
679
+ load();
680
+ }, []);
681
+
682
+ return (
683
+ <div>
684
+ <h3>Version 1.0.0</h3>
685
+ {v1Button && <v1Button />}
686
+
687
+ <h3>Version 2.0.0</h3>
688
+ {v2Button && <v2Button />}
689
+ </div>
690
+ );
691
+ }
692
+ ```
693
+
694
+ ## 最佳实践
695
+
696
+ ### 1. 版本管理
697
+
698
+ ```typescript
699
+ // ✅ 好的做法:生产环境使用固定版本
700
+ const { mf } = await loadRemoteMultiVersion({
701
+ name: 'ui_lib',
702
+ pkg: 'my-ui-components',
703
+ version: '1.2.3', // 固定版本
704
+ });
705
+
706
+ // ⚠️ 谨慎使用:latest 可能导致不可预期的变化
707
+ const { mf } = await loadRemoteMultiVersion({
708
+ name: 'ui_lib',
709
+ pkg: 'my-ui-components',
710
+ version: 'latest',
711
+ });
712
+ ```
713
+
714
+ ### 2. 错误处理
715
+
716
+ ```typescript
717
+ // ✅ 好的做法:提供加载状态和错误处理
718
+ function RemoteButton() {
719
+ const [state, setState] = useState({
720
+ Component: null,
721
+ loading: true,
722
+ error: null,
723
+ });
724
+
725
+ useEffect(() => {
726
+ loadRemoteComponent()
727
+ .then(Component => setState({ Component, loading: false, error: null }))
728
+ .catch(error => setState({ Component: null, loading: false, error }));
729
+ }, []);
730
+
731
+ if (state.loading) return <Spinner />;
732
+ if (state.error) return <ErrorFallback error={state.error} />;
733
+ return <state.Component />;
734
+ }
735
+ ```
736
+
737
+ ### 3. 缓存策略
738
+
739
+ ```typescript
740
+ // ✅ 好的做法:合理设置缓存时间
741
+ const { mf } = await loadRemoteMultiVersion({
742
+ name: 'ui_lib',
743
+ pkg: 'my-ui-components',
744
+ version: 'latest',
745
+ cacheTTL: 24 * 60 * 60 * 1000, // 24 小时
746
+ revalidate: true, // 后台验证新版本
747
+ });
748
+
749
+ // ⚠️ 避免设置过短的缓存
750
+ cacheTTL: 60000, // 1 分钟 - 太短,增加请求次数
751
+ ```
752
+
753
+ ### 4. 性能优化
754
+
755
+ ```typescript
756
+ // ✅ 好的做法:并行加载多个组件
757
+ const [Button, Card] = await Promise.all([
758
+ mf.loadRemote('ui_lib/Button'),
759
+ mf.loadRemote('ui_lib/Card'),
760
+ ]);
761
+
762
+ // ❌ 避免:顺序加载
763
+ const Button = await mf.loadRemote('ui_lib/Button');
764
+ const Card = await mf.loadRemote('ui_lib/Card'); // 等待 Button 加载完才开始
765
+ ```
766
+
767
+ ### 5. 本地开发
768
+
769
+ ```typescript
770
+ // ✅ 好的做法:开发环境使用本地兜底
771
+ const isDev = process.env.NODE_ENV === 'development';
772
+
773
+ const { mf } = await loadRemoteMultiVersion({
774
+ name: 'ui_lib',
775
+ pkg: 'my-ui-components',
776
+ version: '1.0.0',
777
+ ...(isDev && {
778
+ localFallback: 'http://localhost:3001/remoteEntry.js',
779
+ }),
780
+ });
781
+ ```
782
+
783
+ ## 故障排查
784
+
785
+ ### 常见问题
786
+
787
+ #### 1. 加载失败:网络错误
788
+
789
+ **症状**: 控制台显示 `[MF] 所有 CDN 加载失败`
790
+
791
+ **解决方案**:
792
+ - 检查网络连接
793
+ - 验证 CDN 地址是否可访问
794
+ - 增加 `retries` 和 `delay` 值
795
+ - 配置 `localFallback` 作为兜底
796
+
797
+ ```typescript
798
+ {
799
+ retries: 5,
800
+ delay: 2000,
801
+ localFallback: 'http://localhost:3001/remoteEntry.js',
802
+ }
803
+ ```
804
+
805
+ #### 2. 版本缓存过期
806
+
807
+ **症状**: 使用 `latest` 时版本不是最新的
808
+
809
+ **解决方案**:
810
+ - 清除 localStorage 中的 `mf-multi-version` 键
811
+ - 减少 `cacheTTL` 值
812
+ - 手动指定版本号
813
+
814
+ ```typescript
815
+ localStorage.removeItem('mf-multi-version');
816
+ ```
817
+
818
+ #### 3. 组件渲染错误
819
+
820
+ **症状**: 组件加载成功但渲染报错
821
+
822
+ **解决方案**:
823
+ - 检查 React 版本兼容性
824
+ - 确认共享模块配置正确
825
+ - 使用错误边界捕获错误
826
+
827
+ ```typescript
828
+ class ErrorBoundary extends React.Component {
829
+ state = { hasError: false };
830
+
831
+ static getDerivedStateFromError(error) {
832
+ return { hasError: true };
833
+ }
834
+
835
+ render() {
836
+ if (this.state.hasError) {
837
+ return <div>Something went wrong</div>;
838
+ }
839
+ return this.props.children;
840
+ }
841
+ }
842
+ ```
843
+
844
+ #### 4. 类型错误
845
+
846
+ **症状**: TypeScript 报错找不到类型定义
847
+
848
+ **解决方案**:
849
+ - 确保远程组件已发布类型定义
850
+ - 检查 `tsconfig.json` 的 `moduleResolution` 配置
851
+ - 使用 `import type` 导入类型
852
+
853
+ ```typescript
854
+ import type { ButtonProps } from 'my-ui-components';
855
+ ```
856
+
857
+ ### 调试技巧
858
+
859
+ #### 1. 启用详细日志
860
+
861
+ ```typescript
862
+ const { mf } = await loadRemoteMultiVersion(options, [
863
+ {
864
+ name: 'debug-plugin',
865
+ afterResolve(args) {
866
+ console.log('[Debug] Resolved:', args);
867
+ },
868
+ },
869
+ ]);
870
+ ```
871
+
872
+ #### 2. 监控加载状态
873
+
874
+ ```typescript
875
+ console.log('Starting load...');
876
+
877
+ const { scopeName, mf } = await loadRemoteMultiVersion(options);
878
+
879
+ console.log('MF instance created:', mf);
880
+
881
+ const mod = await mf.loadRemote(`${scopeName}/Button`);
882
+
883
+ console.log('Module loaded:', mod);
884
+ ```
885
+
886
+ #### 3. 验证 CDN 地址
887
+
888
+ ```typescript
889
+ function buildCdnUrls(pkg: string, version: string) {
890
+ return [
891
+ `https://cdn.jsdelivr.net/npm/${pkg}@${version}/dist/remoteEntry.js`,
892
+ `https://unpkg.com/${pkg}@${version}/dist/remoteEntry.js`,
893
+ ];
894
+ }
895
+
896
+ // 在浏览器中手动访问这些 URL 验证是否可用
897
+ ```
898
+
899
+ ## 高级功能
900
+
901
+ ### 预加载远程模块
902
+
903
+ 使用 `preloadRemote` 预加载远程模块,提升用户体验。
904
+
905
+ ```typescript
906
+ import { preloadRemote } from 'remote-reload-utils';
907
+
908
+ // 空闲时预加载
909
+ preloadRemote({
910
+ name: 'my-lib',
911
+ pkg: 'my-ui-lib',
912
+ version: '1.0.0',
913
+ priority: 'idle', // 'idle' 或 'high'
914
+ });
915
+
916
+ // 批量预加载
917
+ await preloadRemoteList([
918
+ { name: 'lib1', pkg: 'pkg1', version: '1.0.0' },
919
+ { name: 'lib2', pkg: 'pkg2', version: '2.0.0' },
920
+ ], (loaded, total) => {
921
+ console.log(`加载进度: ${loaded}/${total}`);
922
+ });
923
+
924
+ // 检查预加载状态
925
+ const status = getPreloadStatus('my-lib');
926
+ if (status?.loaded) {
927
+ console.log('已预加载,时间戳:', status.timestamp);
928
+ }
929
+
930
+ // 取消预加载
931
+ cancelPreload('my-lib');
932
+
933
+ // 清除所有预加载缓存
934
+ clearPreloadCache();
935
+ ```
936
+
937
+ ### 卸载远程模块
938
+
939
+ 使用 `unloadRemote` 卸载已加载的远程模块,释放资源。
940
+
941
+ ```typescript
942
+ import { unloadRemote, unloadAll, getLoadedRemotes } from 'remote-reload-utils';
943
+
944
+ // 卸载指定模块
945
+ await unloadRemote({
946
+ name: 'my-lib',
947
+ pkg: 'my-ui-lib',
948
+ version: '1.0.0',
949
+ });
950
+
951
+ // 卸载所有模块
952
+ await unloadAll(true); // true 表示同时清除缓存
953
+
954
+ // 查看已加载的模块
955
+ const loaded = getLoadedRemotes();
956
+ console.log(loaded);
957
+ // [{ name, pkg, version, loadedModules, timestamp }]
958
+ ```
959
+
960
+ ### 健康检查
961
+
962
+ 使用 `checkRemoteHealth` 检查远程模块的可用性和性能。
963
+
964
+ ```typescript
965
+ import { checkRemoteHealth, getRemoteHealthReport, formatHealthStatus } from 'remote-reload-utils';
966
+
967
+ // 检查单个远程模块
968
+ const health = await checkRemoteHealth({
969
+ name: 'my-lib',
970
+ pkg: 'my-ui-lib',
971
+ version: '1.0.0',
972
+ });
973
+
974
+ console.log(formatHealthStatus(health.status));
975
+ // 🟢 healthy 或 🟡 degraded 或 🔴 unhealthy
976
+
977
+ // 批量检查多个远程模块
978
+ const report = await getRemoteHealthReport([
979
+ { name: 'lib1', pkg: 'pkg1' },
980
+ { name: 'lib2', pkg: 'pkg2' },
981
+ ]);
982
+
983
+ console.log('总体状态:', report.overall);
984
+ ```
985
+
986
+ ### React Hooks
987
+
988
+ 提供 `useRemote` 和 `useRemoteList` Hooks,简化 React 中的使用。
989
+
990
+ ```typescript
991
+ import { useRemote, useRemoteList, onRemoteReady, onRemoteError } from 'remote-reload-utils';
992
+
993
+ // 单个远程组件
994
+ function MyComponent() {
995
+ const { component: Button, loading, error, retry } = useRemote({
996
+ name: 'ui-lib',
997
+ pkg: 'my-ui-lib',
998
+ modulePath: 'Button',
999
+ version: '1.0.0',
1000
+ onReady: (comp) => console.log('加载成功'),
1001
+ onError: (err) => console.error('加载失败', err),
1002
+ });
1003
+
1004
+ if (loading) return <div>Loading...</div>;
1005
+ if (error) return <button onClick={retry}>重试</button>;
1006
+
1007
+ return Button ? <Button /> : null;
1008
+ }
1009
+
1010
+ // 批量加载
1011
+ function MultiComponent() {
1012
+ const { components, loading, errors } = useRemoteList({
1013
+ remotes: [
1014
+ { name: 'lib1', pkg: 'pkg1', modulePath: 'Button' },
1015
+ { name: 'lib2', pkg: 'pkg2', modulePath: 'Card' },
1016
+ ],
1017
+ onAllReady: (cmps) => console.log('全部加载完成'),
1018
+ onRemoteError: (err, pkg) => console.error(`${pkg} 加载失败`, err),
1019
+ });
1020
+
1021
+ if (loading) return <div>Loading...</div>;
1022
+
1023
+ return (
1024
+ <div>
1025
+ {components.get('pkg1/Button')?.()}
1026
+ {components.get('pkg2/Card')?.()}
1027
+ </div>
1028
+ );
1029
+ }
1030
+
1031
+ // 事件监听
1032
+ onRemoteReady('ui-lib', (scopeName, mf) => {
1033
+ console.log('远程模块已就绪', scopeName);
1034
+ });
1035
+
1036
+ onRemoteError('ui-lib', (error) => {
1037
+ console.error('远程模块加载失败', error);
1038
+ });
1039
+ ```
1040
+
1041
+ ### 跨模块共享状态
1042
+
1043
+ 使用 `createSharedContext` 在不同远程模块间共享状态。
1044
+
1045
+ ```typescript
1046
+ import { createSharedContext } from 'remote-reload-utils';
1047
+
1048
+ // 创建共享上下文
1049
+ const { Provider, useContext, useSharedState, useSelector, setValue, getValue, reset, destroy } =
1050
+ createSharedContext('app-store', { count: 0, user: null });
1051
+
1052
+ // 在 React 中使用
1053
+ function App() {
1054
+ return (
1055
+ <Provider value={{ count: 0, user: null }}>
1056
+ <Counter />
1057
+ <UserInfo />
1058
+ </Provider>
1059
+ );
1060
+ }
1061
+
1062
+ function Counter() {
1063
+ const [count, setCount] = useSharedState();
1064
+ return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
1065
+ }
1066
+
1067
+ function UserInfo() {
1068
+ const user = useSelector((state) => state.user);
1069
+ return <div>{user?.name || '未登录'}</div>;
1070
+ }
1071
+
1072
+ // 非 React 环境使用
1073
+ setValue({ count: 5, user: { id: 1, name: '张三' } });
1074
+ const current = getValue();
1075
+ reset(); // 重置为初始值
1076
+ destroy(); // 销毁上下文
1077
+ ```
1078
+
1079
+ ### 事件总线
1080
+
1081
+ 使用 `eventBus` 实现跨模块通信。
1082
+
1083
+ ```typescript
1084
+ import { eventBus, createEventBus } from 'remote-reload-utils';
1085
+
1086
+ // 监听事件
1087
+ const unsubscribe = eventBus.on('user-login', (user) => {
1088
+ console.log('用户登录:', user);
1089
+ });
1090
+
1091
+ // 一次性监听
1092
+ eventBus.once('notification', (data) => {
1093
+ console.log('收到通知:', data);
1094
+ });
1095
+
1096
+ // 发送事件
1097
+ eventBus.emit('user-login', { id: 1, name: '张三' });
1098
+ eventBus.emit('notification', { message: '有新消息' }, { source: 'system' });
1099
+
1100
+ // 带过滤条件监听
1101
+ eventBus.on('order', (order) => {
1102
+ console.log('订单:', order);
1103
+ }, { filter: (order) => order.status === 'paid' });
1104
+
1105
+ // 查看历史事件
1106
+ const history = eventBus.getHistory('user-login');
1107
+ console.log('登录历史:', history);
1108
+
1109
+ // 查看所有事件
1110
+ console.log('所有事件:', eventBus.getEvents());
1111
+
1112
+ // 清除事件
1113
+ eventBus.clear('user-login'); // 清除单个事件
1114
+ eventBus.clear(); // 清除所有事件
1115
+
1116
+ // 创建独立的事件总线实例
1117
+ const myBus = createEventBus();
1118
+ ```
1119
+
1120
+ ### 版本兼容性检查
1121
+
1122
+ 使用 `checkVersionCompatibility` 检查版本兼容性。
1123
+
1124
+ ```typescript
1125
+ import {
1126
+ checkVersionCompatibility,
1127
+ satisfiesVersion,
1128
+ findCompatibleVersion,
1129
+ fetchAvailableVersions,
1130
+ sortVersions,
1131
+ getLatestVersion,
1132
+ getStableVersions,
1133
+ } from 'remote-reload-utils';
1134
+
1135
+ // 检查版本兼容性
1136
+ const result = checkVersionCompatibility('18.2.0', '^18.0.0', 'react');
1137
+ console.log(result.compatible); // true
1138
+ console.log(result.severity); // 'info' | 'warning' | 'error'
1139
+ console.log(result.message); // 描述信息
1140
+ console.log(result.suggestion); // 升级建议
1141
+
1142
+ // 检查是否满足版本范围
1143
+ satisfiesVersion('1.2.3', '^1.0.0'); // true
1144
+ satisfiesVersion('2.0.0', '^1.0.0'); // false
1145
+
1146
+ // 查找兼容版本
1147
+ const versions = ['1.0.0', '1.1.0', '2.0.0', '2.1.0'];
1148
+ findCompatibleVersion(versions, { min: '1.0.0', max: '2.0.0' }); // '2.0.0'
1149
+
1150
+ // 获取可用版本
1151
+ const available = await fetchAvailableVersions('react');
1152
+ const sorted = sortVersions(available, 'desc');
1153
+ const latest = getLatestVersion(available);
1154
+ const stable = getStableVersions(available); // 过滤掉 alpha/beta/rc 版本
1155
+ ```
1156
+
1157
+ ### React 组件适配器
1158
+
1159
+ 提供多种方式在 React 中使用远程组件。
1160
+
1161
+ ```typescript
1162
+ import { RemoteComponent, SuspenseRemote, ErrorBoundary, withRemote, lazyRemote } from 'remote-reload-utils';
1163
+
1164
+ // 1. 直接使用 RemoteComponent
1165
+ function App() {
1166
+ return (
1167
+ <RemoteComponent
1168
+ name="ui-lib"
1169
+ pkg="my-ui-lib"
1170
+ modulePath="Button"
1171
+ version="1.0.0"
1172
+ fallback={<div>Loading...</div>}
1173
+ errorFallback={(error) => <div>加载失败: {error.message}</div>}
1174
+ onLoading={() => console.log('开始加载')}
1175
+ onError={(error) => console.error(error)}
1176
+ />
1177
+ );
1178
+ }
1179
+
1180
+ // 2. 使用 SuspenseRemote(支持 React.Suspense)
1181
+ function App() {
1182
+ return (
1183
+ <SuspenseRemote
1184
+ name="ui-lib"
1185
+ pkg="my-ui-lib"
1186
+ modulePath="Button"
1187
+ version="1.0.0"
1188
+ loading={<div>Loading...</div>}
1189
+ >
1190
+ <ChildComponent />
1191
+ </SuspenseRemote>
1192
+ );
1193
+ }
1194
+
1195
+ // 3. 使用 withRemote 高阶组件
1196
+ const RemoteButton = withRemote({
1197
+ name: 'ui-lib',
1198
+ pkg: 'my-ui-lib',
1199
+ modulePath: 'Button',
1200
+ version: '1.0.0',
1201
+ })(OriginalButton);
1202
+
1203
+ // 4. 使用 lazyRemote(支持 React.lazy)
1204
+ const RemoteButton = lazyRemote({
1205
+ name: 'ui-lib',
1206
+ pkg: 'my-ui-lib',
1207
+ modulePath: 'Button',
1208
+ version: '1.0.0',
1209
+ });
1210
+
1211
+ function App() {
1212
+ return (
1213
+ <React.Suspense fallback={<div>Loading...</div>}>
1214
+ <RemoteButton />
1215
+ </React.Suspense>
1216
+ );
1217
+ }
1218
+
1219
+ // 5. 使用 ErrorBoundary 包裹
1220
+ function App() {
1221
+ return (
1222
+ <ErrorBoundary fallback={(error) => <div>出错了: {error.message}</div>}>
1223
+ <RemoteComponent {...config} />
1224
+ </ErrorBoundary>
1225
+ );
1226
+ }
1227
+ ```
1228
+
1229
+ ## 类型定义
1230
+
1231
+ ```typescript
1232
+ interface LoadRemoteOptions {
1233
+ name: string; // 模块联邦 name(基础名)
1234
+ pkg: string; // npm 包名
1235
+ version?: string; // 指定版本 or "latest"
1236
+ retries?: number; // 重试次数
1237
+ delay?: number; // 重试间隔
1238
+ localFallback?: string; // 本地兜底
1239
+ cacheTTL?: number; // 缓存时间
1240
+ revalidate?: boolean; // 灰度更新
1241
+ shared?: Record<string, ModuleFederationRuntimePlugin>; // 自定义 shared 配置
1242
+ }
1243
+
1244
+ interface VersionCache {
1245
+ [pkg: string]: {
1246
+ [version: string]: {
1247
+ timestamp: number;
1248
+ };
1249
+ };
1250
+ }
1251
+
1252
+ interface LoadResult {
1253
+ scopeName: string;
1254
+ mf: ReturnType<typeof createInstance>;
1255
+ }
1256
+
1257
+ // 预加载相关类型
1258
+ interface PreloadOptions extends LoadRemoteOptions {
1259
+ priority?: 'idle' | 'high';
1260
+ force?: boolean;
1261
+ }
1262
+
1263
+ interface PreloadCacheItem {
1264
+ version: string;
1265
+ scopeName: string;
1266
+ mf: any;
1267
+ timestamp: number;
1268
+ }
1269
+
1270
+ interface PreloadStatus {
1271
+ loaded: boolean;
1272
+ timestamp: number;
1273
+ }
1274
+
1275
+ // 健康检查相关类型
1276
+ interface HealthCheckResult {
1277
+ pkg: string;
1278
+ version: string;
1279
+ status: 'healthy' | 'degraded' | 'unhealthy';
1280
+ latency: number;
1281
+ cdn: string;
1282
+ details: {
1283
+ cdnReachable: boolean;
1284
+ remoteEntryValid: boolean;
1285
+ modulesLoadable: boolean;
1286
+ error?: string;
1287
+ };
1288
+ }
1289
+
1290
+ interface RemoteHealthReport {
1291
+ timestamp: number;
1292
+ overall: 'healthy' | 'degraded' | 'unhealthy';
1293
+ remotes: HealthCheckResult[];
1294
+ }
1295
+
1296
+ // 事件总线相关类型
1297
+ type EventCallback<T = any> = (data: T, meta?: EventMeta) => void;
1298
+
1299
+ interface EventMeta {
1300
+ timestamp: number;
1301
+ source?: string;
1302
+ id?: string;
1303
+ }
1304
+
1305
+ interface EventEmitterOptions {
1306
+ once?: boolean;
1307
+ filter?: (data: any, meta: EventMeta) => boolean;
1308
+ }
1309
+
1310
+ // 版本检查相关类型
1311
+ interface VersionInfo {
1312
+ major: number;
1313
+ minor: number;
1314
+ patch: number;
1315
+ prerelease?: string;
1316
+ build?: string;
1317
+ raw: string;
1318
+ }
1319
+
1320
+ interface CompatibilityResult {
1321
+ compatible: boolean;
1322
+ currentVersion: string;
1323
+ requiredVersion: string;
1324
+ suggestion?: string;
1325
+ severity: 'error' | 'warning' | 'info';
1326
+ message: string;
1327
+ }
1328
+
1329
+ interface VersionRange {
1330
+ min?: string;
1331
+ max?: string;
1332
+ exact?: string;
1333
+ }
1334
+
1335
+ // React Hooks 相关类型
1336
+ interface RemoteHookResult<T = any> {
1337
+ component: T | null;
1338
+ loading: boolean;
1339
+ error: Error | null;
1340
+ retry: () => void;
1341
+ scopeName: string | null;
1342
+ mf: any | null;
1343
+ }
1344
+
1345
+ interface UseRemoteOptions extends LoadRemoteOptions {
1346
+ modulePath: string;
1347
+ plugins?: ModuleFederationRuntimePlugin[];
1348
+ onReady?: (component: any, scopeName: string) => void;
1349
+ onError?: (error: Error) => void;
1350
+ skip?: boolean;
1351
+ }
1352
+
1353
+ // 共享上下文相关类型
1354
+ interface SharedContextApi<T> {
1355
+ Provider: React.ComponentType<{ value: T; children: React.ReactNode }>;
1356
+ useContext: () => T;
1357
+ useSharedState: () => [T, (value: T | ((prev: T) => T)) => void];
1358
+ useSelector: <R>(selector: (value: T) => R) => R;
1359
+ setValue: (value: T | ((prev: T) => T)) => void;
1360
+ getValue: () => T;
1361
+ subscribe: (listener: (value: T) => void) => () => void;
1362
+ reset: () => void;
1363
+ destroy: () => void;
1364
+ }
1365
+ ```
1366
+
1367
+ ## 更多资源
1368
+
1369
+ - [Module Federation 官方文档](https://module-federation.io/)
1370
+ - [@module-federation/enhanced 文档](https://github.com/module-federation/enhanced)
1371
+ - [主项目 README](../../readme.md)