@hhfenpm/micro-app 1.0.6 → 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,144 +291,210 @@ 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;
335
391
 
336
- store.watch(
337
- state => state.base,
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 };
398
+
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
 
429
- var index = { initBridge, baseSyncPlugin };
482
+ function syncState(watch = {}) {
483
+ return function SYNC_STATE(state, payload) {
484
+ if (!payload || typeof payload !== 'object') return
485
+
486
+ Object.keys(watch).forEach(key => {
487
+ if (state[key] !== payload[key] && typeof watch[key] === 'function')
488
+ watch[key]({
489
+ store: this,
490
+ value: payload[key],
491
+ valueOld: state[key],
492
+ });
493
+ });
494
+ Object.keys(payload).forEach(key => {
495
+ if (state[key] !== payload[key]) state[key] = payload[key];
496
+ });
497
+ }
498
+ }
430
499
 
431
- export { baseSyncPlugin, createMicroAppCore, createRegisterHandlers, index as default, initBridge };
500
+ export { baseSyncPlugin, createMicroAppCore, createRegisterHandlers, initBridge, syncState };
package/dist/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var uiHandler = vm => ({
6
4
  $message: (...args) => vm.$message(...args),
7
5
  $success: msg => vm.$message.success(msg),
@@ -93,7 +91,7 @@ const safeClone = value => {
93
91
  const createBaseObject = pending => {
94
92
  const namespaces = {};
95
93
  const hasProxy = typeof Proxy !== 'undefined';
96
-
94
+
97
95
  const createMethod = (namespace, method) => {
98
96
  return params =>
99
97
  new Promise(resolve => {
@@ -108,7 +106,7 @@ const createBaseObject = pending => {
108
106
  });
109
107
  })
110
108
  };
111
-
109
+
112
110
  if (hasProxy) {
113
111
  const base = {};
114
112
  return new Proxy(base, {
@@ -121,33 +119,36 @@ const createBaseObject = pending => {
121
119
  get(target2, method) {
122
120
  if (typeof method === 'string') {
123
121
  if (!namespaces[namespace][method]) {
124
- namespaces[namespace][method] = createMethod(namespace, method);
122
+ namespaces[namespace][method] = createMethod(
123
+ namespace,
124
+ method
125
+ );
125
126
  }
126
127
  return namespaces[namespace][method]
127
128
  }
128
129
  return undefined
129
- }
130
+ },
130
131
  })
131
132
  }
132
133
  return undefined
133
- }
134
+ },
134
135
  })
135
136
  } else {
136
137
  const base = {};
137
138
  const commonNamespaces = ['ui', 'cs'];
138
-
139
+
139
140
  commonNamespaces.forEach(ns => {
140
141
  namespaces[ns] = {};
141
142
  const nsObj = {};
142
-
143
+
143
144
  Object.defineProperty(base, ns, {
144
145
  get() {
145
146
  return nsObj
146
147
  },
147
- configurable: true
148
+ configurable: true,
148
149
  });
149
150
  });
150
-
151
+
151
152
  return base
152
153
  }
153
154
  };
@@ -164,15 +165,15 @@ function createRegisterHandlers(getElectron) {
164
165
  }
165
166
 
166
167
  const initBridge = ({
167
- isParent = false,
168
+ isBase = false,
168
169
  vm,
169
170
  iframeSelector = '#microApp',
170
171
  handlers = {},
171
172
  } = {}) => {
172
- const finalHandlers = isParent ? handlers : Object.create(null);
173
+ const finalHandlers = isBase ? handlers : Object.create(null);
173
174
  const pending = Object.create(null);
174
175
 
175
- if (!isParent && vm) {
176
+ if (!isBase && vm) {
176
177
  const baseObj = createBaseObject(pending);
177
178
  vm.$base = baseObj;
178
179
  vm.$baseReady = true;
@@ -220,8 +221,8 @@ const initBridge = ({
220
221
  if (e.origin !== window.location.origin) return
221
222
 
222
223
  const { type } = e.data || {};
223
- if (isParent && type === BRIDGE$1.INVOKE) handleInvoke(e);
224
- 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);
225
226
  });
226
227
  };
227
228
 
@@ -292,148 +293,214 @@ const LIFE_CYCLE = {
292
293
 
293
294
  const BRIDGE = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
294
295
 
295
- const LIFE_CYCLE_TYPES = Object.values(LIFE_CYCLE);
296
- 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
+ ]);
297
303
 
298
304
  const postToChild = (iframeSelector, message) => {
299
- document.querySelector(iframeSelector)?.contentWindow?.postMessage(
300
- message,
301
- window.location.origin
302
- );
305
+ const iframe = document.querySelector(iframeSelector);
306
+ if (!iframe) return
307
+ iframe.contentWindow?.postMessage(message, window.location.origin);
303
308
  };
304
309
 
305
310
  const postToParent = message => {
306
311
  window.parent?.postMessage(message, window.location.origin);
307
312
  };
308
313
 
309
- const isLifeCycleMessage = type => LIFE_CYCLE_TYPES.includes(type);
310
-
311
- const isBridgeMessage = type => BRIDGE_TYPES.includes(type);
312
-
313
- const isInternalMessage = type =>
314
- [
315
- MESSAGE.SYNC_FROM_CHILD,
316
- MESSAGE.REQUEST_FROM_CHILD,
317
- MESSAGE.CHILD_ROUTE_CHANGE,
318
- ].includes(type);
319
-
320
314
  const syncRouteFromChild = fullPath => {
321
315
  if (typeof fullPath !== 'string') return
316
+
322
317
  const { origin, pathname } = window.location;
323
- const base =
324
- pathname === '/' || pathname === ''
325
- ? origin
326
- : (origin + pathname).replace(/\/$/, '');
318
+ let base = origin;
319
+
320
+ if (pathname && pathname !== '/') {
321
+ base = (origin + pathname).replace(/\/$/, '');
322
+ }
323
+
327
324
  const target = `${base}/#${fullPath}`;
328
325
  if (window.location.href !== target) {
329
326
  window.location.href = target;
330
327
  }
331
328
  };
