@hhfenpm/micro-app 1.0.7 → 1.0.9

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
@@ -54,7 +54,7 @@ const KEYS = [
54
54
  'get_version',
55
55
  ];
56
56
 
57
- function createCs(electron = {}) {
57
+ const createCs = (electron = {}) => {
58
58
  const base = Object.fromEntries(
59
59
  KEYS.map(k => [k, typeof electron[k] === 'function' ? electron[k] : noop])
60
60
  );
@@ -66,7 +66,7 @@ function createCs(electron = {}) {
66
66
  ? electron.is_app()
67
67
  : !!electron.is_app,
68
68
  }
69
- }
69
+ };
70
70
 
71
71
  const BRIDGE$1 = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
72
72
 
@@ -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,38 +117,41 @@ 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
  };
150
153
 
151
- function createRegisterHandlers(getElectron) {
154
+ const createRegisterHandlers = getElectron => {
152
155
  const electron = (() => {
153
156
  try {
154
157
  return typeof getElectron === 'function' ? getElectron() : {}
@@ -157,18 +160,18 @@ function createRegisterHandlers(getElectron) {
157
160
  }
158
161
  })();
159
162
  return vm => ({ ui: uiHandler(vm), cs: createCs(electron) })
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,15 +219,12 @@ 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
 
224
- function createMicroAppCore({
225
- modelMap,
226
- enabledModules,
227
- } = {}) {
227
+ const createMicroAppCore = ({ modelMap, enabledModules } = {}) => {
228
228
  if (!modelMap || typeof modelMap !== 'function') {
229
229
  throw new Error('modelMap function is required')
230
230
  }
@@ -259,7 +259,10 @@ function createMicroAppCore({
259
259
 
260
260
  const microAppDeployed = async module => {
261
261
  try {
262
- const res = await fetch(`/${module}`, { method: 'GET', cache: 'no-store' });
262
+ const res = await fetch(`/${module}`, {
263
+ method: 'GET',
264
+ cache: 'no-store',
265
+ });
263
266
  return res.ok
264
267
  } catch {
265
268
  return false
@@ -271,7 +274,27 @@ function createMicroAppCore({
271
274
  microAppSrc,
272
275
  microAppDeployed,
273
276
  }
274
- }
277
+ };
278
+
279
+ const mergeParentGlobalConfig = () => {
280
+ const getParentGlobalConfig = () => {
281
+ try {
282
+ if (
283
+ window.parent &&
284
+ window.parent !== window &&
285
+ window.parent.GLOBAL_CONFIG
286
+ ) {
287
+ return JSON.parse(JSON.stringify(window.parent.GLOBAL_CONFIG))
288
+ }
289
+ return null
290
+ } catch (e) {
291
+ return null
292
+ }
293
+ };
294
+ const parentGlobalConfig = getParentGlobalConfig();
295
+ if (parentGlobalConfig)
296
+ Object.assign(window.GLOBAL_CONFIG, parentGlobalConfig);
297
+ };
275
298
 
276
299
  const MESSAGE = {
277
300
  SYNC_FROM_CHILD: 'sync-base-from-child',
@@ -288,145 +311,195 @@ const LIFE_CYCLE = {
288
311
 
289
312
  const BRIDGE = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
290
313
 
291
- const LIFE_CYCLE_TYPES = Object.values(LIFE_CYCLE);
292
- const BRIDGE_TYPES = Object.values(BRIDGE);
314
+ const LIFE_CYCLE_SET = new Set(Object.values(LIFE_CYCLE));
315
+ const BRIDGE_SET = new Set(Object.values(BRIDGE));
316
+ const INTERNAL_SET = new Set([
317
+ MESSAGE.SYNC_FROM_CHILD,
318
+ MESSAGE.REQUEST_FROM_CHILD,
319
+ MESSAGE.CHILD_ROUTE_CHANGE,
320
+ ]);
293
321
 
294
322
  const postToChild = (iframeSelector, message) => {
295
- document.querySelector(iframeSelector)?.contentWindow?.postMessage(
296
- message,
297
- window.location.origin
298
- );
323
+ const iframe = document.querySelector(iframeSelector);
324
+ if (!iframe) return
325
+ iframe.contentWindow?.postMessage(message, window.location.origin);
299
326
  };
300
327
 
301
328
  const postToParent = message => {
302
329
  window.parent?.postMessage(message, window.location.origin);
303
330
  };
304
331
 
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
332
  const syncRouteFromChild = fullPath => {
317
333
  if (typeof fullPath !== 'string') return
334
+
318
335
  const { origin, pathname } = window.location;
319
- const base =
320
- pathname === '/' || pathname === ''
321
- ? origin
322
- : (origin + pathname).replace(/\/$/, '');
336
+ let base = origin;
337
+
338
+ if (pathname && pathname !== '/') {
339
+ base = (origin + pathname).replace(/\/$/, '');
340
+ }
341
+
323
342
  const target = `${base}/#${fullPath}`;
324
343
  if (window.location.href !== target) {
325
344
  window.location.href = target;
326
345
  }
327
346
  };
328
347
 
329
- function baseSyncPlugin({
330
- isParent = false,
348
+ const createMessageHandlers = (
349
+ store,
350
+ isBase,
351
+ iframeSelector,
352
+ getLatestBaseState
353
+ ) => {
354
+ const handlers = [];
355
+
356
+ if (!isBase) {
357
+ handlers.push((type, payload) => {
358
+ if (LIFE_CYCLE_SET.has(type)) {
359
+ window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
360
+ return true
361
+ }
362
+ return false
363
+ });
364
+ }
365
+
366
+ handlers.push((type, payload, fullPath) => {
367
+ if (type === MESSAGE.SYNC_FROM_CHILD && isBase) {
368
+ store.commit('base/SYNC_STATE', payload);
369
+ return true
370
+ }
371
+
372
+ if (type === MESSAGE.SYNC_FROM_PARENT && !isBase) {
373
+ store.commit('base/SYNC_STATE', payload);
374
+ return true
375
+ }
376
+
377
+ if (type === MESSAGE.REQUEST_FROM_CHILD && isBase) {
378
+ postToChild(iframeSelector, {
379
+ type: MESSAGE.SYNC_FROM_PARENT,
380
+ payload: getLatestBaseState(),
381
+ });
382
+ return true
383
+ }
384
+
385
+ if (type === MESSAGE.CHILD_ROUTE_CHANGE && isBase) {
386
+ syncRouteFromChild(fullPath);
387
+ return true
388
+ }
389
+
390
+ return false
391
+ });
392
+
393
+ handlers.push((type, data) => {
394
+ if (isBase && !INTERNAL_SET.has(type) && !BRIDGE_SET.has(type)) {
395
+ window.parent?.postMessage(data, '*');
396
+ return true
397
+ }
398
+ return false
399
+ });
400
+
401
+ return handlers
402
+ };
403
+
404
+ const baseSyncPlugin = ({
405
+ isBase = false,
331
406
  iframeSelector = '#microApp',
332
- } = {}) {
407
+ } = {}) => {
333
408
  return store => {
334
409
  let latestBaseState = store.state.base;
410
+ let isMounted = false;
411
+
412
+ const handleStateChange = () => {
413
+ if (!isMounted) return
414
+
415
+ const message = isBase
416
+ ? { type: MESSAGE.SYNC_FROM_PARENT, payload: latestBaseState }
417
+ : { type: MESSAGE.SYNC_FROM_CHILD, payload: latestBaseState };
418
+
419
+ isBase ? postToChild(iframeSelector, message) : postToParent(message);
420
+ };
335
421
 
336
- store.watch(
337
- state => state.base,
422
+ const unwatch = store.watch(
423
+ () => store.state.base,
338
424
  newBase => {
339
425
  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
- }
426
+ handleStateChange();
351
427
  },
352
428
  { deep: true }
353
429
  );
354
430
 
431
+ const messageHandlers = createMessageHandlers(
432
+ store,
433
+ isBase,
434
+ iframeSelector,
435
+ () => latestBaseState
436
+ );
437
+
438
+ const messageListener = event => {
439
+ if (event.origin !== window.location.origin) return
440
+
441
+ const { type, payload, fullPath } = event.data || {};
442
+ if (!type) return
443
+
444
+ for (const handler of messageHandlers) {
445
+ if (handler(type, payload, fullPath, event.data)) {
446
+ break
447
+ }
448
+ }
449
+ };
450
+
355
451
  store.attachRouterSync = router => {
356
- if (!router || isParent) return
357
- router.afterEach(to => {
452
+ if (!router || isBase) return
453
+
454
+ const routeHandler = to => {
358
455
  const parentHref = window.parent?.location?.href || '';
359
- const parentFullPath = parentHref.includes('#')
360
- ? parentHref.split('#')[1]
361
- : '';
456
+ const hashIndex = parentHref.indexOf('#');
457
+ const parentFullPath =
458
+ hashIndex > -1 ? parentHref.slice(hashIndex + 1) : '';
362
459
  const childFullPath = to.fullPath.startsWith('/')
363
460
  ? to.fullPath
364
461
  : '/' + to.fullPath;
462
+
365
463
  if (parentFullPath !== childFullPath) {
366
464
  postToParent({
367
465
  type: MESSAGE.CHILD_ROUTE_CHANGE,
368
466
  fullPath: to.fullPath,
369
467
  });
370
468
  }
371
- });
469
+ };
470
+
471
+ router.afterEach(routeHandler);
372
472
  };
373
473
 
374
474
  store.callMicroAppLifeCycle = (type, payload) => {
375
- if (!isParent) return
376
- if (!isLifeCycleMessage(type)) return
475
+ if (!isBase || !LIFE_CYCLE_SET.has(type)) return
377
476
 
378
- postToChild(iframeSelector, {
379
- type,
380
- payload,
381
- });
477
+ postToChild(iframeSelector, { type, payload });
382
478
  };
383
479
 
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
- }
480
+ store.destroy = () => {
481
+ window.removeEventListener('message', messageListener);
482
+ unwatch?.();
483
+ isMounted = false;
419
484
  };
420
485
 
421
- window.addEventListener('message', handleMessage);
486
+ window.addEventListener('message', messageListener);
422
487
 
423
- if (!isParent && window.parent) {
424
- postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
488
+ if (!isBase && window.parent) {
489
+ const requestTimer = setTimeout(() => {
490
+ postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
491
+ isMounted = true;
492
+ clearTimeout(requestTimer);
493
+ }, 0);
494
+ } else {
495
+ isMounted = true;
425
496
  }
497
+
498
+ return store
426
499
  }
427
- }
500
+ };
428
501
 
429
- function syncState(watch = {}) {
502
+ const syncState = (watch = {}) => {
430
503
  return function SYNC_STATE(state, payload) {
431
504
  if (!payload || typeof payload !== 'object') return
432
505
 
@@ -442,6 +515,6 @@ function syncState(watch = {}) {
442
515
  if (state[key] !== payload[key]) state[key] = payload[key];
443
516
  });
444
517
  }
445
- }
518
+ };
446
519
 
447
- export { baseSyncPlugin, createMicroAppCore, createRegisterHandlers, initBridge, syncState };
520
+ export { baseSyncPlugin, createMicroAppCore, createRegisterHandlers, initBridge, mergeParentGlobalConfig, syncState };
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ const KEYS = [
56
56
  'get_version',
57
57
  ];
58
58
 
59
- function createCs(electron = {}) {
59
+ const createCs = (electron = {}) => {
60
60
  const base = Object.fromEntries(
61
61
  KEYS.map(k => [k, typeof electron[k] === 'function' ? electron[k] : noop])
62
62
  );
@@ -68,7 +68,7 @@ function createCs(electron = {}) {
68
68
  ? electron.is_app()
69
69
  : !!electron.is_app,
70
70
  }
71
- }
71
+ };
72
72
 
73
73
  const BRIDGE$1 = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
74
74
 
@@ -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,38 +119,41 @@ 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
  };
152
155
 
153
- function createRegisterHandlers(getElectron) {
156
+ const createRegisterHandlers = getElectron => {
154
157
  const electron = (() => {
155
158
  try {
156
159
  return typeof getElectron === 'function' ? getElectron() : {}
@@ -159,18 +162,18 @@ function createRegisterHandlers(getElectron) {
159
162
  }
160
163
  })();
161
164
  return vm => ({ ui: uiHandler(vm), cs: createCs(electron) })
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,15 +221,12 @@ 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
 
226
- function createMicroAppCore({
227
- modelMap,
228
- enabledModules,
229
- } = {}) {
229
+ const createMicroAppCore = ({ modelMap, enabledModules } = {}) => {
230
230
  if (!modelMap || typeof modelMap !== 'function') {
231
231
  throw new Error('modelMap function is required')
232
232
  }
@@ -261,7 +261,10 @@ function createMicroAppCore({
261
261
 
262
262
  const microAppDeployed = async module => {
263
263
  try {
264
- const res = await fetch(`/${module}`, { method: 'GET', cache: 'no-store' });
264
+ const res = await fetch(`/${module}`, {
265
+ method: 'GET',
266
+ cache: 'no-store',
267
+ });
265
268
  return res.ok
266
269
  } catch {
267
270
  return false
@@ -273,7 +276,27 @@ function createMicroAppCore({
273
276
  microAppSrc,
274
277
  microAppDeployed,
275
278
  }
276
- }
279
+ };
280
+
281
+ const mergeParentGlobalConfig = () => {
282
+ const getParentGlobalConfig = () => {
283
+ try {
284
+ if (
285
+ window.parent &&
286
+ window.parent !== window &&
287
+ window.parent.GLOBAL_CONFIG
288
+ ) {
289
+ return JSON.parse(JSON.stringify(window.parent.GLOBAL_CONFIG))
290
+ }
291
+ return null
292
+ } catch (e) {
293
+ return null
294
+ }
295
+ };
296
+ const parentGlobalConfig = getParentGlobalConfig();
297
+ if (parentGlobalConfig)
298
+ Object.assign(window.GLOBAL_CONFIG, parentGlobalConfig);
299
+ };
277
300
 
278
301
  const MESSAGE = {
279
302
  SYNC_FROM_CHILD: 'sync-base-from-child',
@@ -290,145 +313,195 @@ const LIFE_CYCLE = {
290
313
 
291
314
  const BRIDGE = { INVOKE: 'bridge-invoke', CALLBACK: 'bridge-callback' };
292
315
 
293
- const LIFE_CYCLE_TYPES = Object.values(LIFE_CYCLE);
294
- const BRIDGE_TYPES = Object.values(BRIDGE);
316
+ const LIFE_CYCLE_SET = new Set(Object.values(LIFE_CYCLE));
317
+ const BRIDGE_SET = new Set(Object.values(BRIDGE));
318
+ const INTERNAL_SET = new Set([
319
+ MESSAGE.SYNC_FROM_CHILD,
320
+ MESSAGE.REQUEST_FROM_CHILD,
321
+ MESSAGE.CHILD_ROUTE_CHANGE,
322
+ ]);
295
323
 
296
324
  const postToChild = (iframeSelector, message) => {
297
- document.querySelector(iframeSelector)?.contentWindow?.postMessage(
298
- message,
299
- window.location.origin
300
- );
325
+ const iframe = document.querySelector(iframeSelector);
326
+ if (!iframe) return
327
+ iframe.contentWindow?.postMessage(message, window.location.origin);
301
328
  };
302
329
 
303
330
  const postToParent = message => {
304
331
  window.parent?.postMessage(message, window.location.origin);
305
332
  };
306
333
 
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
334
  const syncRouteFromChild = fullPath => {
319
335
  if (typeof fullPath !== 'string') return
336
+
320
337
  const { origin, pathname } = window.location;
321
- const base =
322
- pathname === '/' || pathname === ''
323
- ? origin
324
- : (origin + pathname).replace(/\/$/, '');
338
+ let base = origin;
339
+
340
+ if (pathname && pathname !== '/') {
341
+ base = (origin + pathname).replace(/\/$/, '');
342
+ }
343
+
325
344
  const target = `${base}/#${fullPath}`;
326
345
  if (window.location.href !== target) {
327
346
  window.location.href = target;
328
347
  }
329
348
  };
330
349
 
331
- function baseSyncPlugin({
332
- isParent = false,
350
+ const createMessageHandlers = (
351
+ store,
352
+ isBase,
353
+ iframeSelector,
354
+ getLatestBaseState
355
+ ) => {
356
+ const handlers = [];
357
+
358
+ if (!isBase) {
359
+ handlers.push((type, payload) => {
360
+ if (LIFE_CYCLE_SET.has(type)) {
361
+ window.__MICRO_APP_LIFECYCLE__?.[type]?.(payload);
362
+ return true
363
+ }
364
+ return false
365
+ });
366
+ }
367
+
368
+ handlers.push((type, payload, fullPath) => {
369
+ if (type === MESSAGE.SYNC_FROM_CHILD && isBase) {
370
+ store.commit('base/SYNC_STATE', payload);
371
+ return true
372
+ }
373
+
374
+ if (type === MESSAGE.SYNC_FROM_PARENT && !isBase) {
375
+ store.commit('base/SYNC_STATE', payload);
376
+ return true
377
+ }
378
+
379
+ if (type === MESSAGE.REQUEST_FROM_CHILD && isBase) {
380
+ postToChild(iframeSelector, {
381
+ type: MESSAGE.SYNC_FROM_PARENT,
382
+ payload: getLatestBaseState(),
383
+ });
384
+ return true
385
+ }
386
+
387
+ if (type === MESSAGE.CHILD_ROUTE_CHANGE && isBase) {
388
+ syncRouteFromChild(fullPath);
389
+ return true
390
+ }
391
+
392
+ return false
393
+ });
394
+
395
+ handlers.push((type, data) => {
396
+ if (isBase && !INTERNAL_SET.has(type) && !BRIDGE_SET.has(type)) {
397
+ window.parent?.postMessage(data, '*');
398
+ return true
399
+ }
400
+ return false
401
+ });
402
+
403
+ return handlers
404
+ };
405
+
406
+ const baseSyncPlugin = ({
407
+ isBase = false,
333
408
  iframeSelector = '#microApp',
334
- } = {}) {
409
+ } = {}) => {
335
410
  return store => {
336
411
  let latestBaseState = store.state.base;
412
+ let isMounted = false;
413
+
414
+ const handleStateChange = () => {
415
+ if (!isMounted) return
416
+
417
+ const message = isBase
418
+ ? { type: MESSAGE.SYNC_FROM_PARENT, payload: latestBaseState }
419
+ : { type: MESSAGE.SYNC_FROM_CHILD, payload: latestBaseState };
420
+
421
+ isBase ? postToChild(iframeSelector, message) : postToParent(message);
422
+ };
337
423
 
338
- store.watch(
339
- state => state.base,
424
+ const unwatch = store.watch(
425
+ () => store.state.base,
340
426
  newBase => {
341
427
  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
- }
428
+ handleStateChange();
353
429
  },
354
430
  { deep: true }
355
431
  );
356
432
 
433
+ const messageHandlers = createMessageHandlers(
434
+ store,
435
+ isBase,
436
+ iframeSelector,
437
+ () => latestBaseState
438
+ );
439
+
440
+ const messageListener = event => {
441
+ if (event.origin !== window.location.origin) return
442
+
443
+ const { type, payload, fullPath } = event.data || {};
444
+ if (!type) return
445
+
446
+ for (const handler of messageHandlers) {
447
+ if (handler(type, payload, fullPath, event.data)) {
448
+ break
449
+ }
450
+ }
451
+ };
452
+
357
453
  store.attachRouterSync = router => {
358
- if (!router || isParent) return
359
- router.afterEach(to => {
454
+ if (!router || isBase) return
455
+
456
+ const routeHandler = to => {
360
457
  const parentHref = window.parent?.location?.href || '';
361
- const parentFullPath = parentHref.includes('#')
362
- ? parentHref.split('#')[1]
363
- : '';
458
+ const hashIndex = parentHref.indexOf('#');
459
+ const parentFullPath =
460
+ hashIndex > -1 ? parentHref.slice(hashIndex + 1) : '';
364
461
  const childFullPath = to.fullPath.startsWith('/')
365
462
  ? to.fullPath
366
463
  : '/' + to.fullPath;
464
+
367
465
  if (parentFullPath !== childFullPath) {
368
466
  postToParent({
369
467
  type: MESSAGE.CHILD_ROUTE_CHANGE,
370
468
  fullPath: to.fullPath,
371
469
  });
372
470
  }
373
- });
471
+ };
472
+
473
+ router.afterEach(routeHandler);
374
474
  };
375
475
 
376
476
  store.callMicroAppLifeCycle = (type, payload) => {
377
- if (!isParent) return
378
- if (!isLifeCycleMessage(type)) return
477
+ if (!isBase || !LIFE_CYCLE_SET.has(type)) return
379
478
 
380
- postToChild(iframeSelector, {
381
- type,
382
- payload,
383
- });
479
+ postToChild(iframeSelector, { type, payload });
384
480
  };
385
481
 
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
- }
482
+ store.destroy = () => {
483
+ window.removeEventListener('message', messageListener);
484
+ unwatch?.();
485
+ isMounted = false;
421
486
  };
422
487
 
423
- window.addEventListener('message', handleMessage);
488
+ window.addEventListener('message', messageListener);
424
489
 
425
- if (!isParent && window.parent) {
426
- postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
490
+ if (!isBase && window.parent) {
491
+ const requestTimer = setTimeout(() => {
492
+ postToParent({ type: MESSAGE.REQUEST_FROM_CHILD });
493
+ isMounted = true;
494
+ clearTimeout(requestTimer);
495
+ }, 0);
496
+ } else {
497
+ isMounted = true;
427
498
  }
499
+
500
+ return store
428
501
  }
429
- }
502
+ };
430
503
 
431
- function syncState(watch = {}) {
504
+ const syncState = (watch = {}) => {
432
505
  return function SYNC_STATE(state, payload) {
433
506
  if (!payload || typeof payload !== 'object') return
434
507
 
@@ -444,10 +517,11 @@ function syncState(watch = {}) {
444
517
  if (state[key] !== payload[key]) state[key] = payload[key];
445
518
  });
446
519
  }
447
- }
520
+ };
448
521
 
449
522
  exports.baseSyncPlugin = baseSyncPlugin;
450
523
  exports.createMicroAppCore = createMicroAppCore;
451
524
  exports.createRegisterHandlers = createRegisterHandlers;
452
525
  exports.initBridge = initBridge;
526
+ exports.mergeParentGlobalConfig = mergeParentGlobalConfig;
453
527
  exports.syncState = syncState;
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.9",
4
4
  "description": "微前端通信桥接和状态同步工具,支持父子应用通信、状态同步、生命周期管理",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",