@hhfenpm/micro-app 1.0.4 → 1.0.6

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
@@ -1,6 +1,8 @@
1
1
  # @hhfenpm/micro-app
2
2
 
3
- 微前端通信桥接和状态同步工具,支持父子应用之间的通信、状态同步和生命周期管理。
3
+ 基于 iframe + postMessage 的微前端工具:父子应用**通信桥接**、**Vuex 状态同步**、**生命周期**与**路由同步**。
4
+
5
+ **兼容 IE9+**
4
6
 
5
7
  ## 安装
6
8
 
@@ -10,202 +12,203 @@ npm install @hhfenpm/micro-app
10
12
  yarn add @hhfenpm/micro-app
11
13
  ```
12
14
 
13
- ## 功能特性
15
+ ## 能力概览
16
+
17
+ | 能力 | 说明 |
18
+ |------|------|
19
+ | **通信桥接** | 子应用通过 `vm.$base.命名空间.方法(params)` 调用父应用方法,支持 Promise |
20
+ | **状态同步** | 父子应用 Vuex `base` 模块双向同步 |
21
+ | **生命周期** | 父应用调用 `mount/update/unmount`,子应用通过 `window.__MICRO_APP_LIFECYCLE__` 响应 |
22
+ | **路由同步** | 子应用路由变化同步到父应用 URL(需子应用调用 `store.attachRouterSync(router)`) |
14
23
 
15
- - 🔗 **通信桥接**:支持父子应用之间的方法调用和通信
16
- - 🔄 **状态同步**:自动同步 Vuex store 状态
17
- - 🎯 **生命周期管理**:支持微前端生命周期(mount、update、unmount)
18
- - 🛣️ **路由同步**:自动同步子应用路由到父应用
19
- - ⚙️ **可配置**:支持自定义处理器、状态映射等
24
+ ---
20
25
 
21
- ## 使用方式
26
+ ## 一、Bridge(通信桥接)
22
27
 
23
- ### 1. 初始化通信桥接
28
+ ### 父应用
24
29
 
25
- #### 父应用
30
+ 父应用需提供 `handlers`:`{ 命名空间: { 方法名: 函数 } }`。可使用内置 `createRegisterHandlers` 生成 `ui`(Element UI 等)和 `cs`(Electron)两个命名空间:
26
31
 
27
32
  ```javascript
28
- import { initBridge } from '@hhfenpm/micro-app'
29
- import Vue from 'vue'
33
+ import { initBridge, createRegisterHandlers } from '@hhfenpm/micro-app'
30
34
 
31
35
  const vm = new Vue({ /* ... */ })
32
36
 
33
- // 创建处理器(项目特定)
34
- const handlers = {
35
- elementUI: {
36
- $message: (...args) => vm.$message(...args),
37
- // ... 其他 Element UI 方法
38
- },
39
- electron: {
40
- // ... Electron 方法
41
- },
42
- }
37
+ // getElectron:返回 electron 模块,无则 () => ({}) 或 () => require('@/utils/electron')
38
+ const getElectron = () => ({}) // 或 () => require('@/utils/electron')
39
+ const toHandlers = createRegisterHandlers(getElectron)
43
40
 
44
- // 初始化桥接
45
41
  initBridge({
46
42
  isParent: true,
47
43
  vm,
48
- handlers,
44
+ handlers: toHandlers(vm),
49
45
  iframeSelector: '#microApp', // 可选,默认 '#microApp'
50
46
  })
51
47
  ```
52
48
 
53
- #### 子应用
49
+ `createRegisterHandlers(getElectron)` 返回 `(vm) => ({ ui, cs })`:
50
+
51
+ - **ui**:`$message`、`$success`、`$warning`、`$error`、`$notify`、`$confirm`、`$alert`、`$prompt`、`$loading`
52
+ - **cs**:Electron 能力(由 `getElectron()` 提供,缺失的方法为 noop)
53
+
54
+ ### 子应用
54
55
 
55
56
  ```javascript
56
57
  import { initBridge } from '@hhfenpm/micro-app'
