@hhfenpm/micro-app 1.0.7 → 1.0.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 CHANGED
@@ -14,12 +14,12 @@ yarn add @hhfenpm/micro-app
14
14
 
15
15
  ## 能力概览
16
16
 
17
- | 能力 | 说明 |
18
- |------|------|
19
- | **通信桥接** | 子应用通过 `vm.$base.命名空间.方法(params)` 调用父应用方法,支持 Promise |
20
- | **状态同步** | 父子应用 Vuex `base` 模块双向同步 |
17
+ | 能力 | 说明 |
18
+ | ------------ | ----------------------------------------------------------------------------------- |
19
+ | **通信桥接** | 子应用通过 `vm.$base.命名空间.方法(params)` 调用父应用方法,支持 Promise |
20
+ | **状态同步** | 父子应用 Vuex `base` 模块双向同步 |
21
21
  | **生命周期** | 父应用调用 `mount/update/unmount`,子应用通过 `window.__MICRO_APP_LIFECYCLE__` 响应 |
22
- | **路由同步** | 子应用路由变化同步到父应用 URL(需子应用调用 `store.attachRouterSync(router)`) |
22
+ | **路由同步** | 子应用路由变化同步到父应用 URL(需子应用调用 `store.attachRouterSync(router)`) |
23
23
 
24
24
  ---
25
25
 
@@ -32,14 +32,16 @@ yarn add @hhfenpm/micro-app
32
32
  ```javascript
33
33
  import { initBridge, createRegisterHandlers } from '@hhfenpm/micro-app'
34
34
 
35
- const vm = new Vue({ /* ... */ })
35
+ const vm = new Vue({
36
+ /* ... */
37
+ })
36
38
 
37
39
  // getElectron:返回 electron 模块,无则 () => ({}) 或 () => require('@/utils/electron')
38
40
  const getElectron = () => ({}) // 或 () => require('@/utils/electron')
39
41
  const toHandlers = createRegisterHandlers(getElectron)
40
42
 
41
43
  initBridge({
42
- isParent: true,
44
+ isBase: true,
43
45
  vm,
44
46
  handlers: toHandlers(vm),
45
47
  iframeSelector: '#microApp', // 可选,默认 '#microApp'
@@ -56,13 +58,17 @@ initBridge({
56
58
  ```javascript
57
59
  import { initBridge } from '@hhfenpm/micro-app'
58
60
 
59
- const vm = new Vue({ /* ... */ })
61
+ const vm = new Vue({
62
+ /* ... */
63
+ })
60
64
 
61
- initBridge({ isParent: false, vm })
65
+ initBridge({ isBase: false, vm })
62
66
 
63
67
  // 调用父应用:vm.$base.命名空间.方法(params),返回 Promise
64
68
  vm.$base.ui.$message('来自子应用')
65
- vm.$base.ui.$confirm('确认?').then(ok => { /* ... */ })
69
+ vm.$base.ui.$confirm('确认?').then(ok => {
70
+ /* ... */
71
+ })
66
72
  vm.$base.cs.show()
67
73
  ```
68
74
 
@@ -77,7 +83,7 @@ import { createMicroAppCore } from '@hhfenpm/micro-app'
77
83
 
78
84
  const modelMap = () => ({
79
85
  'disease-analysis': ['/disease-analysis', '/ds-consult'],
80
- 'health': ['/health-manage'],
86
+ health: ['/health-manage'],
81
87
  })
82
88
 
83
89
  const core = createMicroAppCore({
@@ -86,7 +92,7 @@ const core = createMicroAppCore({
86
92
  })
87
93
 
88
94
  // 根据路径得到模块名,未命中或未启用则为 null
89
- const module = core.microAppModule('/disease-analysis') // 'disease-analysis'
95
+ const module = core.microAppModule('/disease-analysis') // 'disease-analysis'
90
96
 
91
97
  // 子应用完整 URL(含 hash)
92
98
  const src = core.microAppSrc('/disease-analysis')
@@ -110,7 +116,7 @@ import base from './store/modules/base' // 需含 base/SYNC_STATE
110
116
 
111
117
  const store = new Vuex.Store({
112
118
  modules: { base },
113
- plugins: [baseSyncPlugin({ isParent: true, iframeSelector: '#microApp' })],
119
+ plugins: [baseSyncPlugin({ isBase: true, iframeSelector: '#microApp' })],
114
120
  })
115
121
  ```
