remote-reload-utils 0.0.8 → 0.0.11
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 +154 -0
- package/README.md +62 -0
- package/dist/components/ErrorBoundary.d.ts +34 -0
- package/dist/components/RemoteModuleCard.d.ts +84 -0
- package/dist/components/SuspenseLoader.d.ts +131 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/event-bus/index.d.ts +40 -0
- package/dist/health/index.d.ts +23 -0
- package/dist/index.d.ts +12 -2
- package/dist/loader/index.d.ts +6 -0
- package/dist/loader/utils.d.ts +38 -0
- package/dist/main.cjs +1084 -52
- package/dist/main.js +935 -51
- package/dist/preload/index.d.ts +19 -0
- package/dist/{types.d.ts → types/index.d.ts} +15 -0
- package/dist/unload/index.d.ts +29 -0
- package/dist/version/index.d.ts +34 -0
- package/loadRemote.md +1356 -28
- package/package.json +24 -7
- package/dist/loadRemote.d.ts +0 -9
- package/dist/loadRemote2.d.ts +0 -11
- package/dist/react-mf-adapter.d.ts +0 -0
- /package/dist/{plugins.d.ts → plugins/fallback.d.ts} +0 -0
- /package/dist/{loadReactVersion.d.ts → version/react.d.ts} +0 -0
package/loadRemote.md
CHANGED
|
@@ -1,43 +1,1371 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
7
|
-
const [
|
|
109
|
+
function App() {
|
|
110
|
+
const [Button, setButton] = useState(null);
|
|
111
|
+
|
|
8
112
|
useEffect(() => {
|
|
9
|
-
async function
|
|
113
|
+
async function loadComponent() {
|
|
114
|
+
// 加载远程模块
|
|
10
115
|
const { scopeName, mf } = await loadRemoteMultiVersion({
|
|
11
|
-
name: '
|
|
12
|
-
pkg: '
|
|
13
|
-
version: '1.0.
|
|
14
|
-
// version: 'latest',
|
|
116
|
+
name: 'ui_lib',
|
|
117
|
+
pkg: 'my-ui-components',
|
|
118
|
+
version: '1.0.0',
|
|
15
119
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
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
|
-
|
|
125
|
+
|
|
126
|
+
loadComponent();
|
|
31
127
|
}, []);
|
|
32
128
|
|
|
33
129
|
return (
|
|
34
|
-
<div
|
|
35
|
-
<
|
|
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
|
-
|
|
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)
|