57
- import Vue from 'vue'
58
58
 
59
59
  const vm = new Vue({ /* ... */ })
60
60
 
61
- // 初始化桥接
62
- initBridge({
63
- isParent: false,
64
- vm,
65
- })
61
+ initBridge({ isParent: false, vm })
66
62
 
67
- // 使用 $base 调用父应用方法
68
- vm.$base.elementUI.$message('Hello from child app')
69
- vm.$base.electron.show()
63
+ // 调用父应用:vm.$base.命名空间.方法(params),返回 Promise
64
+ vm.$base.ui.$message('来自子应用')
65
+ vm.$base.ui.$confirm('确认?').then(ok => { /* ... */ })
66
+ vm.$base.cs.show()
70
67
  ```
71
68
 
72
- ### 2. 使用核心功能
69
+ ---
70
+
71
+ ## 二、Core(子应用 URL / 部署检测)
72
+
73
+ 根据「路径 → 模块名」映射、以及「启用模块列表」,计算当前路由是否命中某子应用、其入口 URL、是否已部署。
73
74
 
74
75
  ```javascript
75
76
  import { createMicroAppCore } from '@hhfenpm/micro-app'
76
77
 
77
- // 定义模块路由映射
78
- const ModelMap = () => ({
79
- 'module1': ['/module1', '/m1'],
80
- 'module2': ['/module2'],
78
+ const modelMap = () => ({
79
+ 'disease-analysis': ['/disease-analysis', '/ds-consult'],
80
+ 'health': ['/health-manage'],
81
81
  })
82
82
 
83
- // 创建核心功能实例
84
83
  const core = createMicroAppCore({
85
- modelMap: ModelMap,
86
- enabledModules: () => window.GLOBAL_CONFIG?.microApp || null,
84
+ modelMap,
85
+ enabledModules: () => window.GLOBAL_CONFIG?.microApp || null, // 可选,不传则用 GLOBAL_CONFIG.microApp
87
86
  })
88
87
 
89
- // 使用
90
- const module = core.microAppModule('/module1')
91
- const src = core.microAppSrc('/module1')
92
- const deployed = await core.microAppDeployed('module1')
88
+ // 根据路径得到模块名,未命中或未启用则为 null
89
+ const module = core.microAppModule('/disease-analysis') // 'disease-analysis'
90
+
91
+ // 子应用完整 URL(含 hash)
92
+ const src = core.microAppSrc('/disease-analysis')
93
+
94
+ // 是否已部署(HEAD /module 可访问)
95
+ const ok = await core.microAppDeployed('disease-analysis')
93
96
  ```
94
97
 
95
- ### 3. 使用状态同步插件
98
+ ---
99
+
100
+ ## 三、Store 插件(状态同步)
101
+
102
+ 在 Vuex 中注册 `baseSyncPlugin`,并在**子应用**中调用 `store.attachRouterSync(router)` 以同步路由。
103
+
104
+ ### 父应用
96
105
 
97
106
  ```javascript
98
107
  import Vuex from 'vuex'
99
108
  import { baseSyncPlugin } from '@hhfenpm/micro-app'
109
+ import base from './store/modules/base' // 需含 base/SYNC_STATE
100
110
 
101
- // 创建 Vuex Store
102
111
  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
- ],
112
+ modules: { base },
113
+ plugins: [baseSyncPlugin({ isParent: true, iframeSelector: '#microApp' })],
114
+ })
115
+ ```
116
+
117
+ ### 子应用
118
+
119
+ ```javascript
120
+ import { baseSyncPlugin } from '@hhfenpm/micro-app'
121
+ import base from './store/modules/base'
122
+
123
+ const store = new Vuex.Store({
124
+ modules: { base },
125
+ plugins: [baseSyncPlugin({ isParent: false, iframeSelector: '#microApp' })],
122
126
  })
123
127
 
124
- // 子应用附加路由同步
125
128
  import router from './router'