116
122
 
@@ -122,7 +128,7 @@ import base from './store/modules/base'
122
128
 
123
129
  const store = new Vuex.Store({
124
130
  modules: { base },
125
- plugins: [baseSyncPlugin({ isParent: false, iframeSelector: '#microApp' })],
131
+ plugins: [baseSyncPlugin({ isBase: false, iframeSelector: '#microApp' })],
126
132
  })
127
133
 
128
134
  import router from './router'
@@ -141,9 +147,15 @@ store.attachRouterSync(router) // 必调,用于把子应用路由同步到父
141
147
 
142
148
  ```javascript
143
149
  window.__MICRO_APP_LIFECYCLE__ = {
144
- mount(payload) { /* 挂载后 */ },
145
- update(payload) { /* 更新时,如路由变化 */ },
146
- unmount(payload) { /* 卸载前 */ },
150
+ mount(payload) {
151
+ /* 挂载后 */
152
+ },
153
+ update(payload) {
154
+ /* 更新时,如路由变化 */
155
+ },
156
+ unmount(payload) {
157
+ /* 卸载前 */
158
+ },
147
159
  }
148
160
  ```
149
161
 
@@ -161,30 +173,33 @@ store.callMicroAppLifeCycle('unmount')
161
173
 
162
174
  ## API 一览
163
175
 
164
- | 接口 | 说明 |
165
- |------|------|
166
- | `initBridge({ isParent, vm, iframeSelector?, handlers? })` | 初始化桥接;父应用传 `handlers`,子应用可访问 `vm.$base` |
167
- | `createRegisterHandlers(getElectron?)` | 返回 `(vm) => ({ ui, cs })`,用作 `handlers` |
168
- | `createMicroAppCore({ modelMap, enabledModules? })` | 返回 `{ microAppModule, microAppSrc, microAppDeployed }` |
169
- | `baseSyncPlugin({ isParent, iframeSelector? })` | Vuex 插件,并挂载 `store.attachRouterSync`、`store.callMicroAppLifeCycle` |
176
+ | 接口 | 说明 |
177
+ | -------------------------------------------------------- | ------------------------------------------------------------------------- |
178
+ | `initBridge({ isBase, vm, iframeSelector?, handlers? })` | 初始化桥接;父应用传 `handlers`,子应用可访问 `vm.$base` |
179
+ | `createRegisterHandlers(getElectron?)` | 返回 `(vm) => ({ ui, cs })`,用作 `handlers` |
180
+ | `createMicroAppCore({ modelMap, enabledModules? })` | 返回 `{ microAppModule, microAppSrc, microAppDeployed }` |
181
+ | `baseSyncPlugin({ isBase, iframeSelector? })` | Vuex 插件,并挂载 `store.attachRouterSync`、`store.callMicroAppLifeCycle` |
170
182
 
171
183
  ### 参数说明
172
184
 
173
185
  - **initBridge**
174
- - `isParent`: 是否父应用
186
+
187
+ - `isBase`: 是否父应用
175
188
  - `vm`: Vue 实例(子应用会在其上挂 `vm.$base`、`vm.$baseReady`)
176
189
  - `iframeSelector`: 父应用 iframe 选择器,默认 `'#microApp'`
177
190
  - `handlers`: 仅父应用需要,`{ [namespace]: { [method]: (params)=>any } }`
178
191
 
179
192
  - **createRegisterHandlers**
193
+
180
194
  - `getElectron`: `() => electronModule`,可选;异常或未传时 `cs` 使用 noop
181
195
 
182
196
  - **createMicroAppCore**
197
+
183
198
  - `modelMap`: `() => ({ [moduleName]: path[] })`,必填
184
199
  - `enabledModules`: `() => string[]` 或 `string[]`,可选;缺省时从 `window.GLOBAL_CONFIG.microApp` 读取
185
200
 
186
201
  - **baseSyncPlugin**
187
- - `isParent`: 是否父应用
202
+ - `isBase`: 是否父应用
188
203
  - `iframeSelector`: 默认 `'#microApp'`
189
204
 
190
205
  ---
