@vnb/mfe-events 0.0.1-rc.6 → 0.0.1-rc.8
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/README.md +12 -16
- package/dist/bus.d.ts +4 -1
- package/dist/bus.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +173 -79
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +171 -80
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +173 -79
- package/dist/index.umd.js.map +1 -1
- package/dist/plugin.d.ts +30 -10
- package/dist/plugin.d.ts.map +1 -1
- package/dist/transport.d.ts +11 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/types.d.ts +26 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -225,7 +225,7 @@ mfeBus.on(EvtMfe.ShlTokenExpired, (data) => {
|
|
|
225
225
|
// 子应用 token 过期 → 重发当前 token
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
-
// 3.
|
|
228
|
+
// 3. 定向发送(三级降级:registry → DOM 扫描 iframe[data-mfe-app] → 广播 fallback)
|
|
229
229
|
mfeEventPlugin.emitTo('mma', EvtMfe.RouteNavigate, { path: '/chairman' });
|
|
230
230
|
|
|
231
231
|
// 4. 广播(所有子应用均可收到)
|
|
@@ -332,7 +332,7 @@ shlBus.on(EvtShl.TabOverflow, (data) => {
|
|
|
332
332
|
|------|-----|------|------|---------|---------|
|
|
333
333
|
| **ThemeUpdate** | `mfe:theme-update` | Shell → App | 主题切换 | `{ theme: 'light' \| 'dark' }` | "主题切换了,你们跟着换" |
|
|
334
334
|
| **LocaleUpdate** | `mfe:locale-update` | Shell → App | 语言切换 | `{ locale: string }` | "语言切换了,你们跟着换" |
|
|
335
|
-
| **BreakpointUpdate** | `mfe:breakpoint-update` | Shell → App | 响应式断点/横竖屏变化(Shell 基于 `window.matchMedia` 监听并广播,子应用收到后同步更新布局,如侧边栏收起、表格横向滚动等) | `{ current: string; orientation:
|
|
335
|
+
| **BreakpointUpdate** | `mfe:breakpoint-update` | Shell → App | 响应式断点/横竖屏变化(Shell 基于 `window.matchMedia` 监听并广播,子应用收到后同步更新布局,如侧边栏收起、表格横向滚动等) | `{ current: string; orientation: string }` | "屏幕尺寸变了,你们调整一下布局" |
|
|
336
336
|
| **StateUpdate** | `mfe:state-update` | Shell → App | 全局状态同步 | `{ state: AppState; prev: AppState \| null }` | "全局状态更新了,你们同步" |
|
|
337
337
|
| **ShellReady** | `mfe:shell-ready` | Shell → App | Shell 初始化完成 | `{}` | "我准备好了,你们可以开始工作" |
|
|
338
338
|
| **Error** | `mfe:error` | App → Shell | 子应用错误上报 | `{ source: string; message: string; stack?: string }` | "我报错了,你看一下" |
|
|
@@ -349,8 +349,8 @@ shlBus.on(EvtShl.TabOverflow, (data) => {
|
|
|
349
349
|
| **AuthSelfOk** | `mfe:auth-self-ok` | App → Shell | 自行登录成功 | `{ appId: string; user?: any; token?: string }` | "我自行登录成功了" |
|
|
350
350
|
| **AuthSelfErr** | `mfe:auth-self-err` | App → Shell | 自行登录失败 | `{ appId: string; reason?: string }` | "我自行登录失败了" |
|
|
351
351
|
| **AuthSelfDeny** | `mfe:auth-self-deny` | App → Shell | 子应用拒绝加载,请求基座卸载 | `{ appId: string; reason?: string }` | "我不想被加载,卸载掉我" |
|
|
352
|
-
| **RouteNavigate** | `mfe:route-navigate` | Shell → App | Shell 指定子应用导航到路径(`emitTo` 定向) | `{ path: string }` | "跳到这个路径去" |
|
|
353
|
-
| **RouteRefresh** | `mfe:route-refresh` | Shell → App | 要求子应用刷新当前页面,保留路由(`emitTo` 定向) | `{ path: string }` | "刷新你当前的路由/页面" |
|
|
352
|
+
| **RouteNavigate** | `mfe:route-navigate` | Shell → App | Shell 指定子应用导航到路径(`emitTo` 定向) | `{ path: string; appId?: string }` | "跳到这个路径去" |
|
|
353
|
+
| **RouteRefresh** | `mfe:route-refresh` | Shell → App | 要求子应用刷新当前页面,保留路由(`emitTo` 定向) | `{ path: string; appId?: string }` | "刷新你当前的路由/页面" |
|
|
354
354
|
| **RouteChange** | `mfe:route-change` | App → Shell | 子应用上报路由变化 | `{ appId: string; path: string }` | "我路由变了,跟一下" |
|
|
355
355
|
| **LifecycleAppClose** | `mfe:lifecycle-app-close` | Shell → App | Shell 关闭子应用 | `{ appId: string }` | "我要关掉你了" |
|
|
356
356
|
| **LifecycleAppReady** | `mfe:lifecycle-app-ready` | App → Shell | 子应用初始化完成 | `{ appId: string }` | "我加载完了" |
|
|
@@ -573,8 +573,8 @@ mfeEventPlugin.register({
|
|
|
573
573
|
appId: 'my-app',
|
|
574
574
|
handlers: {
|
|
575
575
|
[EvtMfe.BreakpointUpdate]: (data) => {
|
|
576
|
-
// data.current: 'xs'
|
|
577
|
-
// data.orientation: 'portrait'
|
|
576
|
+
// data.current: string (如 'xs', 'sm', 'lg', 'md', 'xl' 等)
|
|
577
|
+
// data.orientation: string (如 'portrait', 'landscape')
|
|
578
578
|
document.documentElement.dataset.breakpoint = data.current;
|
|
579
579
|
document.documentElement.dataset.orientation = data.orientation;
|
|
580
580
|
},
|
|
@@ -823,18 +823,14 @@ ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
|
|
|
823
823
|
2. 确认 Federation 配置中有 `@vnb/mfe-events` 且 `singleton: true`
|
|
824
824
|
3. 确认使用的是事件常量 `EvtMfe.XXX` 而非硬编码字符串
|
|
825
825
|
4. 控制台检查 `window.__MFE_EVENT_PLUGIN__` 是否存在
|
|
826
|
-
5. 如果是 iframe,确认调用了 `mfeEventPlugin.registerIframe()`
|
|
827
|
-
6.
|
|
826
|
+
5. 如果是 iframe,确认调用了 `mfeEventPlugin.registerIframe()`,或 iframe 设置了 `data-mfe-app="appId"` 属性
|
|
827
|
+
6. `emitTo` 支持三级降级:registry 命中 → DOM 扫描 `iframe[data-mfe-app]` → 广播 fallback(带 appId 让子应用自判断)
|
|
828
828
|
|
|
829
829
|
### emitTo 报 app not registered
|
|
830
830
|
|
|
831
|
-
动态加载时,如果 Shell 在子应用注册之前就调用了 `emitTo
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
[mfe-events] emitTo: app not registered: "mma" (event: mfe:route-navigate)
|
|
835
|
-
```
|
|
836
|
-
|
|
837
|
-
解决方案:确保子应用先调用 `register()`,再让 Shell 发 `emitTo`。或者在子应用 `LifecycleAppReady` 之后,再由子应用主动请求数据(而不是等 Shell push)。
|
|
831
|
+
动态加载时,如果 Shell 在子应用注册之前就调用了 `emitTo`,插件会自动降级处理:
|
|
832
|
+
1. 扫描 DOM 中 `iframe[data-mfe-app="appId"]`,找到则动态创建 transport 定向发送
|
|
833
|
+
2. 都没找到则广播 `emit`(带 appId 参数),子应用收到后自行判断是否处理
|
|
838
834
|
|
|
839
835
|
### 通知不显示
|
|
840
836
|
|
|
@@ -844,7 +840,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
|
|
|
844
840
|
|
|
845
841
|
### iframe 子应用收不到事件
|
|
846
842
|
|
|
847
|
-
1. 确认 Shell
|
|
843
|
+
1. 确认 iframe 设置了 `data-mfe-app="appId"` 属性,或 Shell 调用了 `mfeEventPlugin.registerIframe(appId, iframeEl, targetOrigin)`
|
|
848
844
|
2. 确认 `targetOrigin` 与 iframe 的 `src` origin 匹配(生产环境建议明确指定)
|
|
849
845
|
3. 检查浏览器控制台是否有 postMessage 相关的 CSP 警告
|
|
850
846
|
|
package/dist/bus.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export declare class Bus<T extends Record<string, any>> {
|
|
|
17
17
|
* Returns unsubscribe function
|
|
18
18
|
*/
|
|
19
19
|
on<K extends keyof T>(event: K, handler: (data: T[K]) => void, options?: {
|
|
20
|
-
priority?: number;
|
|
21
20
|
once?: boolean;
|
|
22
21
|
}): () => void;
|
|
23
22
|
/**
|
|
@@ -25,5 +24,9 @@ export declare class Bus<T extends Record<string, any>> {
|
|
|
25
24
|
* Returns unsubscribe function
|
|
26
25
|
*/
|
|
27
26
|
once<K extends keyof T>(event: K, handler: (data: T[K]) => void): () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Destroy the bus and clean up all resources
|
|
29
|
+
*/
|
|
30
|
+
destroy(): void;
|
|
28
31
|
}
|
|
29
32
|
//# sourceMappingURL=bus.d.ts.map
|
package/dist/bus.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,qBAAa,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAChC,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,SAAS;IAExC;;OAEG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAInD;;;OAGG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,EAClB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7B,OAAO,CAAC,EAAE;QAAE,
|
|
1
|
+
{"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,qBAAa,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAChC,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,SAAS;IAExC;;OAEG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAInD;;;OAGG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,EAClB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7B,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,MAAM,IAAI;IAOb;;;OAGG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAQ5E;;OAEG;IACH,OAAO,IAAI,IAAI;CAGhB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ export { MemoryTransport, WindowTransport, PostMessageTransport, IframeProxyTran
|
|
|
20
20
|
export type { Transport } from './transport';
|
|
21
21
|
export { Bus } from './bus';
|
|
22
22
|
export { EvtShl, EvtMfe, EvtApp } from './constants';
|
|
23
|
-
export type { PayloadShl, PayloadMfe, PayloadApp, NotificationType, NotificationConfig, AppType, AppMeta, PluginOptions, } from './types';
|
|
23
|
+
export type { PayloadShl, PayloadMfe, PayloadApp, NotificationType, NotificationConfig, AppType, AppMeta, PluginOptions, User, AppState, BreakpointValue, DeviceTypeValue, } from './types';
|
|
24
|
+
export { Breakpoint, DeviceType, breakpointToDevice, } from './types';
|
|
24
25
|
export { mfeEventPlugin } from './plugin';
|
|
25
26
|
export type { default as MfeEventPlugin } from './plugin';
|
|
26
27
|
export { mfeEventPlugin as default } from './plugin';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3G,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAG5B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrD,YAAY,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EACP,OAAO,EACP,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3G,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAG5B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrD,YAAY,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EACP,OAAO,EACP,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,UAAU,EACV,UAAU,EACV,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,YAAY,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAE,cAAc,IAAI,OAAO,EAAE,MAAM,UAAU,CAAC;AAOrD,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,MAAM,iBAA6C,CAAC;AAmBjE,eAAO,MAAM,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @vnb/mfe-events v0.0.1-rc.
|
|
2
|
+
* @vnb/mfe-events v0.0.1-rc.8
|
|
3
3
|
* Micro frontend event plugin for cross-app communication with type safety
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
@@ -32,27 +32,47 @@ class MemoryTransport {
|
|
|
32
32
|
this.target.addEventListener(event, wrapper);
|
|
33
33
|
return () => this.target.removeEventListener(event, wrapper);
|
|
34
34
|
}
|
|
35
|
+
destroy() {
|
|
36
|
+
this.target = new EventTarget();
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
37
40
|
* WindowTransport — MF/NF Remote Apps communication
|
|
38
41
|
* Uses window.CustomEvent for same-tab communication
|
|
39
42
|
*/
|
|
40
43
|
class WindowTransport {
|
|
44
|
+
constructor() {
|
|
45
|
+
this.listeners = new Map();
|
|
46
|
+
}
|
|
41
47
|
emit(event, data) {
|
|
42
48
|
window.dispatchEvent(new CustomEvent(event, { detail: data }));
|
|
43
49
|
}
|
|
44
50
|
on(event, handler) {
|
|
45
51
|
const wrapper = (e) => handler(e.detail);
|
|
46
52
|
window.addEventListener(event, wrapper);
|
|
47
|
-
|
|
53
|
+
this.listeners.set(event, wrapper);
|
|
54
|
+
return () => {
|
|
55
|
+
window.removeEventListener(event, wrapper);
|
|
56
|
+
this.listeners.delete(event);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
destroy() {
|
|
60
|
+
this.listeners.forEach((wrapper, event) => {
|
|
61
|
+
window.removeEventListener(event, wrapper);
|
|
62
|
+
});
|
|
63
|
+
this.listeners.clear();
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
66
|
/**
|
|
51
67
|
* PostMessageTransport — iframe Remote Apps communication
|
|
52
68
|
* Uses window.postMessage for cross-origin communication
|
|
69
|
+
*
|
|
70
|
+
* 注意:Shell 环境下不要使用此Transport(Shell 应使用 IframeProxyTransport)。
|
|
71
|
+
* 此Transport 用于 iframe 子应用向 parent Shell 发送消息。
|
|
53
72
|
*/
|
|
54
73
|
class PostMessageTransport {
|
|
55
74
|
constructor(options = {}) {
|
|
75
|
+
this.listeners = new Map();
|
|
56
76
|
this.targetWindow = options.targetWindow || window.parent;
|
|
57
77
|
this.targetOrigin = options.targetOrigin || '*';
|
|
58
78
|
}
|
|
@@ -66,7 +86,17 @@ class PostMessageTransport {
|
|
|
66
86
|
}
|
|
67
87
|
};
|
|
68
88
|
window.addEventListener('message', wrapper);
|
|
69
|
-
|
|
89
|
+
this.listeners.set(event, wrapper);
|
|
90
|
+
return () => {
|
|
91
|
+
window.removeEventListener('message', wrapper);
|
|
92
|
+
this.listeners.delete(event);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
destroy() {
|
|
96
|
+
this.listeners.forEach((wrapper) => {
|
|
97
|
+
window.removeEventListener('message', wrapper);
|
|
98
|
+
});
|
|
99
|
+
this.listeners.clear();
|
|
70
100
|
}
|
|
71
101
|
}
|
|
72
102
|
/**
|
|
@@ -75,6 +105,7 @@ class PostMessageTransport {
|
|
|
75
105
|
*/
|
|
76
106
|
class IframeProxyTransport {
|
|
77
107
|
constructor(iframe, targetOrigin = '*') {
|
|
108
|
+
this.listeners = new Map();
|
|
78
109
|
this.iframe = iframe;
|
|
79
110
|
this.targetOrigin = targetOrigin;
|
|
80
111
|
}
|
|
@@ -83,7 +114,6 @@ class IframeProxyTransport {
|
|
|
83
114
|
}
|
|
84
115
|
on(event, handler) {
|
|
85
116
|
const wrapper = (e) => {
|
|
86
|
-
// Only receive messages from this specific iframe
|
|
87
117
|
if (e.source === this.iframe.contentWindow &&
|
|
88
118
|
e.data?.scope === 'mfe' &&
|
|
89
119
|
e.data?.event === event) {
|
|
@@ -91,18 +121,20 @@ class IframeProxyTransport {
|
|
|
91
121
|
}
|
|
92
122
|
};
|
|
93
123
|
window.addEventListener('message', wrapper);
|
|
94
|
-
|
|
124
|
+
this.listeners.set(event, wrapper);
|
|
125
|
+
return () => {
|
|
126
|
+
window.removeEventListener('message', wrapper);
|
|
127
|
+
this.listeners.delete(event);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
destroy() {
|
|
131
|
+
this.listeners.forEach((wrapper) => {
|
|
132
|
+
window.removeEventListener('message', wrapper);
|
|
133
|
+
});
|
|
134
|
+
this.listeners.clear();
|
|
95
135
|
}
|
|
96
136
|
}
|
|
97
137
|
|
|
98
|
-
var transport = /*#__PURE__*/Object.freeze({
|
|
99
|
-
__proto__: null,
|
|
100
|
-
IframeProxyTransport: IframeProxyTransport,
|
|
101
|
-
MemoryTransport: MemoryTransport,
|
|
102
|
-
PostMessageTransport: PostMessageTransport,
|
|
103
|
-
WindowTransport: WindowTransport
|
|
104
|
-
});
|
|
105
|
-
|
|
106
138
|
/**
|
|
107
139
|
* @xxxx/mfe-events — Type-safe Event Bus
|
|
108
140
|
*
|
|
@@ -140,6 +172,12 @@ class Bus {
|
|
|
140
172
|
});
|
|
141
173
|
return off;
|
|
142
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Destroy the bus and clean up all resources
|
|
177
|
+
*/
|
|
178
|
+
destroy() {
|
|
179
|
+
this.transport.destroy?.();
|
|
180
|
+
}
|
|
143
181
|
}
|
|
144
182
|
|
|
145
183
|
/**
|
|
@@ -212,18 +250,45 @@ const EvtMfe = {
|
|
|
212
250
|
// ─────────────────────────────────────────────
|
|
213
251
|
function EvtApp(appId, name) {
|
|
214
252
|
if (!appId || !name) {
|
|
215
|
-
console.warn(
|
|
253
|
+
console.warn('[EvtApp] appId and name must not be empty');
|
|
216
254
|
return '';
|
|
217
255
|
}
|
|
218
256
|
return `app:${appId}-${name}`;
|
|
219
257
|
}
|
|
220
258
|
|
|
259
|
+
/**
|
|
260
|
+
* @xxxx/mfe-events — Type Definitions
|
|
261
|
+
*
|
|
262
|
+
* Payload types for each event scope.
|
|
263
|
+
* Keys must reference constants from constants.ts.
|
|
264
|
+
*/
|
|
265
|
+
/** Shell breakpoint constants */
|
|
266
|
+
const Breakpoint = {
|
|
267
|
+
XS: 'xs',
|
|
268
|
+
SM: 'sm',
|
|
269
|
+
LG: 'lg',
|
|
270
|
+
};
|
|
271
|
+
/** Device type constants */
|
|
272
|
+
const DeviceType = {
|
|
273
|
+
MOBILE: 'mobile',
|
|
274
|
+
TABLET: 'tablet',
|
|
275
|
+
WEB: 'web',
|
|
276
|
+
};
|
|
277
|
+
/** Breakpoint → Device type (mobile<768 / tablet 768-1023 / web≥1024) */
|
|
278
|
+
function breakpointToDevice(bp) {
|
|
279
|
+
if (bp === 'xs')
|
|
280
|
+
return 'mobile';
|
|
281
|
+
if (bp === 'sm')
|
|
282
|
+
return 'tablet';
|
|
283
|
+
return 'web';
|
|
284
|
+
}
|
|
285
|
+
|
|
221
286
|
/**
|
|
222
287
|
* @xxxx/mfe-events — Plugin Core
|
|
223
288
|
*
|
|
224
289
|
* MfeEventPlugin manages Remote App registration and provides:
|
|
225
290
|
* - register/unregister for Remote Apps
|
|
226
|
-
* - emitTo for targeted communication
|
|
291
|
+
* - emitTo for targeted communication (with 3-tier fallback)
|
|
227
292
|
* - emit for broadcast communication
|
|
228
293
|
* - Notification API (success/info/warning/danger)
|
|
229
294
|
* - Automatic iframe message proxying
|
|
@@ -270,6 +335,18 @@ class MfeEventPlugin {
|
|
|
270
335
|
}
|
|
271
336
|
}
|
|
272
337
|
// ─────────────────────────────────────────
|
|
338
|
+
// Environment Detection
|
|
339
|
+
// ─────────────────────────────────────────
|
|
340
|
+
/**
|
|
341
|
+
* Auto-detect current runtime environment.
|
|
342
|
+
* - iframe (window.self !== window.top) → 'iframe'
|
|
343
|
+
* - top-level window → 'nf' (Native Federation)
|
|
344
|
+
* - Webpack MF sub-app (also top-level) → 'nf', can explicitly pass type:'mf'
|
|
345
|
+
*/
|
|
346
|
+
detectType() {
|
|
347
|
+
return typeof window !== 'undefined' && window.self !== window.top ? 'iframe' : 'nf';
|
|
348
|
+
}
|
|
349
|
+
// ─────────────────────────────────────────
|
|
273
350
|
// Custom Renderer (for Shell integration)
|
|
274
351
|
// ─────────────────────────────────────────
|
|
275
352
|
/**
|
|
@@ -461,7 +538,7 @@ class MfeEventPlugin {
|
|
|
461
538
|
this._notifyItems.delete(id);
|
|
462
539
|
}
|
|
463
540
|
this._pendingClosers.delete(id);
|
|
464
|
-
//
|
|
541
|
+
// Dispatch NotifyClose — keep DOM and programmatic paths symmetric
|
|
465
542
|
window.dispatchEvent(new CustomEvent(EvtMfe.NotifyClose, {
|
|
466
543
|
detail: { id },
|
|
467
544
|
bubbles: true,
|
|
@@ -477,29 +554,40 @@ class MfeEventPlugin {
|
|
|
477
554
|
// ─────────────────────────────────────────
|
|
478
555
|
/**
|
|
479
556
|
* Register a Remote App with event handlers
|
|
557
|
+
*
|
|
558
|
+
* `options.type` 可选 —— 不传时自动识别环境:
|
|
559
|
+
* - iframe 内(window.self !== window.top)→ 'iframe':监听 postMessage,
|
|
560
|
+
* 并自动建立下行 message → CustomEvent 桥,同窗自定义事件也能收到。
|
|
561
|
+
* - 顶层 window → 'nf'(Native Federation,监听 CustomEvent);
|
|
562
|
+
* Webpack MF 同窗也是 CustomEvent,可显式传 type:'mf'。
|
|
480
563
|
*/
|
|
481
564
|
register(options) {
|
|
482
565
|
if (this.registry.has(options.appId)) {
|
|
483
566
|
this.unregister(options.appId);
|
|
484
567
|
}
|
|
568
|
+
const type = options.type || this.detectType();
|
|
485
569
|
const meta = {
|
|
486
570
|
appId: options.appId,
|
|
487
|
-
type
|
|
571
|
+
type,
|
|
488
572
|
};
|
|
489
573
|
const cleanups = [];
|
|
490
574
|
// Auto-subscribe handlers
|
|
491
575
|
Object.entries(options.handlers).forEach(([event, handler]) => {
|
|
492
576
|
if (!handler)
|
|
493
577
|
return;
|
|
494
|
-
if (
|
|
495
|
-
// iframe:
|
|
496
|
-
const
|
|
578
|
+
if (type === 'iframe') {
|
|
579
|
+
// iframe: Shell 经 postMessage 下行 → 监听 message 事件
|
|
580
|
+
const msgWrapper = (e) => {
|
|
497
581
|
if (e.data?.scope === 'mfe' && e.data?.event === event) {
|
|
498
582
|
handler(e.data.data);
|
|
499
583
|
}
|
|
500
584
|
};
|
|
501
|
-
window.addEventListener('message',
|
|
502
|
-
cleanups.push(() => window.removeEventListener('message',
|
|
585
|
+
window.addEventListener('message', msgWrapper);
|
|
586
|
+
cleanups.push(() => window.removeEventListener('message', msgWrapper));
|
|
587
|
+
// Auto-bridge: Shell postMessage → same-tab CustomEvent
|
|
588
|
+
const ceWrapper = (e) => handler(e.detail);
|
|
589
|
+
window.addEventListener(event, ceWrapper);
|
|
590
|
+
cleanups.push(() => window.removeEventListener(event, ceWrapper));
|
|
503
591
|
}
|
|
504
592
|
else {
|
|
505
593
|
// MF/NF: listen via window.CustomEvent
|
|
@@ -514,25 +602,12 @@ class MfeEventPlugin {
|
|
|
514
602
|
* Register an iframe Remote App (Shell side)
|
|
515
603
|
*/
|
|
516
604
|
registerIframe(appId, iframe, targetOrigin) {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
meta: { appId, type: 'iframe', transport },
|
|
523
|
-
options: { appId, type: 'iframe', handlers: {} },
|
|
524
|
-
cleanups: [],
|
|
525
|
-
});
|
|
526
|
-
})
|
|
527
|
-
.catch((err) => {
|
|
528
|
-
MfeEventPlugin._logError('registerIframe', 'failed to load transport for', `"${appId}": ${err}`);
|
|
605
|
+
const transport = new IframeProxyTransport(iframe, targetOrigin);
|
|
606
|
+
this.registry.set(appId, {
|
|
607
|
+
meta: { appId, type: 'iframe', transport },
|
|
608
|
+
options: { appId, type: 'iframe', handlers: {} },
|
|
609
|
+
cleanups: [],
|
|
529
610
|
});
|
|
530
|
-
// Timeout protection — if import hangs, registry stays clean and caller knows nothing
|
|
531
|
-
setTimeout(() => {
|
|
532
|
-
if (!this.registry.has(appId)) {
|
|
533
|
-
MfeEventPlugin._logError('registerIframe', 'timeout', `${TIMEOUT_MS}ms for "${appId}"`);
|
|
534
|
-
}
|
|
535
|
-
}, TIMEOUT_MS);
|
|
536
611
|
}
|
|
537
612
|
/**
|
|
538
613
|
* Unregister a Remote App
|
|
@@ -541,47 +616,84 @@ class MfeEventPlugin {
|
|
|
541
616
|
const entry = this.registry.get(appId);
|
|
542
617
|
if (entry) {
|
|
543
618
|
entry.cleanups.forEach((fn) => fn());
|
|
619
|
+
entry.meta.transport?.destroy?.();
|
|
544
620
|
}
|
|
545
621
|
this.registry.delete(appId);
|
|
546
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Unregister all Remote Apps
|
|
625
|
+
*/
|
|
626
|
+
unregisterAll() {
|
|
627
|
+
this.registry.forEach((entry) => {
|
|
628
|
+
entry.cleanups.forEach((fn) => fn());
|
|
629
|
+
entry.meta.transport?.destroy?.();
|
|
630
|
+
});
|
|
631
|
+
this.registry.clear();
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Check if an app is registered
|
|
635
|
+
*/
|
|
636
|
+
has(appId) {
|
|
637
|
+
return this.registry.has(appId);
|
|
638
|
+
}
|
|
547
639
|
// ─────────────────────────────────────────
|
|
548
640
|
// Shell Targeted Communication
|
|
549
641
|
// ─────────────────────────────────────────
|
|
550
642
|
/**
|
|
551
643
|
* Send event to a specific Remote App
|
|
644
|
+
*
|
|
645
|
+
* 3-tier fallback:
|
|
646
|
+
* 1. Registry hit → send directly
|
|
647
|
+
* 2. DOM scan → find iframe[data-mfe-app="appId"], create transport on-the-fly
|
|
648
|
+
* 3. Fallback → broadcast with appId for self-filtering
|
|
552
649
|
*/
|
|
553
650
|
emitTo(appId, event, data) {
|
|
651
|
+
// 1. Registry hit
|
|
554
652
|
const app = this.registry.get(appId);
|
|
555
|
-
if (
|
|
556
|
-
|
|
653
|
+
if (app) {
|
|
654
|
+
if (app.meta.type === 'iframe' && app.meta.transport) {
|
|
655
|
+
app.meta.transport.emit(event, data);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
const handler = app.options.handlers[event];
|
|
659
|
+
if (handler) {
|
|
660
|
+
handler(data);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
557
663
|
return;
|
|
558
664
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
665
|
+
// 2. DOM scan: iframe[data-mfe-app="appId"]
|
|
666
|
+
const iframe = document.querySelector(`iframe[data-mfe-app="${appId}"]`);
|
|
667
|
+
if (iframe) {
|
|
668
|
+
const transport = new IframeProxyTransport(iframe);
|
|
669
|
+
this.registry.set(appId, {
|
|
670
|
+
meta: { appId, type: 'iframe', transport },
|
|
671
|
+
options: { appId, type: 'iframe', handlers: {} },
|
|
672
|
+
cleanups: [],
|
|
673
|
+
});
|
|
674
|
+
transport.emit(event, data);
|
|
675
|
+
return;
|
|
569
676
|
}
|
|
677
|
+
// 3. Fallback: broadcast with appId for self-filtering
|
|
678
|
+
const fallbackData = { ...data };
|
|
679
|
+
fallbackData.appId = appId;
|
|
680
|
+
this.emit(event, fallbackData);
|
|
570
681
|
}
|
|
571
682
|
// ─────────────────────────────────────────
|
|
572
683
|
// Shell Broadcast Communication
|
|
573
684
|
// ─────────────────────────────────────────
|
|
574
685
|
/**
|
|
575
686
|
* Broadcast event to all Remote Apps (MF + iframe)
|
|
687
|
+
*
|
|
688
|
+
* - MF/NF: window CustomEvent
|
|
689
|
+
* - iframe: postMessage to every iframe with data-mfe-app attribute
|
|
576
690
|
*/
|
|
577
691
|
emit(event, data) {
|
|
578
|
-
// Broadcast to all MF Remote Apps
|
|
692
|
+
// Broadcast to all MF/NF Remote Apps via CustomEvent
|
|
579
693
|
window.dispatchEvent(new CustomEvent(event, { detail: data }));
|
|
580
|
-
// Broadcast to all
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
meta.transport.emit(event, data);
|
|
584
|
-
}
|
|
694
|
+
// Broadcast to all iframes with data-mfe-app attribute
|
|
695
|
+
document.querySelectorAll('iframe[data-mfe-app]').forEach((iframe) => {
|
|
696
|
+
iframe.contentWindow?.postMessage({ scope: 'mfe', event, data, ts: Date.now() }, '*');
|
|
585
697
|
});
|
|
586
698
|
}
|
|
587
699
|
// ─────────────────────────────────────────
|
|
@@ -613,11 +725,6 @@ class MfeEventPlugin {
|
|
|
613
725
|
}
|
|
614
726
|
/**
|
|
615
727
|
* Build the closer function for a notification
|
|
616
|
-
*
|
|
617
|
-
* Render path determines closer behavior:
|
|
618
|
-
* - _customRenderer (Shell): dispatch NotifyClose + customCloser
|
|
619
|
-
* - appWithFallback: dispatch NotifyClose + fallback.close()
|
|
620
|
-
* - DOM fallback: _removeToast (which dispatches NotifyClose itself)
|
|
621
728
|
*/
|
|
622
729
|
_buildCloser(id, renderPath, fallbackCloser) {
|
|
623
730
|
return () => {
|
|
@@ -637,7 +744,6 @@ class MfeEventPlugin {
|
|
|
637
744
|
fallbackCloser?.close();
|
|
638
745
|
break;
|
|
639
746
|
case 'dom':
|
|
640
|
-
// _removeToast dispatches NotifyClose itself (symmetric with DOM-driven closes)
|
|
641
747
|
this._removeToast(id);
|
|
642
748
|
break;
|
|
643
749
|
}
|
|
@@ -648,7 +754,6 @@ class MfeEventPlugin {
|
|
|
648
754
|
* Show a notification
|
|
649
755
|
*
|
|
650
756
|
* Flow: dispatch NotifyShow → render (custom > fallback > DOM) → register closer
|
|
651
|
-
* The closer is registered exactly once per notification, no overwrite.
|
|
652
757
|
*/
|
|
653
758
|
notify(config) {
|
|
654
759
|
if (typeof window === 'undefined')
|
|
@@ -668,19 +773,16 @@ class MfeEventPlugin {
|
|
|
668
773
|
// 2. Render + register closer (exactly once per path)
|
|
669
774
|
let closer;
|
|
670
775
|
if (this._customRenderer) {
|
|
671
|
-
// Shell path: custom renderer handles display
|
|
672
776
|
this._customRenderer(finalConfig);
|
|
673
777
|
closer = this._buildCloser(id, 'custom');
|
|
674
778
|
}
|
|
675
779
|
else {
|
|
676
|
-
// Check if any registered app provided a fallback renderer
|
|
677
780
|
const appWithFallback = [...this.registry.values()].find((app) => app.options.fallback);
|
|
678
781
|
if (appWithFallback) {
|
|
679
782
|
const fallbackCloser = appWithFallback.options.fallback(finalConfig);
|
|
680
783
|
closer = this._buildCloser(id, 'fallback', fallbackCloser);
|
|
681
784
|
}
|
|
682
785
|
else {
|
|
683
|
-
// DOM fallback
|
|
684
786
|
this._renderToast(finalConfig);
|
|
685
787
|
closer = this._buildCloser(id, 'dom');
|
|
686
788
|
}
|
|
@@ -728,21 +830,15 @@ class MfeEventPlugin {
|
|
|
728
830
|
}
|
|
729
831
|
/**
|
|
730
832
|
* Close all notifications
|
|
731
|
-
*
|
|
732
|
-
* Snapshot closers before iteration to prevent re-entrancy:
|
|
733
|
-
* a closer may dispatch new events that add to _pendingClosers.
|
|
734
833
|
*/
|
|
735
834
|
closeAll() {
|
|
736
|
-
// Snapshot — closers may trigger new notify() calls
|
|
737
835
|
const closers = [...this._pendingClosers.values()];
|
|
738
836
|
this._pendingClosers.clear();
|
|
739
837
|
closers.forEach((closer) => closer());
|
|
740
|
-
// Dispatch close-all event
|
|
741
838
|
window.dispatchEvent(new CustomEvent(EvtMfe.NotifyCloseAll, {
|
|
742
839
|
detail: {},
|
|
743
840
|
bubbles: true,
|
|
744
841
|
}));
|
|
745
|
-
// Clear DOM toasts
|
|
746
842
|
if (this._customClear) {
|
|
747
843
|
this._customClear();
|
|
748
844
|
}
|
|
@@ -858,16 +954,11 @@ const shlBus = new Bus(new MemoryTransport());
|
|
|
858
954
|
*/
|
|
859
955
|
function createMfeBus() {
|
|
860
956
|
if (typeof window === 'undefined') {
|
|
861
|
-
// SSR fallback — use MemoryTransport
|
|
862
957
|
return new Bus(new MemoryTransport());
|
|
863
958
|
}
|
|
864
959
|
if (window.self !== window.top) {
|
|
865
|
-
// iframe environment → send to parent via postMessage
|
|
866
960
|
return new Bus(new PostMessageTransport());
|
|
867
961
|
}
|
|
868
|
-
// Shell/MF/NF environment → use window.CustomEvent
|
|
869
|
-
// Federation modules may not share the same instance,
|
|
870
|
-
// so window is used as the shared channel fallback
|
|
871
962
|
return new Bus(new WindowTransport());
|
|
872
963
|
}
|
|
873
964
|
const mfeBus = createMfeBus();
|
|
@@ -881,7 +972,9 @@ if (typeof window !== 'undefined') {
|
|
|
881
972
|
}
|
|
882
973
|
}
|
|
883
974
|
|
|
975
|
+
exports.Breakpoint = Breakpoint;
|
|
884
976
|
exports.Bus = Bus;
|
|
977
|
+
exports.DeviceType = DeviceType;
|
|
885
978
|
exports.EvtApp = EvtApp;
|
|
886
979
|
exports.EvtMfe = EvtMfe;
|
|
887
980
|
exports.EvtShl = EvtShl;
|
|
@@ -889,6 +982,7 @@ exports.IframeProxyTransport = IframeProxyTransport;
|
|
|
889
982
|
exports.MemoryTransport = MemoryTransport;
|
|
890
983
|
exports.PostMessageTransport = PostMessageTransport;
|
|
891
984
|
exports.WindowTransport = WindowTransport;
|
|
985
|
+
exports.breakpointToDevice = breakpointToDevice;
|
|
892
986
|
exports.default = mfeEventPlugin;
|
|
893
987
|
exports.mfeBus = mfeBus;
|
|
894
988
|
exports.mfeEventPlugin = mfeEventPlugin;
|