126
- store.attachRouterSync(router)
129
+ store.attachRouterSync(router) // 必调,用于把子应用路由同步到父应用
127
130
  ```
128
131
 
129
- ### 4. 生命周期管理
132
+ `base` 模块需提供 `base/SYNC_STATE` mutation,用于接收并合并同步过来的 state。
133
+
134
+ ---
130
135
 
131
- #### 子应用
136
+ ## 四、生命周期
137
+
138
+ ### 子应用
139
+
140
+ 在子应用入口注册:
132
141
 
133
142
  ```javascript
134
- // 注册生命周期钩子
135
143
  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
- },
144
+ mount(payload) { /* 挂载后 */ },
145
+ update(payload) { /* 更新时,如路由变化 */ },
146
+ unmount(payload) { /* 卸载前 */ },
145
147
  }
146
148
  ```
147
149
 
148
- #### 父应用
150
+ ### 父应用
151
+
152
+ 由 `baseSyncPlugin` 在 store 上挂载 `callMicroAppLifeCycle`,在 iframe 加载完成、路由变化、子应用切换时调用:
149
153
 
150
154
  ```javascript
151
- // 调用生命周期
152
- store.callMicroAppLifeCycle('mount', { /* payload */ })
153
- store.callMicroAppLifeCycle('update', { /* payload */ })
154
- store.callMicroAppLifeCycle('unmount', { /* payload */ })
155
+ store.callMicroAppLifeCycle('mount', { route: '/xxx' })
156
+ store.callMicroAppLifeCycle('update', { route: '/yyy' })
157
+ store.callMicroAppLifeCycle('unmount')
155
158
  ```
156
159
 
157
- ## API 文档
158
-
159
- ### Bridge
160
+ ---
160
161
 
161
- #### `initBridge(options)`
162
+ ## API 一览
162
163
 
163
- 初始化通信桥接。
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` |
164
170
 
165
- **参数:**
166
- - `options.isParent` (boolean): 是否为父应用
167
- - `options.vm` (Object): Vue 实例
168
- - `options.iframeSelector` (string): iframe 选择器,默认 `'#microApp'`
169
- - `options.handlers` (Object): 处理器对象(父应用需要)
171
+ ### 参数说明
170
172
 
171
- ### Core
173
+ - **initBridge**
174
+ - `isParent`: 是否父应用
175
+ - `vm`: Vue 实例(子应用会在其上挂 `vm.$base`、`vm.$baseReady`)
176
+ - `iframeSelector`: 父应用 iframe 选择器,默认 `'#microApp'`
177
+ - `handlers`: 仅父应用需要,`{ [namespace]: { [method]: (params)=>any } }`
172
178
 
173
- #### `createMicroAppCore(options)`
179
+ - **createRegisterHandlers**
180
+ - `getElectron`: `() => electronModule`,可选;异常或未传时 `cs` 使用 noop
174
181
 
175
- 创建微前端核心功能实例。
182
+ - **createMicroAppCore**
183
+ - `modelMap`: `() => ({ [moduleName]: path[] })`,必填
184
+ - `enabledModules`: `() => string[]` 或 `string[]`,可选;缺省时从 `window.GLOBAL_CONFIG.microApp` 读取
176
185
 
177
- **参数:**
178
- - `options.modelMap` (Function): 模块路由映射函数,返回 `{ [moduleName]: string[] }`
179
- - `options.enabledModules` (Array|Function): 启用的模块列表或获取模块列表的函数
186
+ - **baseSyncPlugin**
187
+ - `isParent`: 是否父应用
188
+ - `iframeSelector`: 默认 `'#microApp'`
180
189
 
181
- **返回:**
182
- - `microAppModule(path)`: 根据路径获取模块名
183
- - `microAppSrc(path)`: 生成完整 URL
184
- - `microAppDeployed(module)`: 检测模块是否已部署
190
+ ---
185
191
 
186
- ### Store
187
-
188
- #### `baseSyncPlugin(options)`
189
-
190
- 创建状态同步插件。
192
+ ## 注意事项
191
193
 