package/dist/index.esm.js CHANGED
@@ -89,7 +89,7 @@ const safeClone = value => {
89
89
  const createBaseObject = pending => {
90
90
  const namespaces = {};
91
91
  const hasProxy = typeof Proxy !== 'undefined';
92
-
92
+
93
93
  const createMethod = (namespace, method) => {
94
94
  return params =>
95
95
  new Promise(resolve => {
@@ -104,7 +104,7 @@ const createBaseObject = pending => {
104
104
  });
105
105
  })
106
106
  };
107
-
107
+
108
108
  if (hasProxy) {
109
109
  const base = {};
110
110
  return new Proxy(base, {
@@ -117,33 +117,36 @@ const createBaseObject = pending => {
117
117
  get(target2, method) {
118
118
  if (typeof method === 'string') {
119
119
  if (!namespaces[namespace][method]) {
120
- namespaces[namespace][method] = createMethod(namespace, method);
120
+ namespaces[namespace][method] = createMethod(
121
+ namespace,
122
+ method
123
+ );
121
124
  }
122
125
  return namespaces[namespace][method]
123
126
  }
124
127
  return undefined
125
- }
128
+ },
126
129
  })
127
130
  }
128
131
  return undefined
129
- }
132
+ },
130
133
  })
131
134
  } else {
132
135
  const base = {};
133
136
  const commonNamespaces = ['ui', 'cs'];
134
-
137
+
135
138
  commonNamespaces.forEach(ns => {
136
139
  namespaces[ns] = {};
137
140
  const nsObj = {};
138
-
141
+
139
142
  Object.defineProperty(base, ns, {
140
143
  get() {
141
144
  return nsObj
142
145
  },
143
- configurable: true
146
+ configurable: true,
144
147
  });
145
148
  });
146
-
149
+
147
150
  return base
148
151
  }
149
152
  };
@@ -160,15 +163,15 @@ function createRegisterHandlers(getElectron) {
160
163
  }
161
164
 