332
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
+
333
386
  function baseSyncPlugin({
334
- isParent = false,
387
+ isBase = false,
335
388
  iframeSelector = '#microApp',
336
389
  } = {}) {
337
390
  return store => {
338
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 };
400
+
401
+ isBase ? postToChild(iframeSelector, message) : postToParent(message);
402
+ };
339
403
 
340
- store.watch(
341
- state => state.base,
404
+ const unwatch = store.watch(
405
+ () => store.state.base,
342
406
  newBase => {
343
407
  latestBaseState = newBase;
344
- if (isParent) {
345
- postToChild(iframeSelector, {
346
- type: MESSAGE.SYNC_FROM_PARENT,
347
- payload: latestBaseState,
348
- });
349
- } else {
350
- postToParent({
351
- type: MESSAGE.SYNC_FROM_CHILD,
352
- payload: latestBaseState,
353
- });
354
- }
408
+ handleStateChange();
355
409
  },
356
410
  { deep: true }
357
411
  );
358
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
+
359
433
  store.attachRouterSync = router => {
360
- if (!router || isParent) return
361
- router.afterEach(to => {
434
+ if (!router || isBase) return
435
+
436
+ const routeHandler = to => {
362
437
  const parentHref = window.parent?.location?.href || '';
363
- const parentFullPath = parentHref.includes('#')
364
- ? parentHref.split('#')[1]
365
- : '';
438
+ const hashIndex = parentHref.indexOf('#');
439
+ const parentFullPath =
440
+ hashIndex > -1 ? parentHref.slice(hashIndex + 1) : '';
366
441
  const childFullPath = to.fullPath.startsWith('/')
367
442
  ? to.fullPath
368
443
  : '/' + to.fullPath;
444
+
369
445
  if (parentFullPath !== childFullPath) {
370
446
  postToParent({
371
447
  type: MESSAGE.CHILD_ROUTE_CHANGE,
372
448
  fullPath: to.fullPath,
373
449
  });
374
450
  }
375
- });
451
+ };
452
+
453
+ router.afterEach(routeHandler);
376
454
  };
377
455
 
378
456
  store.callMicroAppLifeCycle = (type, payload) => {
379
- if (!isParent) return
380
- if (!isLifeCycleMessage(type)) return
457
+ if (!isBase || !LIFE_CYCLE_SET.has(type)) return
381
458
 
382
- postToChild(iframeSelector, {
383
- type,
384
- payload,
385
- });
459
+ postToChild(iframeSelector, { type, payload });
386
460
  };
387
461
 
388
- const handleMessage = event => {
389
- if (event.origin !== window.location.origin) return
390
- const { type, payload, fullPath } = event.data || {};
391
-
392
- if (!isParent && isLifeCycleMessage(type)) {
393
- window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
394
- return
395
- }
396
-
397
- if (isParent && type === MESSAGE.SYNC_FROM_CHILD) {
398
- store.commit('base/SYNC_STATE', payload);
399
- return
400
- }
401
-
402
- if (!isParent && type === MESSAGE.SYNC_FROM_PARENT) {
403
- store.commit('base/SYNC_STATE', payload);
404
- return
405
- }
406
-
407
- if (isParent && type === MESSAGE.REQUEST_FROM_CHILD) {
408
- postToChild(iframeSelector, {
409
- type: MESSAGE.SYNC_FROM_PARENT,
410
- payload: latestBaseState,
411
- });
412
- return
413
- }
414
-
415
- if (isParent && type === MESSAGE.CHILD_ROUTE_CHANGE) {
416
- syncRouteFromChild(fullPath);
417
- return
418
- }
419
-
420
- if (isParent && !isInternalMessage(type) && !isBridgeMessage(type)) {
421
- window.parent?.postMessage(event.data || {}, '*');
422
- }
462
+ store.destroy = () => {
463
+ window.removeEventListener('message', messageListener);
464
+ unwatch?.();
465
+ isMounted = false;
423
466
  };
424
467
 
425
- window.addEventListener('message', handleMessage);
468
+ window.addEventListener('message', messageListener);
426
469
 
427
- if (!isParent && window.parent) {
428
- 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;
429
478
  }
479
+
480
+ return store
430
481
  }
431
482
  }
432
483
 
433
- var index = { initBridge, baseSyncPlugin };
484
+ function syncState(watch = {}) {
485
+ return function SYNC_STATE(state, payload) {
486
+ if (!payload || typeof payload !== 'object') return
487
+
488
+ Object.keys(watch).forEach(key => {
489
+ if (state[key] !== payload[key] && typeof watch[key] === 'function')
490
+ watch[key]({
491
+ store: this,
492
+ value: payload[key],
493
+ valueOld: state[key],
494
+ });
495
+ });
496
+ Object.keys(payload).forEach(key => {
497
+ if (state[key] !== payload[key]) state[key] = payload[key];
498
+ });
499
+ }
500
+ }
434
501
 
435
502
  exports.baseSyncPlugin = baseSyncPlugin;
436
503
  exports.createMicroAppCore = createMicroAppCore;
437
504
  exports.createRegisterHandlers = createRegisterHandlers;
438
- exports.default = index;
439
505
  exports.initBridge = initBridge;
506
+ exports.syncState = syncState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hhfenpm/micro-app",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "微前端通信桥接和状态同步工具,支持父子应用通信、状态同步、生命周期管理",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",