192
- **参数:**
193
- - `options.isParent` (boolean): 是否为父应用
194
- - `options.iframeSelector` (string): iframe 选择器
194
+ 1. **同源**:postMessage 仅在同源下使用,需校验 `event.origin`。
195
+ 2. **base 模块**:`baseSyncPlugin` 通过 `base/SYNC_STATE` 同步;`base` 的 state 结构需与约定一致。
196
+ 3. **子应用必调**:`store.attachRouterSync(router)`,否则子应用路由不会回写到父应用 URL。
197
+ 4. **IE9 兼容**:本包已针对 IE9 进行了兼容处理,需要确保项目配置了 Babel 转译和 core-js polyfill。
195
198
 
196
- ## 浏览器兼容性
199
+ ---
197
200
 
198
- - 现代浏览器(Chrome, Firefox, Safari, Edge)
199
- - 需要支持 `postMessage` API
200
- - 需要支持 `Proxy` API(IE 11 不支持)
201
+ ## 兼容性
201
202
 
202
- ## 注意事项
203
+ - **浏览器支持**:IE9+、现代浏览器(Chrome、Firefox、Safari、Edge)
204
+ - **依赖要求**:
205
+ - Vue 2.6+ / 3.x,Vuex 3.x / 4.x(peerDependencies)
206
+ - **项目配置要求**:
207
+ - 需要配置 Babel 转译(`transpileDependencies: true`)
208
+ - 需要配置 core-js polyfill(Promise、Object.fromEntries、Array.includes 等)
203
209
 
204
- 1. **安全性**:消息通信会检查 `origin`,确保只在同源窗口间通信
205
- 2. **状态同步**:状态同步使用深拷贝,避免引用污染
206
- 3. **生命周期**:子应用需要注册 `window.__MICRO_APP_LIFECYCLE__` 对象
207
- 4. **路由同步**:子应用需要调用 `store.attachRouterSync(router)` 附加路由同步
210
+ ---
208
211
 
209
- ## 许可证
212
+ ## 许可
210
213
 
211
214
  MIT
package/dist/index.esm.js CHANGED
@@ -86,6 +86,68 @@ const safeClone = value => {
86
86
  }
87
87
  };
88
88
 