162
165
  const initBridge = ({
163
- isParent = false,
166
+ isBase = false,
164
167
  vm,
165
168
  iframeSelector = '#microApp',
166
169
  handlers = {},
167
170
  } = {}) => {
168
- const finalHandlers = isParent ? handlers : Object.create(null);
171
+ const finalHandlers = isBase ? handlers : Object.create(null);
169
172
  const pending = Object.create(null);
170
173
 
171
- if (!isParent && vm) {
174
+ if (!isBase && vm) {
172
175
  const baseObj = createBaseObject(pending);
173
176
  vm.$base = baseObj;
174
177
  vm.$baseReady = true;
@@ -216,8 +219,8 @@ const initBridge = ({
216
219
  if (e.origin !== window.location.origin) return
217
220
 
218
221
  const { type } = e.data || {};
219
- if (isParent && type === BRIDGE$1.INVOKE) handleInvoke(e);
220
- if (!isParent && type === BRIDGE$1.CALLBACK) handleCallback(e);
222
+ if (isBase && type === BRIDGE$1.INVOKE) handleInvoke(e);
223
+ if (!isBase && type === BRIDGE$1.CALLBACK) handleCallback(e);
221
224
  });
222
225
  };
223
226
 
@@ -288,141 +291,191 @@ const LIFE_CYCLE = {
288
291
 
289
292
  const BRIDGE = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
290
293
 
291
- const LIFE_CYCLE_TYPES = Object.values(LIFE_CYCLE);
292
- const BRIDGE_TYPES = Object.values(BRIDGE);
294
+ const LIFE_CYCLE_SET = new Set(Object.values(LIFE_CYCLE));
295
+ const BRIDGE_SET = new Set(Object.values(BRIDGE));
296
+ const INTERNAL_SET = new Set([
297
+ MESSAGE.SYNC_FROM_CHILD,
298
+ MESSAGE.REQUEST_FROM_CHILD,
299
+ MESSAGE.CHILD_ROUTE_CHANGE,
300
+ ]);
293
301
 
294
302
  const postToChild = (iframeSelector, message) => {
295
- document.querySelector(iframeSelector)?.contentWindow?.postMessage(
296
- message,
297
- window.location.origin
298
- );
303
+ const iframe = document.querySelector(iframeSelector);
304
+ if (!iframe) return
305
+ iframe.contentWindow?.postMessage(message, window.location.origin);
299
306
  };
300
307
 
301
308
  const postToParent = message => {
302
309
  window.parent?.postMessage(message, window.location.origin);
303
310
  };
304
311
 
305
- const isLifeCycleMessage = type => LIFE_CYCLE_TYPES.includes(type);
306
-
307
- const isBridgeMessage = type => BRIDGE_TYPES.includes(type);
308
-
309
- const isInternalMessage = type =>
310
- [
311
- MESSAGE.SYNC_FROM_CHILD,
312
- MESSAGE.REQUEST_FROM_CHILD,
313
- MESSAGE.CHILD_ROUTE_CHANGE,
314
- ].includes(type);
315
-
316
312
  const syncRouteFromChild = fullPath => {
317
313
  if (typeof fullPath !== 'string') return
314
+
318
315
  const { origin, pathname } = window.location;
319
- const base =
320
- pathname === '/' || pathname === ''
321
- ? origin
322
- : (origin + pathname).replace(/\/$/, '');
316
+ let base = origin;
317
+
318
+ if (pathname && pathname !== '/') {
319
+ base = (origin + pathname).replace(/\/$/, '');
320
+ }
321
+
323
322
  const target = `${base}/#${fullPath}`;
324
323
  if (window.location.href !== target) {
325
324
  window.location.href = target;
326
325
  }
327
326
  };
328
327
 
328
+ const createMessageHandlers = (
329
+ store,
330
+ isBase,
331
+ iframeSelector,
332
+ getLatestBaseState
333
+ ) => {
334
+ const handlers = [];
335
+
336
+ if (!isBase) {
337
+ handlers.push((type, payload) => {
338
+ if (LIFE_CYCLE_SET.has(type)) {
339
+ window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
340
+ return true
341
+ }
342
+ return false
343
+ });
344
+ }
345
+
346
+ handlers.push((type, payload, fullPath) => {
347
+ if (type === MESSAGE.SYNC_FROM_CHILD && isBase) {
348
+ store.commit('base/SYNC_STATE', payload);
349
+ return true
350
+ }
351
+
352
+ if (type === MESSAGE.SYNC_FROM_PARENT && !isBase) {
353
+ store.commit('base/SYNC_STATE', payload);
354
+ return true
355
+ }
356
+
357
+ if (type === MESSAGE.REQUEST_FROM_CHILD && isBase) {
358
+ postToChild(iframeSelector, {
359
+ type: MESSAGE.SYNC_FROM_PARENT,
360
+ payload: getLatestBaseState(),
361
+ });
362
+ return true
363
+ }
364
+
365
+ if (type === MESSAGE.CHILD_ROUTE_CHANGE && isBase) {
366
+ syncRouteFromChild(fullPath);
367
+ return true
368
+ }
369
+
370
+ return false
371
+ });
372
+
373
+ handlers.push((type, data) => {
374
+ if (isBase && !INTERNAL_SET.has(type) && !BRIDGE_SET.has(type)) {
375
+ window.parent?.postMessage(data, '*');
376
+ return true
377
+ }
378
+ return false
379
+ });
380
+
381
+ return handlers
382
+ };
383
+
329
384
  function baseSyncPlugin({
330
- isParent = false,
385
+ isBase = false,
331
386
  iframeSelector = '#microApp',
332
387
  } = {}) {
333
388
  return store => {
334
389
  let latestBaseState = store.state.base;
390
+ let isMounted = false;
391
+
392
+ const handleStateChange = () => {
393
+ if (!isMounted) return
394
+
395
+ const message = isBase
396
+ ? { type: MESSAGE.SYNC_FROM_PARENT, payload: latestBaseState }
397
+ : { type: MESSAGE.SYNC_FROM_CHILD, payload: latestBaseState };
335
398
 
336
- store.watch(
337
- state => state.base,
399
+ isBase ? postToChild(iframeSelector, message) : postToParent(message);
400
+ };
401
+
402
+ const unwatch = store.watch(
403
+ () => store.state.base,
338
404
  newBase => {
339
405
  latestBaseState = newBase;
340
- if (isParent) {
341
- postToChild(iframeSelector, {
342
- type: MESSAGE.SYNC_FROM_PARENT,
343
- payload: latestBaseState,
344
- });
345
- } else {
346
- postToParent({
347
- type: MESSAGE.SYNC_FROM_CHILD,
348
- payload: latestBaseState,
349
- });
350
- }
406
+ handleStateChange();
351
407
  },
352
408
  { deep: true }
353
409
  );
354
410
 
411
+ const messageHandlers = createMessageHandlers(
412
+ store,
413
+ isBase,
414
+ iframeSelector,
415
+ () => latestBaseState
416
+ );
417
+
418
+ const messageListener = event => {
419
+ if (event.origin !== window.location.origin) return
420
+
421
+ const { type, payload, fullPath } = event.data || {};
422
+ if (!type) return
423
+
424
+ for (const handler of messageHandlers) {
425
+ if (handler(type, payload, fullPath, event.data)) {
426
+ break
427
+ }
428
+ }
429
+ };
430
+
355
431
  store.attachRouterSync = router => {
356
- if (!router || isParent) return
357
- router.afterEach(to => {
432
+ if (!router || isBase) return
433
+
434
+ const routeHandler = to => {
358
435
  const parentHref = window.parent?.location?.href || '';
359
- const parentFullPath = parentHref.includes('#')
360
- ? parentHref.split('#')[1]
361
- : '';
436
+ const hashIndex = parentHref.indexOf('#');
437
+ const parentFullPath =
438
+ hashIndex > -1 ? parentHref.slice(hashIndex + 1) : '';
362
439
  const childFullPath = to.fullPath.startsWith('/')
363
440
  ? to.fullPath
364
441
  : '/' + to.fullPath;
442
+
365
443
  if (parentFullPath !== childFullPath) {
366
444
  postToParent({
367
445
  type: MESSAGE.CHILD_ROUTE_CHANGE,
368
446
  fullPath: to.fullPath,
369
447
  });
370
448
  }
371
- });
449
+ };
450
+
451
+ router.afterEach(routeHandler);
372
452
  };
373
453
 
374
454
  store.callMicroAppLifeCycle = (type, payload) => {
375
- if (!isParent) return
376
- if (!isLifeCycleMessage(type)) return
455
+ if (!isBase || !LIFE_CYCLE_SET.has(type)) return
377
456
 
378
- postToChild(iframeSelector, {
379
- type,
380
- payload,
381
- });
457
+ postToChild(iframeSelector, { type, payload });
382
458
  };
383
459
 
384
- const handleMessage = event => {
385
- if (event.origin !== window.location.origin) return
386
- const { type, payload, fullPath } = event.data || {};
387
-
388
- if (!isParent && isLifeCycleMessage(type)) {
389
- window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
390
- return
391
- }
392
-
393
- if (isParent && type === MESSAGE.SYNC_FROM_CHILD) {
394
- store.commit('base/SYNC_STATE', payload);
395
- return
396
- }
397
-
398
- if (!isParent && type === MESSAGE.SYNC_FROM_PARENT) {
399
- store.commit('base/SYNC_STATE', payload);
400
- return
401
- }
402
-
403
- if (isParent && type === MESSAGE.REQUEST_FROM_CHILD) {
404
- postToChild(iframeSelector, {
405
- type: MESSAGE.SYNC_FROM_PARENT,
406
- payload: latestBaseState,
407
- });
408
- return
409
- }
410
-
411
- if (isParent && type === MESSAGE.CHILD_ROUTE_CHANGE) {
412
- syncRouteFromChild(fullPath);
413
- return
414
- }
415
-
416
- if (isParent && !isInternalMessage(type) && !isBridgeMessage(type)) {
417
- window.parent?.postMessage(event.data || {}, '*');
418
- }
460
+ store.destroy = () => {
461
+ window.removeEventListener('message', messageListener);
462
+ unwatch?.();
463
+ isMounted = false;
419
464
  };
420
465
 
421
- window.addEventListener('message', handleMessage);
466
+ window.addEventListener('message', messageListener);
422
467
 
423
- if (!isParent && window.parent) {
424
- postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
468
+ if (!isBase && window.parent) {
469
+ const requestTimer = setTimeout(() => {
470
+ postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
471
+ isMounted = true;
472
+ clearTimeout(requestTimer);
473
+ }, 0);
474
+ } else {
475
+ isMounted = true;
425
476
  }
477
+
478
+ return store
426
479
  }
427
480
  }
428
481
 
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ const safeClone = value => {
91
91
  const createBaseObject = pending => {
92
92
  const namespaces = {};
93
93
  const hasProxy = typeof Proxy !== 'undefined';
94
-
94
+
95
95
  const createMethod = (namespace, method) => {
96
96
  return params =>
97
97
  new Promise(resolve => {
@@ -106,7 +106,7 @@ const createBaseObject = pending => {
106
106
  });
107
107
  })
108
108
  };
109
-
109
+
110
110
  if (hasProxy) {
111
111
  const base = {};
112
112
  return new Proxy(base, {
@@ -119,33 +119,36 @@ const createBaseObject = pending => {
119
119
  get(target2, method) {
120
120
  if (typeof method === 'string') {
121
121
  if (!namespaces[namespace][method]) {
122
- namespaces[namespace][method] = createMethod(namespace, method);
122
+ namespaces[namespace][method] = createMethod(
123
+ namespace,
124
+ method
125
+ );
123
126
  }
124
127
  return namespaces[namespace][method]
125
128
  }
126
129
  return undefined
127
- }
130
+ },
128
131
  })
129
132
  }
130
133
  return undefined
131
- }
134
+ },
132
135
  })
133
136
  } else {
134
137
  const base = {};
135
138
  const commonNamespaces = ['ui', 'cs'];
136
-
139
+
137
140
  commonNamespaces.forEach(ns => {
138
141
  namespaces[ns] = {};
139
142
  const nsObj = {};
140
-
143
+
141
144
  Object.defineProperty(base, ns, {
142
145
  get() {
143
146
  return nsObj
144
147
  },
145
- configurable: true
148
+ configurable: true,
146
149
  });
147
150
  });
148
-
151
+
149
152
  return base
150
153
  }
151
154
  };
@@ -162,15 +165,15 @@ function createRegisterHandlers(getElectron) {
162
165
  }
163
166
 
164
167
  const initBridge = ({
165
- isParent = false,
168
+ isBase = false,
166
169
  vm,
167
170
  iframeSelector = '#microApp',
168
171
  handlers = {},
169
172
  } = {}) => {
170
- const finalHandlers = isParent ? handlers : Object.create(null);
173
+ const finalHandlers = isBase ? handlers : Object.create(null);
171
174
  const pending = Object.create(null);
172
175
 
173
- if (!isParent && vm) {
176
+ if (!isBase && vm) {
174
177
  const baseObj = createBaseObject(pending);
175
178
  vm.$base = baseObj;
176
179
  vm.$baseReady = true;
@@ -218,8 +221,8 @@ const initBridge = ({
218
221
  if (e.origin !== window.location.origin) return
219
222
 
220
223
  const { type } = e.data || {};
221
- if (isParent && type === BRIDGE$1.INVOKE) handleInvoke(e);
222
- if (!isParent && type === BRIDGE$1.CALLBACK) handleCallback(e);
224
+ if (isBase && type === BRIDGE$1.INVOKE) handleInvoke(e);
225
+ if (!isBase && type === BRIDGE$1.CALLBACK) handleCallback(e);
223
226
  });
224
227
  };
225
228
 
@@ -290,141 +293,191 @@ const LIFE_CYCLE = {
290
293
 
291
294
  const BRIDGE = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
292
295
 
293
- const LIFE_CYCLE_TYPES = Object.values(LIFE_CYCLE);
294
- const BRIDGE_TYPES = Object.values(BRIDGE);
296
+ const LIFE_CYCLE_SET = new Set(Object.values(LIFE_CYCLE));
297
+ const BRIDGE_SET = new Set(Object.values(BRIDGE));
298
+ const INTERNAL_SET = new Set([
299
+ MESSAGE.SYNC_FROM_CHILD,
300
+ MESSAGE.REQUEST_FROM_CHILD,
301
+ MESSAGE.CHILD_ROUTE_CHANGE,
302
+ ]);
295
303
 
296
304
  const postToChild = (iframeSelector, message) => {
297
- document.querySelector(iframeSelector)?.contentWindow?.postMessage(
298
- message,
299
- window.location.origin
300
- );
305
+ const iframe = document.querySelector(iframeSelector);
306
+ if (!iframe) return
307
+ iframe.contentWindow?.postMessage(message, window.location.origin);
301
308
  };
302
309
 
303
310
  const postToParent = message => {
304
311
  window.parent?.postMessage(message, window.location.origin);
305
312
  };
306
313
 
307
- const isLifeCycleMessage = type => LIFE_CYCLE_TYPES.includes(type);
308
-
309
- const isBridgeMessage = type => BRIDGE_TYPES.includes(type);
310
-
311
- const isInternalMessage = type =>
312
- [
313
- MESSAGE.SYNC_FROM_CHILD,
314
- MESSAGE.REQUEST_FROM_CHILD,
315
- MESSAGE.CHILD_ROUTE_CHANGE,
316
- ].includes(type);
317
-
318
314
  const syncRouteFromChild = fullPath => {
319
315
  if (typeof fullPath !== 'string') return
316
+
320
317
  const { origin, pathname } = window.location;
321
- const base =
322
- pathname === '/' || pathname === ''
323
- ? origin
324
- : (origin + pathname).replace(/\/$/, '');
318
+ let base = origin;
319
+
320
+ if (pathname && pathname !== '/') {
321
+ base = (origin + pathname).replace(/\/$/, '');
322
+ }
323
+
325
324
  const target = `${base}/#${fullPath}`;
326
325
  if (window.location.href !== target) {
327
326
  window.location.href = target;
328
327
  }
329
328
  };
330
329
 
330
+ const createMessageHandlers = (
331
+ store,
332
+ isBase,
333
+ iframeSelector,
334
+ getLatestBaseState
335
+ ) => {
336
+ const handlers = [];
337
+
338
+ if (!isBase) {
339
+ handlers.push((type, payload) => {
340
+ if (LIFE_CYCLE_SET.has(type)) {
341
+ window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
342
+ return true
343
+ }
344
+ return false
345
+ });
346
+ }
347
+
348
+ handlers.push((type, payload, fullPath) => {
349
+ if (type === MESSAGE.SYNC_FROM_CHILD && isBase) {
350
+ store.commit('base/SYNC_STATE', payload);
351
+ return true
352
+ }
353
+
354
+ if (type === MESSAGE.SYNC_FROM_PARENT && !isBase) {
355
+ store.commit('base/SYNC_STATE', payload);
356
+ return true
357
+ }
358
+
359
+ if (type === MESSAGE.REQUEST_FROM_CHILD && isBase) {
360
+ postToChild(iframeSelector, {
361
+ type: MESSAGE.SYNC_FROM_PARENT,
362
+ payload: getLatestBaseState(),
363
+ });
364
+ return true
365
+ }
366
+
367
+ if (type === MESSAGE.CHILD_ROUTE_CHANGE && isBase) {
368
+ syncRouteFromChild(fullPath);
369
+ return true
370
+ }
371
+
372
+ return false
373
+ });
374
+
375
+ handlers.push((type, data) => {
376
+ if (isBase && !INTERNAL_SET.has(type) && !BRIDGE_SET.has(type)) {
377
+ window.parent?.postMessage(data, '*');
378
+ return true
379
+ }
380
+ return false
381
+ });
382
+
383
+ return handlers
384
+ };
385
+
331
386
  function baseSyncPlugin({
332
- isParent = false,
387
+ isBase = false,
333
388
  iframeSelector = '#microApp',
334
389
  } = {}) {
335
390
  return store => {
336
391
  let latestBaseState = store.state.base;
392
+ let isMounted = false;
393
+
394
+ const handleStateChange = () => {
395
+ if (!isMounted) return
396
+
397
+ const message = isBase
398
+ ? { type: MESSAGE.SYNC_FROM_PARENT, payload: latestBaseState }
399
+ : { type: MESSAGE.SYNC_FROM_CHILD, payload: latestBaseState };
337
400
 
338
- store.watch(
339
- state => state.base,
401
+ isBase ? postToChild(iframeSelector, message) : postToParent(message);
402
+ };
403
+
404
+ const unwatch = store.watch(
405
+ () => store.state.base,
340
406
  newBase => {
341
407
  latestBaseState = newBase;
342
- if (isParent) {
343
- postToChild(iframeSelector, {
344
- type: MESSAGE.SYNC_FROM_PARENT,
345
- payload: latestBaseState,
346
- });
347
- } else {
348
- postToParent({
349
- type: MESSAGE.SYNC_FROM_CHILD,
350
- payload: latestBaseState,
351
- });
352
- }
408
+ handleStateChange();
353
409
  },
354
410
  { deep: true }
355
411
  );
356
412
 
413
+ const messageHandlers = createMessageHandlers(
414
+ store,
415
+ isBase,
416
+ iframeSelector,
417
+ () => latestBaseState
418
+ );
419
+
420
+ const messageListener = event => {
421
+ if (event.origin !== window.location.origin) return
422
+
423
+ const { type, payload, fullPath } = event.data || {};
424
+ if (!type) return
425
+
426
+ for (const handler of messageHandlers) {
427
+ if (handler(type, payload, fullPath, event.data)) {
428
+ break
429
+ }
430
+ }
431
+ };
432
+
357
433
  store.attachRouterSync = router => {
358
- if (!router || isParent) return
359
- router.afterEach(to => {
434
+ if (!router || isBase) return
435
+
436
+ const routeHandler = to => {
360
437
  const parentHref = window.parent?.location?.href || '';
361
- const parentFullPath = parentHref.includes('#')
362
- ? parentHref.split('#')[1]
363
- : '';
438
+ const hashIndex = parentHref.indexOf('#');
439
+ const parentFullPath =
440
+ hashIndex > -1 ? parentHref.slice(hashIndex + 1) : '';
364
441
  const childFullPath = to.fullPath.startsWith('/')
365
442
  ? to.fullPath
366
443
  : '/' + to.fullPath;
444
+
367
445
  if (parentFullPath !== childFullPath) {
368
446
  postToParent({
369
447
  type: MESSAGE.CHILD_ROUTE_CHANGE,
370
448
  fullPath: to.fullPath,
371
449
  });
372
450
  }
373
- });
451
+ };
452
+
453
+ router.afterEach(routeHandler);
374
454
  };
375
455
 
376
456
  store.callMicroAppLifeCycle = (type, payload) => {
377
- if (!isParent) return
378
- if (!isLifeCycleMessage(type)) return
457
+ if (!isBase || !LIFE_CYCLE_SET.has(type)) return
379
458
 
380
- postToChild(iframeSelector, {
381
- type,
382
- payload,
383
- });
459
+ postToChild(iframeSelector, { type, payload });
384
460
  };
385
461
 
386
- const handleMessage = event => {
387
- if (event.origin !== window.location.origin) return
388
- const { type, payload, fullPath } = event.data || {};
389
-
390
- if (!isParent && isLifeCycleMessage(type)) {
391
- window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
392
- return
393
- }
394
-
395
- if (isParent && type === MESSAGE.SYNC_FROM_CHILD) {
396
- store.commit('base/SYNC_STATE', payload);
397
- return
398
- }
399
-
400
- if (!isParent && type === MESSAGE.SYNC_FROM_PARENT) {
401
- store.commit('base/SYNC_STATE', payload);
402
- return
403
- }
404
-
405
- if (isParent && type === MESSAGE.REQUEST_FROM_CHILD) {
406
- postToChild(iframeSelector, {
407
- type: MESSAGE.SYNC_FROM_PARENT,
408
- payload: latestBaseState,
409
- });
410
- return
411
- }
412
-
413
- if (isParent && type === MESSAGE.CHILD_ROUTE_CHANGE) {
414
- syncRouteFromChild(fullPath);
415
- return
416
- }
417
-
418
- if (isParent && !isInternalMessage(type) && !isBridgeMessage(type)) {
419
- window.parent?.postMessage(event.data || {}, '*');
420
- }
462
+ store.destroy = () => {
463
+ window.removeEventListener('message', messageListener);
464
+ unwatch?.();
465
+ isMounted = false;
421
466
  };
422
467
 
423
- window.addEventListener('message', handleMessage);
468
+ window.addEventListener('message', messageListener);
424
469
 
425
- if (!isParent && window.parent) {
426
- postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
470
+ if (!isBase && window.parent) {
471
+ const requestTimer = setTimeout(() => {
472
+ postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
473
+ isMounted = true;
474
+ clearTimeout(requestTimer);
475
+ }, 0);
476
+ } else {
477
+ isMounted = true;
427
478
  }
479
+
480
+ return store
428
481
  }
429
482
  }
430
483
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hhfenpm/micro-app",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "微前端通信桥接和状态同步工具,支持父子应用通信、状态同步、生命周期管理",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",