@hhfenpm/micro-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # @hhfenpm/micro-app
2
+
3
+ 微前端通信桥接和状态同步工具,支持父子应用之间的通信、状态同步和生命周期管理。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @hhfenpm/micro-app
9
+ # 或
10
+ yarn add @hhfenpm/micro-app
11
+ ```
12
+
13
+ ## 功能特性
14
+
15
+ - 🔗 **通信桥接**:支持父子应用之间的方法调用和通信
16
+ - 🔄 **状态同步**:自动同步 Vuex store 状态
17
+ - 🎯 **生命周期管理**:支持微前端生命周期(mount、update、unmount)
18
+ - 🛣️ **路由同步**:自动同步子应用路由到父应用
19
+ - ⚙️ **可配置**:支持自定义处理器、状态映射等
20
+
21
+ ## 使用方式
22
+
23
+ ### 1. 初始化通信桥接
24
+
25
+ #### 父应用
26
+
27
+ ```javascript
28
+ import { initBridge } from '@hhfenpm/micro-app'
29
+ import Vue from 'vue'
30
+
31
+ const vm = new Vue({ /* ... */ })
32
+
33
+ // 创建处理器(项目特定)
34
+ const handlers = {
35
+ elementUI: {
36
+ $message: (...args) => vm.$message(...args),
37
+ // ... 其他 Element UI 方法
38
+ },
39
+ electron: {
40
+ // ... Electron 方法
41
+ },
42
+ }
43
+
44
+ // 初始化桥接
45
+ initBridge({
46
+ isParent: true,
47
+ vm,
48
+ handlers,
49
+ iframeSelector: '#microApp', // 可选,默认 '#microApp'
50
+ })
51
+ ```
52
+
53
+ #### 子应用
54
+
55
+ ```javascript
56
+ import { initBridge } from '@hhfenpm/micro-app'
57
+ import Vue from 'vue'
58
+
59
+ const vm = new Vue({ /* ... */ })
60
+
61
+ // 初始化桥接
62
+ initBridge({
63
+ isParent: false,
64
+ vm,
65
+ })
66
+
67
+ // 使用 $base 调用父应用方法
68
+ vm.$base.elementUI.$message('Hello from child app')
69
+ vm.$base.electron.show()
70
+ ```
71
+
72
+ ### 2. 使用核心功能
73
+
74
+ ```javascript
75
+ import { createMicroAppCore } from '@hhfenpm/micro-app'
76
+
77
+ // 定义模块路由映射
78
+ const ModelMap = () => ({
79
+ 'module1': ['/module1', '/m1'],
80
+ 'module2': ['/module2'],
81
+ })
82
+
83
+ // 创建核心功能实例
84
+ const core = createMicroAppCore({
85
+ modelMap: ModelMap,
86
+ enabledModules: () => window.GLOBAL_CONFIG?.microApp || null,
87
+ })
88
+
89
+ // 使用
90
+ const module = core.microAppModule('/module1')
91
+ const src = core.microAppSrc('/module1')
92
+ const deployed = await core.microAppDeployed('module1')
93
+ ```
94
+
95
+ ### 3. 使用状态同步插件
96
+
97
+ ```javascript
98
+ import Vuex from 'vuex'
99
+ import { baseSyncPlugin } from '@hhfenpm/micro-app'
100
+
101
+ // 创建 Vuex Store
102
+ const store = new Vuex.Store({
103
+ modules: {
104
+ base: {
105
+ namespaced: true,
106
+ state: {
107
+ patient_id: null,
108
+ },
109
+ mutations: {
110
+ SYNC_STATE(state, payload) {
111
+ Object.assign(state, payload)
112
+ },
113
+ },
114
+ },
115
+ },
116
+ plugins: [
117
+ baseSyncPlugin({
118
+ isParent: false, // 子应用设为 false
119
+ iframeSelector: '#microApp',
120
+ }),
121
+ ],
122
+ })
123
+
124
+ // 子应用附加路由同步
125
+ import router from './router'
126
+ store.attachRouterSync(router)
127
+ ```
128
+
129
+ ### 4. 生命周期管理
130
+
131
+ #### 子应用
132
+
133
+ ```javascript
134
+ // 注册生命周期钩子
135
+ window.__MICRO_APP_LIFECYCLE__ = {
136
+ mount(payload) {
137
+ console.log('微前端已挂载', payload)
138
+ },
139
+ update(payload) {
140
+ console.log('微前端已更新', payload)
141
+ },
142
+ unmount(payload) {
143
+ console.log('微前端已卸载', payload)
144
+ },
145
+ }
146
+ ```
147
+
148
+ #### 父应用
149
+
150
+ ```javascript
151
+ // 调用生命周期
152
+ store.callMicroAppLifeCycle('mount', { /* payload */ })
153
+ store.callMicroAppLifeCycle('update', { /* payload */ })
154
+ store.callMicroAppLifeCycle('unmount', { /* payload */ })
155
+ ```
156
+
157
+ ## API 文档
158
+
159
+ ### Bridge
160
+
161
+ #### `initBridge(options)`
162
+
163
+ 初始化通信桥接。
164
+
165
+ **参数:**
166
+ - `options.isParent` (boolean): 是否为父应用
167
+ - `options.vm` (Object): Vue 实例
168
+ - `options.iframeSelector` (string): iframe 选择器,默认 `'#microApp'`
169
+ - `options.handlers` (Object): 处理器对象(父应用需要)
170
+
171
+ ### Core
172
+
173
+ #### `createMicroAppCore(options)`
174
+
175
+ 创建微前端核心功能实例。
176
+
177
+ **参数:**
178
+ - `options.modelMap` (Function): 模块路由映射函数,返回 `{ [moduleName]: string[] }`
179
+ - `options.enabledModules` (Array|Function): 启用的模块列表或获取模块列表的函数
180
+
181
+ **返回:**
182
+ - `microAppModule(path)`: 根据路径获取模块名
183
+ - `microAppSrc(path)`: 生成完整 URL
184
+ - `microAppDeployed(module)`: 检测模块是否已部署
185
+
186
+ ### Store
187
+
188
+ #### `baseSyncPlugin(options)`
189
+
190
+ 创建状态同步插件。
191
+
192
+ **参数:**
193
+ - `options.isParent` (boolean): 是否为父应用
194
+ - `options.iframeSelector` (string): iframe 选择器
195
+
196
+ ## 浏览器兼容性
197
+
198
+ - 现代浏览器(Chrome, Firefox, Safari, Edge)
199
+ - 需要支持 `postMessage` API
200
+ - 需要支持 `Proxy` API(IE 11 不支持)
201
+
202
+ ## 注意事项
203
+
204
+ 1. **安全性**:消息通信会检查 `origin`,确保只在同源窗口间通信
205
+ 2. **状态同步**:状态同步使用深拷贝,避免引用污染
206
+ 3. **生命周期**:子应用需要注册 `window.__MICRO_APP_LIFECYCLE__` 对象
207
+ 4. **路由同步**:子应用需要调用 `store.attachRouterSync(router)` 附加路由同步
208
+
209
+ ## 许可证
210
+
211
+ MIT
@@ -0,0 +1,416 @@
1
+ /**
2
+ * 微前端通信桥接
3
+ * 支持父子应用之间的方法调用和通信
4
+ */
5
+
6
+ const BRIDGE$1 = {
7
+ INVOKE: 'bridge-invoke',
8
+ CALLBACK: 'bridge-callback',
9
+ };
10
+
11
+ const post = (target, message) =>
12
+ target?.postMessage(message, window.location.origin);
13
+
14
+ const getIframeWindow = selector =>
15
+ document.querySelector(selector)?.contentWindow;
16
+
17
+ const genId = () => `${Date.now()}_${Math.random().toString(16).slice(2)}`;
18
+
19
+ const safeClone = value => {
20
+ try {
21
+ return JSON.parse(JSON.stringify(value))
22
+ } catch {
23
+ return String(value)
24
+ }
25
+ };
26
+
27
+ /**
28
+ * 初始化通信桥接
29
+ * @param {Object} options - 配置选项
30
+ * @param {boolean} options.isParent - 是否为父应用,默认 false
31
+ * @param {Object} options.vm - Vue 实例(可选)
32
+ * @param {string} options.iframeSelector - iframe 选择器,默认 '#microApp'
33
+ * @param {Object} options.handlers - 处理器对象(父应用需要提供)
34
+ * @returns {void}
35
+ */
36
+ const initBridge = ({
37
+ isParent = false,
38
+ vm,
39
+ iframeSelector = '#microApp',
40
+ handlers = {},
41
+ } = {}) => {
42
+ // 父应用使用传入的 handlers,子应用为空对象
43
+ const finalHandlers = isParent ? handlers : Object.create(null);
44
+
45
+ const pending = Object.create(null);
46
+
47
+ // 子应用:创建 $base 代理对象,用于调用父应用方法
48
+ if (!isParent && vm) {
49
+ vm.$base = new Proxy(
50
+ {},
51
+ {
52
+ get(_, namespace) {
53
+ return new Proxy(
54
+ {},
55
+ {
56
+ get(_, method) {
57
+ return params =>
58
+ new Promise(resolve => {
59
+ const callbackId = genId();
60
+ pending[callbackId] = resolve;
61
+
62
+ post(window.parent, {
63
+ type: BRIDGE$1.INVOKE,
64
+ action: `${namespace}.${method}`,
65
+ params,
66
+ callbackId,
67
+ });
68
+ })
69
+ },
70
+ }
71
+ )
72
+ },
73
+ }
74
+ );
75
+
76
+ vm.$baseReady = true;
77
+ }
78
+
79
+ // 处理来自子应用的调用请求(父应用)
80
+ const handleInvoke = e => {
81
+ const { action, params, callbackId } = e.data || {};
82
+ if (!action || !callbackId) return
83
+
84
+ const [namespace, method] = action.split('.');
85
+ const fn = finalHandlers?.[namespace]?.[method];
86
+ if (typeof fn !== 'function') return
87
+
88
+ const iframeWindow = getIframeWindow(iframeSelector);
89
+ if (!iframeWindow) return
90
+
91
+ try {
92
+ const result = fn(params);
93
+ const done = payload =>
94
+ post(iframeWindow, {
95
+ type: BRIDGE$1.CALLBACK,
96
+ callbackId,
97
+ result: safeClone(payload),
98
+ });
99
+
100
+ result?.then ? result.then(done).catch(done) : done(result);
101
+ } catch (err) {
102
+ post(iframeWindow, {
103
+ type: BRIDGE$1.CALLBACK,
104
+ callbackId,
105
+ result: safeClone(err?.message || err),
106
+ });
107
+ }
108
+ };
109
+
110
+ // 处理来自父应用的响应(子应用)
111
+ const handleCallback = e => {
112
+ const { callbackId, result } = e.data || {};
113
+ if (!callbackId || !pending[callbackId]) return
114
+
115
+ pending[callbackId](result);
116
+ delete pending[callbackId];
117
+ };
118
+
119
+ // 监听消息
120
+ window.addEventListener('message', e => {
121
+ if (e.origin !== window.location.origin) return
122
+
123
+ const { type } = e.data || {};
124
+ if (isParent && type === BRIDGE$1.INVOKE) handleInvoke(e);
125
+ if (!isParent && type === BRIDGE$1.CALLBACK) handleCallback(e);
126
+ });
127
+ };
128
+
129
+ /**
130
+ * 微前端核心功能
131
+ * 提供模块路由映射、URL 生成、部署检测等功能
132
+ */
133
+
134
+ /**
135
+ * 创建微前端核心功能
136
+ * @param {Object} options - 配置选项
137
+ * @param {Function} options.modelMap - 模块路由映射函数,返回 { [moduleName]: string[] }
138
+ * @param {Array<string>|Function} options.enabledModules - 启用的模块列表或获取模块列表的函数
139
+ * @returns {Object} 核心功能对象
140
+ */
141
+ function createMicroAppCore({
142
+ modelMap,
143
+ enabledModules,
144
+ } = {}) {
145
+ if (!modelMap || typeof modelMap !== 'function') {
146
+ throw new Error('modelMap function is required')
147
+ }
148
+
149
+ const MAP = modelMap();
150
+
151
+ /**
152
+ * 获取启用的模块列表
153
+ * @returns {Array<string>|null}
154
+ */
155
+ const getEnabledModules = () => {
156
+ if (Array.isArray(enabledModules)) {
157
+ return enabledModules
158
+ }
159
+ if (typeof enabledModules === 'function') {
160
+ return enabledModules()
161
+ }
162
+ // 默认从 window.GLOBAL_CONFIG.microApp 获取
163
+ if (typeof window !== 'undefined' && window.GLOBAL_CONFIG) {
164
+ return window.GLOBAL_CONFIG.microApp || null
165
+ }
166
+ return null
167
+ };
168
+
169
+ /**
170
+ * 根据路径获取对应的微前端模块名
171
+ * @param {string} path - 路由路径
172
+ * @returns {string|null} 模块名,如果不在任何模块中则返回 null
173
+ */
174
+ const microAppModule = path => {
175
+ const value = path.split('?')[0];
176
+ const key = Object.keys(MAP).find(key => MAP[key].includes(value));
177
+ const DATA = getEnabledModules();
178
+ return DATA?.includes(key) ? key : null
179
+ };
180
+
181
+ /**
182
+ * 生成微前端应用的完整 URL
183
+ * @param {string} path - 路由路径
184
+ * @returns {string} 完整的 URL
185
+ */
186
+ const microAppSrc = path => {
187
+ const module = microAppModule(path);
188
+ const base = window.location.href.split('#')[0];
189
+ return `${base}${module ? `${module}/` : ''}#${path}`
190
+ };
191
+
192
+ /**
193
+ * 检测微前端模块是否已部署
194
+ * @param {string} module - 模块名
195
+ * @returns {Promise<boolean>} 是否已部署
196
+ */
197
+ const microAppDeployed = async module => {
198
+ try {
199
+ const result = await fetch(`/${module}`, {
200
+ method: 'GET',
201
+ cache: 'no-store',
202
+ });
203
+ return result.ok
204
+ } catch (error) {
205
+ return false
206
+ }
207
+ };
208
+
209
+ return {
210
+ microAppModule,
211
+ microAppSrc,
212
+ microAppDeployed,
213
+ }
214
+ }
215
+
216
+ /**
217
+ * 创建默认的微前端核心功能实例
218
+ * 使用 window.GLOBAL_CONFIG.microApp 作为配置源
219
+ * @param {Function} modelMap - 模块路由映射函数
220
+ * @returns {Object} 核心功能对象
221
+ */
222
+ function createDefaultMicroAppCore(modelMap) {
223
+ return createMicroAppCore({
224
+ modelMap,
225
+ })
226
+ }
227
+
228
+ /**
229
+ * Vuex 状态同步插件
230
+ * 用于在父子应用之间同步 Vuex store 状态
231
+ */
232
+
233
+ const MESSAGE = {
234
+ SYNC_FROM_CHILD: 'sync-base-from-child',
235
+ SYNC_FROM_PARENT: 'sync-base-from-parent',
236
+ REQUEST_FROM_CHILD: 'request-base-from-child',
237
+ CHILD_ROUTE_CHANGE: 'child-route-change',
238
+ };
239
+
240
+ const LIFE_CYCLE = {
241
+ MOUNT: 'mount',
242
+ UPDATE: 'update',
243
+ UNMOUNT: 'unmount',
244
+ };
245
+
246
+ const BRIDGE = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
247
+
248
+ const LIFE_CYCLE_TYPES = Object.values(LIFE_CYCLE);
249
+ const BRIDGE_TYPES = Object.values(BRIDGE);
250
+
251
+ const getIframe = selector => document.querySelector(selector);
252
+
253
+ const postToChild = (iframeSelector, message) => {
254
+ const iframe = getIframe(iframeSelector);
255
+ iframe?.contentWindow?.postMessage(message, window.location.origin);
256
+ };
257
+
258
+ const postToParent = message => {
259
+ window.parent?.postMessage(message, window.location.origin);
260
+ };
261
+
262
+ const isLifeCycleMessage = type => LIFE_CYCLE_TYPES.includes(type);
263
+
264
+ const isBridgeMessage = type => BRIDGE_TYPES.includes(type);
265
+
266
+ const isInternalMessage = type =>
267
+ [
268
+ MESSAGE.SYNC_FROM_CHILD,
269
+ MESSAGE.REQUEST_FROM_CHILD,
270
+ MESSAGE.CHILD_ROUTE_CHANGE,
271
+ ].includes(type);
272
+
273
+ const syncRouteFromChild = fullPath => {
274
+ if (typeof fullPath !== 'string') return
275
+ const { origin, pathname } = window.location;
276
+ const base =
277
+ pathname === '/' || pathname === ''
278
+ ? origin
279
+ : (origin + pathname).replace(/\/$/, '');
280
+ const target = `${base}/#${fullPath}`;
281
+ if (window.location.href !== target) {
282
+ window.location.href = target;
283
+ }
284
+ };
285
+
286
+ /**
287
+ * 创建状态同步插件
288
+ * @param {Object} options - 配置选项
289
+ * @param {boolean} options.isParent - 是否为父应用,默认 false
290
+ * @param {string} options.iframeSelector - iframe 选择器,默认 '#microApp'
291
+ * @returns {Function} Vuex 插件函数
292
+ */
293
+ function baseSyncPlugin({
294
+ isParent = false,
295
+ iframeSelector = '#microApp',
296
+ } = {}) {
297
+ return store => {
298
+ let latestBaseState = store.state.base;
299
+
300
+ // 监听 base 状态变化,自动同步
301
+ store.watch(
302
+ state => state.base,
303
+ newBase => {
304
+ latestBaseState = newBase;
305
+ if (isParent) {
306
+ postToChild(iframeSelector, {
307
+ type: MESSAGE.SYNC_FROM_PARENT,
308
+ payload: latestBaseState,
309
+ });
310
+ } else {
311
+ postToParent({
312
+ type: MESSAGE.SYNC_FROM_CHILD,
313
+ payload: latestBaseState,
314
+ });
315
+ }
316
+ },
317
+ { deep: true }
318
+ );
319
+
320
+ // 附加路由同步功能(子应用使用)
321
+ store.attachRouterSync = router => {
322
+ if (!router || isParent) return
323
+ router.afterEach(to => {
324
+ const parentHref = window.parent?.location?.href || '';
325
+ const parentFullPath = parentHref.includes('#')
326
+ ? parentHref.split('#')[1]
327
+ : '';
328
+ const childFullPath = to.fullPath.startsWith('/')
329
+ ? to.fullPath
330
+ : '/' + to.fullPath;
331
+ if (parentFullPath !== childFullPath) {
332
+ postToParent({
333
+ type: MESSAGE.CHILD_ROUTE_CHANGE,
334
+ fullPath: to.fullPath,
335
+ });
336
+ }
337
+ });
338
+ };
339
+
340
+ // 调用微前端生命周期(父应用使用)
341
+ store.callMicroAppLifeCycle = (type, payload) => {
342
+ if (!isParent) return
343
+ if (!isLifeCycleMessage(type)) return
344
+
345
+ postToChild(iframeSelector, {
346
+ type,
347
+ payload,
348
+ });
349
+ };
350
+
351
+ // 处理消息
352
+ const handleMessage = event => {
353
+ if (event.origin !== window.location.origin) return
354
+ const { type, payload, fullPath } = event.data || {};
355
+
356
+ // 子应用处理生命周期消息
357
+ if (!isParent && isLifeCycleMessage(type)) {
358
+ window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
359
+ return
360
+ }
361
+
362
+ // 父应用处理来自子应用的状态同步
363
+ if (isParent && type === MESSAGE.SYNC_FROM_CHILD) {
364
+ store.commit('base/SYNC_STATE', payload);
365
+ return
366
+ }
367
+
368
+ // 子应用处理来自父应用的状态同步
369
+ if (!isParent && type === MESSAGE.SYNC_FROM_PARENT) {
370
+ store.commit('base/SYNC_STATE', payload);
371
+ return
372
+ }
373
+
374
+ // 父应用响应子应用的状态请求
375
+ if (isParent && type === MESSAGE.REQUEST_FROM_CHILD) {
376
+ postToChild(iframeSelector, {
377
+ type: MESSAGE.SYNC_FROM_PARENT,
378
+ payload: latestBaseState,
379
+ });
380
+ return
381
+ }
382
+
383
+ // 父应用处理子应用的路由变化
384
+ if (isParent && type === MESSAGE.CHILD_ROUTE_CHANGE) {
385
+ syncRouteFromChild(fullPath);
386
+ return
387
+ }
388
+
389
+ // 父应用转发其他消息到父窗口
390
+ if (isParent && !isInternalMessage(type) && !isBridgeMessage(type)) {
391
+ window.parent?.postMessage(event.data || {}, '*');
392
+ }
393
+ };
394
+
395
+ window.addEventListener('message', handleMessage);
396
+
397
+ // 子应用初始化时请求父应用状态
398
+ if (!isParent && window.parent) {
399
+ postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
400
+ }
401
+ }
402
+ }
403
+
404
+ /**
405
+ * @hhfenpm/micro-app
406
+ * 微前端通信桥接和状态同步工具
407
+ */
408
+
409
+
410
+ var index = {
411
+ initBridge,
412
+ createDefaultMicroAppCore,
413
+ baseSyncPlugin,
414
+ };
415
+
416
+ export { baseSyncPlugin, createDefaultMicroAppCore, createMicroAppCore, index as default, initBridge };
package/dist/index.js ADDED
@@ -0,0 +1,424 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /**
6
+ * 微前端通信桥接
7
+ * 支持父子应用之间的方法调用和通信
8
+ */
9
+
10
+ const BRIDGE$1 = {
11
+ INVOKE: 'bridge-invoke',
12
+ CALLBACK: 'bridge-callback',
13
+ };
14
+
15
+ const post = (target, message) =>
16
+ target?.postMessage(message, window.location.origin);
17
+
18
+ const getIframeWindow = selector =>
19
+ document.querySelector(selector)?.contentWindow;
20
+
21
+ const genId = () => `${Date.now()}_${Math.random().toString(16).slice(2)}`;
22
+
23
+ const safeClone = value => {
24
+ try {
25
+ return JSON.parse(JSON.stringify(value))
26
+ } catch {
27
+ return String(value)
28
+ }
29
+ };
30
+
31
+ /**
32
+ * 初始化通信桥接
33
+ * @param {Object} options - 配置选项
34
+ * @param {boolean} options.isParent - 是否为父应用,默认 false
35
+ * @param {Object} options.vm - Vue 实例(可选)
36
+ * @param {string} options.iframeSelector - iframe 选择器,默认 '#microApp'
37
+ * @param {Object} options.handlers - 处理器对象(父应用需要提供)
38
+ * @returns {void}
39
+ */
40
+ const initBridge = ({
41
+ isParent = false,
42
+ vm,
43
+ iframeSelector = '#microApp',
44
+ handlers = {},
45
+ } = {}) => {
46
+ // 父应用使用传入的 handlers,子应用为空对象
47
+ const finalHandlers = isParent ? handlers : Object.create(null);
48
+
49
+ const pending = Object.create(null);
50
+
51
+ // 子应用:创建 $base 代理对象,用于调用父应用方法
52
+ if (!isParent && vm) {
53
+ vm.$base = new Proxy(
54
+ {},
55
+ {
56
+ get(_, namespace) {
57
+ return new Proxy(
58
+ {},
59
+ {
60
+ get(_, method) {
61
+ return params =>
62
+ new Promise(resolve => {
63
+ const callbackId = genId();
64
+ pending[callbackId] = resolve;
65
+
66
+ post(window.parent, {
67
+ type: BRIDGE$1.INVOKE,
68
+ action: `${namespace}.${method}`,
69
+ params,
70
+ callbackId,
71
+ });
72
+ })
73
+ },
74
+ }
75
+ )
76
+ },
77
+ }
78
+ );
79
+
80
+ vm.$baseReady = true;
81
+ }
82
+
83
+ // 处理来自子应用的调用请求(父应用)
84
+ const handleInvoke = e => {
85
+ const { action, params, callbackId } = e.data || {};
86
+ if (!action || !callbackId) return
87
+
88
+ const [namespace, method] = action.split('.');
89
+ const fn = finalHandlers?.[namespace]?.[method];
90
+ if (typeof fn !== 'function') return
91
+
92
+ const iframeWindow = getIframeWindow(iframeSelector);
93
+ if (!iframeWindow) return
94
+
95
+ try {
96
+ const result = fn(params);
97
+ const done = payload =>
98
+ post(iframeWindow, {
99
+ type: BRIDGE$1.CALLBACK,
100
+ callbackId,
101
+ result: safeClone(payload),
102
+ });
103
+
104
+ result?.then ? result.then(done).catch(done) : done(result);
105
+ } catch (err) {
106
+ post(iframeWindow, {
107
+ type: BRIDGE$1.CALLBACK,
108
+ callbackId,
109
+ result: safeClone(err?.message || err),
110
+ });
111
+ }
112
+ };
113
+
114
+ // 处理来自父应用的响应(子应用)
115
+ const handleCallback = e => {
116
+ const { callbackId, result } = e.data || {};
117
+ if (!callbackId || !pending[callbackId]) return
118
+
119
+ pending[callbackId](result);
120
+ delete pending[callbackId];
121
+ };
122
+
123
+ // 监听消息
124
+ window.addEventListener('message', e => {
125
+ if (e.origin !== window.location.origin) return
126
+
127
+ const { type } = e.data || {};
128
+ if (isParent && type === BRIDGE$1.INVOKE) handleInvoke(e);
129
+ if (!isParent && type === BRIDGE$1.CALLBACK) handleCallback(e);
130
+ });
131
+ };
132
+
133
+ /**
134
+ * 微前端核心功能
135
+ * 提供模块路由映射、URL 生成、部署检测等功能
136
+ */
137
+
138
+ /**
139
+ * 创建微前端核心功能
140
+ * @param {Object} options - 配置选项
141
+ * @param {Function} options.modelMap - 模块路由映射函数,返回 { [moduleName]: string[] }
142
+ * @param {Array<string>|Function} options.enabledModules - 启用的模块列表或获取模块列表的函数
143
+ * @returns {Object} 核心功能对象
144
+ */
145
+ function createMicroAppCore({
146
+ modelMap,
147
+ enabledModules,
148
+ } = {}) {
149
+ if (!modelMap || typeof modelMap !== 'function') {
150
+ throw new Error('modelMap function is required')
151
+ }
152
+
153
+ const MAP = modelMap();
154
+
155
+ /**
156
+ * 获取启用的模块列表
157
+ * @returns {Array<string>|null}
158
+ */
159
+ const getEnabledModules = () => {
160
+ if (Array.isArray(enabledModules)) {
161
+ return enabledModules
162
+ }
163
+ if (typeof enabledModules === 'function') {
164
+ return enabledModules()
165
+ }
166
+ // 默认从 window.GLOBAL_CONFIG.microApp 获取
167
+ if (typeof window !== 'undefined' && window.GLOBAL_CONFIG) {
168
+ return window.GLOBAL_CONFIG.microApp || null
169
+ }
170
+ return null
171
+ };
172
+
173
+ /**
174
+ * 根据路径获取对应的微前端模块名
175
+ * @param {string} path - 路由路径
176
+ * @returns {string|null} 模块名,如果不在任何模块中则返回 null
177
+ */
178
+ const microAppModule = path => {
179
+ const value = path.split('?')[0];
180
+ const key = Object.keys(MAP).find(key => MAP[key].includes(value));
181
+ const DATA = getEnabledModules();
182
+ return DATA?.includes(key) ? key : null
183
+ };
184
+
185
+ /**
186
+ * 生成微前端应用的完整 URL
187
+ * @param {string} path - 路由路径
188
+ * @returns {string} 完整的 URL
189
+ */
190
+ const microAppSrc = path => {
191
+ const module = microAppModule(path);
192
+ const base = window.location.href.split('#')[0];
193
+ return `${base}${module ? `${module}/` : ''}#${path}`
194
+ };
195
+
196
+ /**
197
+ * 检测微前端模块是否已部署
198
+ * @param {string} module - 模块名
199
+ * @returns {Promise<boolean>} 是否已部署
200
+ */
201
+ const microAppDeployed = async module => {
202
+ try {
203
+ const result = await fetch(`/${module}`, {
204
+ method: 'GET',
205
+ cache: 'no-store',
206
+ });
207
+ return result.ok
208
+ } catch (error) {
209
+ return false
210
+ }
211
+ };
212
+
213
+ return {
214
+ microAppModule,
215
+ microAppSrc,
216
+ microAppDeployed,
217
+ }
218
+ }
219
+
220
+ /**
221
+ * 创建默认的微前端核心功能实例
222
+ * 使用 window.GLOBAL_CONFIG.microApp 作为配置源
223
+ * @param {Function} modelMap - 模块路由映射函数
224
+ * @returns {Object} 核心功能对象
225
+ */
226
+ function createDefaultMicroAppCore(modelMap) {
227
+ return createMicroAppCore({
228
+ modelMap,
229
+ })
230
+ }
231
+
232
+ /**
233
+ * Vuex 状态同步插件
234
+ * 用于在父子应用之间同步 Vuex store 状态
235
+ */
236
+
237
+ const MESSAGE = {
238
+ SYNC_FROM_CHILD: 'sync-base-from-child',
239
+ SYNC_FROM_PARENT: 'sync-base-from-parent',
240
+ REQUEST_FROM_CHILD: 'request-base-from-child',
241
+ CHILD_ROUTE_CHANGE: 'child-route-change',
242
+ };
243
+
244
+ const LIFE_CYCLE = {
245
+ MOUNT: 'mount',
246
+ UPDATE: 'update',
247
+ UNMOUNT: 'unmount',
248
+ };
249
+
250
+ const BRIDGE = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
251
+
252
+ const LIFE_CYCLE_TYPES = Object.values(LIFE_CYCLE);
253
+ const BRIDGE_TYPES = Object.values(BRIDGE);
254
+
255
+ const getIframe = selector => document.querySelector(selector);
256
+
257
+ const postToChild = (iframeSelector, message) => {
258
+ const iframe = getIframe(iframeSelector);
259
+ iframe?.contentWindow?.postMessage(message, window.location.origin);
260
+ };
261
+
262
+ const postToParent = message => {
263
+ window.parent?.postMessage(message, window.location.origin);
264
+ };
265
+
266
+ const isLifeCycleMessage = type => LIFE_CYCLE_TYPES.includes(type);
267
+
268
+ const isBridgeMessage = type => BRIDGE_TYPES.includes(type);
269
+
270
+ const isInternalMessage = type =>
271
+ [
272
+ MESSAGE.SYNC_FROM_CHILD,
273
+ MESSAGE.REQUEST_FROM_CHILD,
274
+ MESSAGE.CHILD_ROUTE_CHANGE,
275
+ ].includes(type);
276
+
277
+ const syncRouteFromChild = fullPath => {
278
+ if (typeof fullPath !== 'string') return
279
+ const { origin, pathname } = window.location;
280
+ const base =
281
+ pathname === '/' || pathname === ''
282
+ ? origin
283
+ : (origin + pathname).replace(/\/$/, '');
284
+ const target = `${base}/#${fullPath}`;
285
+ if (window.location.href !== target) {
286
+ window.location.href = target;
287
+ }
288
+ };
289
+
290
+ /**
291
+ * 创建状态同步插件
292
+ * @param {Object} options - 配置选项
293
+ * @param {boolean} options.isParent - 是否为父应用,默认 false
294
+ * @param {string} options.iframeSelector - iframe 选择器,默认 '#microApp'
295
+ * @returns {Function} Vuex 插件函数
296
+ */
297
+ function baseSyncPlugin({
298
+ isParent = false,
299
+ iframeSelector = '#microApp',
300
+ } = {}) {
301
+ return store => {
302
+ let latestBaseState = store.state.base;
303
+
304
+ // 监听 base 状态变化,自动同步
305
+ store.watch(
306
+ state => state.base,
307
+ newBase => {
308
+ latestBaseState = newBase;
309
+ if (isParent) {
310
+ postToChild(iframeSelector, {
311
+ type: MESSAGE.SYNC_FROM_PARENT,
312
+ payload: latestBaseState,
313
+ });
314
+ } else {
315
+ postToParent({
316
+ type: MESSAGE.SYNC_FROM_CHILD,
317
+ payload: latestBaseState,
318
+ });
319
+ }
320
+ },
321
+ { deep: true }
322
+ );
323
+
324
+ // 附加路由同步功能(子应用使用)
325
+ store.attachRouterSync = router => {
326
+ if (!router || isParent) return
327
+ router.afterEach(to => {
328
+ const parentHref = window.parent?.location?.href || '';
329
+ const parentFullPath = parentHref.includes('#')
330
+ ? parentHref.split('#')[1]
331
+ : '';
332
+ const childFullPath = to.fullPath.startsWith('/')
333
+ ? to.fullPath
334
+ : '/' + to.fullPath;
335
+ if (parentFullPath !== childFullPath) {
336
+ postToParent({
337
+ type: MESSAGE.CHILD_ROUTE_CHANGE,
338
+ fullPath: to.fullPath,
339
+ });
340
+ }
341
+ });
342
+ };
343
+
344
+ // 调用微前端生命周期(父应用使用)
345
+ store.callMicroAppLifeCycle = (type, payload) => {
346
+ if (!isParent) return
347
+ if (!isLifeCycleMessage(type)) return
348
+
349
+ postToChild(iframeSelector, {
350
+ type,
351
+ payload,
352
+ });
353
+ };
354
+
355
+ // 处理消息
356
+ const handleMessage = event => {
357
+ if (event.origin !== window.location.origin) return
358
+ const { type, payload, fullPath } = event.data || {};
359
+
360
+ // 子应用处理生命周期消息
361
+ if (!isParent && isLifeCycleMessage(type)) {
362
+ window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
363
+ return
364
+ }
365
+
366
+ // 父应用处理来自子应用的状态同步
367
+ if (isParent && type === MESSAGE.SYNC_FROM_CHILD) {
368
+ store.commit('base/SYNC_STATE', payload);
369
+ return
370
+ }
371
+
372
+ // 子应用处理来自父应用的状态同步
373
+ if (!isParent && type === MESSAGE.SYNC_FROM_PARENT) {
374
+ store.commit('base/SYNC_STATE', payload);
375
+ return
376
+ }
377
+
378
+ // 父应用响应子应用的状态请求
379
+ if (isParent && type === MESSAGE.REQUEST_FROM_CHILD) {
380
+ postToChild(iframeSelector, {
381
+ type: MESSAGE.SYNC_FROM_PARENT,
382
+ payload: latestBaseState,
383
+ });
384
+ return
385
+ }
386
+
387
+ // 父应用处理子应用的路由变化
388
+ if (isParent && type === MESSAGE.CHILD_ROUTE_CHANGE) {
389
+ syncRouteFromChild(fullPath);
390
+ return
391
+ }
392
+
393
+ // 父应用转发其他消息到父窗口
394
+ if (isParent && !isInternalMessage(type) && !isBridgeMessage(type)) {
395
+ window.parent?.postMessage(event.data || {}, '*');
396
+ }
397
+ };
398
+
399
+ window.addEventListener('message', handleMessage);
400
+
401
+ // 子应用初始化时请求父应用状态
402
+ if (!isParent && window.parent) {
403
+ postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
404
+ }
405
+ }
406
+ }
407
+
408
+ /**
409
+ * @hhfenpm/micro-app
410
+ * 微前端通信桥接和状态同步工具
411
+ */
412
+
413
+
414
+ var index = {
415
+ initBridge,
416
+ createDefaultMicroAppCore,
417
+ baseSyncPlugin,
418
+ };
419
+
420
+ exports.baseSyncPlugin = baseSyncPlugin;
421
+ exports.createDefaultMicroAppCore = createDefaultMicroAppCore;
422
+ exports.createMicroAppCore = createMicroAppCore;
423
+ exports.default = index;
424
+ exports.initBridge = initBridge;
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@hhfenpm/micro-app",
3
+ "version": "1.0.0",
4
+ "description": "微前端通信桥接和状态同步工具,支持父子应用通信、状态同步、生命周期管理",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.esm.js",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "scripts": {
12
+ "build": "rollup -c",
13
+ "dev": "rollup -c -w",
14
+ "test": "echo \"Error: no test specified\" && exit 1",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "micro-frontend",
19
+ "micro-app",
20
+ "bridge",
21
+ "iframe",
22
+ "communication",
23
+ "vuex",
24
+ "state-sync"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "peerDependencies": {
29
+ "vue": "^2.6.0 || ^3.0.0",
30
+ "vuex": "^3.0.0 || ^4.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@rollup/plugin-commonjs": "^25.0.0",
34
+ "@rollup/plugin-node-resolve": "^15.0.0",
35
+ "rollup": "^3.29.5"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": ""
40
+ },
41
+ "bugs": {
42
+ "url": ""
43
+ },
44
+ "homepage": ""
45
+ }