89
+ const createBaseObject = pending => {
90
+ const namespaces = {};
91
+ const hasProxy = typeof Proxy !== 'undefined';
92
+
93
+ const createMethod = (namespace, method) => {
94
+ return params =>
95
+ new Promise(resolve => {
96
+ const callbackId = genId();
97
+ pending[callbackId] = resolve;
98
+
99
+ post(window.parent, {
100
+ type: BRIDGE$1.INVOKE,
101
+ action: `${namespace}.${method}`,
102
+ params,
103
+ callbackId,
104
+ });
105
+ })
106
+ };
107
+
108
+ if (hasProxy) {
109
+ const base = {};
110
+ return new Proxy(base, {
111
+ get(target, namespace) {
112
+ if (typeof namespace === 'string') {
113
+ if (!namespaces[namespace]) {
114
+ namespaces[namespace] = {};
115
+ }
116
+ return new Proxy(namespaces[namespace], {
117
+ get(target2, method) {
118
+ if (typeof method === 'string') {
119
+ if (!namespaces[namespace][method]) {
120
+ namespaces[namespace][method] = createMethod(namespace, method);
121
+ }
122
+ return namespaces[namespace][method]
123
+ }
124
+ return undefined
125
+ }
126
+ })
127
+ }
128
+ return undefined
129
+ }
130
+ })
131
+ } else {
132
+ const base = {};
133
+ const commonNamespaces = ['ui', 'cs'];
134
+
135
+ commonNamespaces.forEach(ns => {
136
+ namespaces[ns] = {};
137
+ const nsObj = {};
138
+
139
+ Object.defineProperty(base, ns, {
140
+ get() {
141
+ return nsObj
142
+ },
143
+ configurable: true
144
+ });
145
+ });
146
+
147
+ return base
148
+ }
149
+ };
150
+
89
151
  function createRegisterHandlers(getElectron) {
90
152
  const electron = (() => {
91
153
  try {
@@ -107,33 +169,8 @@ const initBridge = ({
107
169
  const pending = Object.create(null);
108
170
 
109
171
  if (!isParent && vm) {
110
- vm.$base = new Proxy(
111
- {},
112
- {
113
- get(_, namespace) {
114
- return new Proxy(
115
- {},
116
- {
117
- get(_, method) {
118
- return params =>
119
- new Promise(resolve => {
120
- const callbackId = genId();
121
- pending[callbackId] = resolve;
122
-
123
- post(window.parent, {
124
- type: BRIDGE$1.INVOKE,
125
- action: `${namespace}.${method}`,
126
- params,
127
- callbackId,
128
- });
129
- })
130
- },
131
- }
132
- )
133
- },
134
- }
135
- );
136
-
172
+ const baseObj = createBaseObject(pending);
173
+ vm.$base = baseObj;
137
174
  vm.$baseReady = true;
138
175
  }
139
176
 
package/dist/index.js CHANGED
@@ -90,6 +90,68 @@ const safeClone = value => {
90
90
  }
91
91
  };
92
92
 
93
+ const createBaseObject = pending => {
94
+ const namespaces = {};
95
+ const hasProxy = typeof Proxy !== 'undefined';
96
+
97
+ const createMethod = (namespace, method) => {
98
+ return params =>
99
+ new Promise(resolve => {
100
+ const callbackId = genId();
101
+ pending[callbackId] = resolve;
102
+
103
+ post(window.parent, {
104
+ type: BRIDGE$1.INVOKE,
105
+ action: `${namespace}.${method}`,
106
+ params,
107
+ callbackId,
108
+ });
109
+ })
110
+ };
111
+
112
+ if (hasProxy) {
113
+ const base = {};
114
+ return new Proxy(base, {
115
+ get(target, namespace) {
116
+ if (typeof namespace === 'string') {
117
+ if (!namespaces[namespace]) {
118
+ namespaces[namespace] = {};
119
+ }
120
+ return new Proxy(namespaces[namespace], {
121
+ get(target2, method) {
122
+ if (typeof method === 'string') {
123
+ if (!namespaces[namespace][method]) {
124
+ namespaces[namespace][method] = createMethod(namespace, method);
125
+ }
126
+ return namespaces[namespace][method]
127
+ }
128
+ return undefined
129
+ }
130
+ })
131
+ }
132
+ return undefined
133
+ }
134
+ })
135
+ } else {
136
+ const base = {};
137
+ const commonNamespaces = ['ui', 'cs'];
138
+
139
+ commonNamespaces.forEach(ns => {
140
+ namespaces[ns] = {};
141
+ const nsObj = {};
142
+
143
+ Object.defineProperty(base, ns, {
144
+ get() {
145
+ return nsObj
146
+ },
147
+ configurable: true
148
+ });
149
+ });
150
+
151
+ return base
152
+ }
153
+ };
154
+
93
155
  function createRegisterHandlers(getElectron) {
94
156
  const electron = (() => {
95
157
  try {
@@ -111,33 +173,8 @@ const initBridge = ({
111
173
  const pending = Object.create(null);
112
174
 
113
175
  if (!isParent && vm) {
114
- vm.$base = new Proxy(
115
- {},
116
- {
117
- get(_, namespace) {
118
- return new Proxy(
119
- {},
120
- {
121
- get(_, method) {
122
- return params =>
123
- new Promise(resolve => {
124
- const callbackId = genId();
125
- pending[callbackId] = resolve;
126
-
127
- post(window.parent, {
128
- type: BRIDGE$1.INVOKE,
129
- action: `${namespace}.${method}`,
130
- params,
131
- callbackId,
132
- });
133
- })
134
- },
135
- }
136
- )
137
- },
138
- }
139
- );
140
-
176
+ const baseObj = createBaseObject(pending);
177
+ vm.$base = baseObj;
141
178
  vm.$baseReady = true;
142
179
  }
143
180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hhfenpm/micro-app",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "微前端通信桥接和状态同步工具,支持父子应用通信、状态同步、生命周期管理",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",