@xingtukeji/micro 1.2.0 → 1.2.2

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,35 +1,70 @@
1
- 主应用初始化:
1
+ # @xingtukeji/micro
2
+
3
+ 基于 Vue 3 和 iframe 的微前端容器,支持应用注册、空闲预加载、保活切换、主子应用路由同步和生命周期通知。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add @xingtukeji/micro
9
+ ```
10
+
11
+ ## 主应用接入
12
+
13
+ 插件必须在 Pinia 等 token 依赖初始化完成后安装,并在 Host 挂载前注册所有子应用:
14
+
15
+ ```ts
16
+ // menu: [
17
+ // {
18
+ // title: "预加载子应用",
19
+ // type: "micro",
20
+ // microOptions: {
21
+ // url: "http://127.0.0.1:12001",
22
+ // name: "childapp",
23
+ // preload: true,
24
+ // keepAlive: true,
25
+ // },
26
+ // },
27
+ // ]
2
28
 
3
- ```javascript
4
- //主微应用加载插件,注意一定要在 pinia 初始化之后
5
29
  import xtMicro, { setupApp } from "@xingtukeji/micro";
30
+
6
31
  app.use(xtMicro);
7
- // 遍历菜单函数
8
- function traverseMenu(menu) {
9
- // 遍历菜单项
32
+
33
+ function traverseMenu(menu: any[]) {
10
34
  menu.forEach((item) => {
11
- // 对微应用的菜单做处理
12
35
  if (item.type === "micro") {
13
- // 设置应用
14
36
  setupApp({
15
37
  ...item.microOptions,
16
38
  token: () => useUserStore().token,
17
- params: item.microOptions?.params || {},
39
+ params: item.microOptions?.params ?? {},
18
40
  });
19
41
  }
20
- // 如果当前菜单项有子菜单,则递归遍历子菜单
21
- if (item.children) {
22
- traverseMenu(item.children);
23
- }
42
+ if (item.children) traverseMenu(item.children);
24
43
  });
25
44
  }
45
+
26
46
  traverseMenu(window.$config.menus);
27
47
  ```
28
48
 
29
- 推荐在不会被路由销毁的布局中挂载 Host:
49
+ `setupApp` 配置:
50
+
51
+ | 参数 | 类型 | 默认值 | 说明 |
52
+ | --- | --- | --- | --- |
53
+ | `name` | `string` | 必填 | 应用唯一名称 |
54
+ | `url` | `string` | 必填 | 子应用入口地址,支持相对地址 |
55
+ | `preload` | `boolean` | `false` | 浏览器空闲时预加载 |
56
+ | `keepAlive` | `boolean` | `false` | 切换离开后保留 iframe 和子应用内存状态 |
57
+ | `sync` | `boolean` | `true` | 将子应用路由同步到主应用 URL |
58
+ | `token` | `string \| () => string` | - | 注入子应用 URL 的 token |
59
+ | `params` | `Record<string, any>` | `{}` | 注入子应用 URL 的查询参数 |
60
+ | `onLoad` / `onUnload` / `onError` | `(event, app) => void` | - | iframe 生命周期回调 |
61
+
62
+ ### 挂载 Host
63
+
64
+ 推荐把 `XTMicroHost` 放在不会被路由销毁的布局中:
30
65
 
31
66
  ```vue
32
- <script setup>
67
+ <script setup lang="ts">
33
68
  import { computed } from "vue";
34
69
  import { useRoute } from "vue-router";
35
70
  import { XTMicroHost } from "@xingtukeji/micro";
@@ -48,48 +83,115 @@ const activeMicroApp = computed(() =>
48
83
  </template>
49
84
 
50
85
  <style>
51
- .content { position: relative; min-width: 0; min-height: 0; }
86
+ .content {
87
+ position: relative;
88
+ min-width: 0;
89
+ min-height: 0;
90
+ }
52
91
  </style>
53
92
  ```
54
93
 
55
- `preload: true` 的应用会在浏览器空闲时串行加载。iframe 始终留在 Host 中,切换应用只改变可见性。`XTMicroView` 继续兼容非 preload 的旧接入方式。
94
+ `preload` 与 `keepAlive` 相互独立:`preload: true` 只表示浏览器空闲时提前加载(单个应用最多等待 15 秒);`keepAlive: true` 才会在切换离开后保留 iframe,并向子应用发送激活/失活通知。
95
+
96
+ ```ts
97
+ setupApp({
98
+ name: "kept-app",
99
+ url: "http://127.0.0.1:12001",
100
+ preload: true,
101
+ keepAlive: true,
102
+ });
103
+
104
+ setupApp({
105
+ name: "fresh-app",
106
+ url: "http://127.0.0.1:12002",
107
+ keepAlive: false,
108
+ });
109
+ ```
110
+
111
+ 保活会占用对应 iframe 的内存。刷新页面、调用 `reloadApp()` 或卸载 `XTMicroHost` 仍会重建 iframe,内存状态不会保留;需要跨刷新保存的数据应由子应用自行持久化。
112
+
113
+ `XTMicroView` 仅用于兼容旧接入方式:
114
+
115
+ ```vue
116
+ <XTMicroView :app-id="appId" />
117
+ ```
118
+
119
+ 它会在组件卸载时销毁 iframe,不提供预加载和保活能力。
120
+
121
+ ## 子应用接入
56
122
 
57
- 子应用初始化:
123
+ 在创建 Vue 应用时调用 `microInit`。该方法在普通独立访问时也可安全调用:
58
124
 
59
- ```javascript
60
- //子应用初始化
125
+ ```ts
61
126
  import { microInit } from "@xingtukeji/micro";
62
- microInit({
63
- // 处理 token,该方法建议同步执行完成,如果有异步处理(比如 http 请求)请后续页面初始化时,判断 token 是否准备完毕。
64
- authHandler: (token: string) => {
65
- window.localStorage.setItem("token", token);
66
- //or pinia store set token
127
+
128
+ await microInit({
129
+ authHandler(token) {
130
+ if (token) localStorage.setItem("token", token);
67
131
  },
68
- //简易处理微应用环境下,隐藏 header
69
- hideHeaderCssSelector: "#app ._hedaui-header",
132
+ hideHeaderCssSelector: ["#app .app-header"],
70
133
  onActivated() {
71
- // 图表、地图等可在这里重新计算尺寸
134
+ // 恢复轮询,并重新计算图表、地图等尺寸
72
135
  },
73
136
  onDeactivated() {
74
- // 可在这里暂停轮询或动画
137
+ // 暂停轮询、动画等后台任务
75
138
  },
76
139
  });
77
140
  ```
78
141
 
79
- 开启调试日志:
142
+ | 参数 | 说明 |
143
+ | --- | --- |
144
+ | `authHandler` | 接收主应用传入的 token,支持异步函数 |
145
+ | `hideHeaderCssSelector` | 微应用模式下隐藏的 CSS 选择器,可传字符串或数组 |
146
+ | `onActivated` | iframe 被激活时调用,支持异步函数 |
147
+ | `onDeactivated` | iframe 被隐藏时调用,支持异步函数 |
148
+
149
+ ## 常用 API
80
150
 
81
- ```javascript
82
- window.micro_api.debug(true);
151
+ ```ts
152
+ import {
153
+ createAppUrl,
154
+ getApp,
155
+ getApps,
156
+ isMicro,
157
+ isParent,
158
+ reloadApp,
159
+ startApp,
160
+ stopApp,
161
+ } from "@xingtukeji/micro";
83
162
  ```
84
163
 
85
- 当需要新标签打开子项目时,还想保留父项目加载的方式(不直接访问子项目),可以在父项目的 url 中添加参数 `__XT_MICRO_${子项目名称}=${子项目 url}`
86
- 但要注意,子项目的 url 需要用 encodeURIComponent 编码,否则会导致参数解析错误
164
+ | API | 说明 |
165
+ | --- | --- |
166
+ | `getApp(name)` / `getApps()` | 获取已注册应用 |
167
+ | `startApp(name, options?)` | 挂载并激活应用 |
168
+ | `stopApp(name)` | 销毁应用 iframe |
169
+ | `reloadApp(name, isGoHome?)` | 刷新应用;`isGoHome` 为 `true` 时回到配置的入口地址 |
170
+ | `createAppUrl(targetUrl, appName?)` | 生成由主应用加载指定子应用页面的 URL |
171
+ | `isMicro()` / `isParent()` | 判断当前运行环境 |
172
+
173
+ ## 路由同步与新标签页
87
174
 
88
- ```javascript
89
- http://192.168.100.127:8000/console/?__XT_MICRO_microtest2=http://localhost:5173/?test=123#/page2#/micro/microtest2
175
+ `sync` 开启时,子应用路由会写入主应用查询参数 `__XT_MICRO_<应用名>`。需要在新标签页中仍由主应用加载子页面时,可使用:
176
+
177
+ ```ts
178
+ const url = createAppUrl("/orders/123?tab=detail#section", "order-app");
179
+ window.open(url, "_blank");
90
180
  ```
91
181
 
92
- ```javascript
93
- window.__XT_MICRO_VERSION; //获取微前端框架版本
94
- window.__XT_MICRO_APP; //获取微应用信息,包括主应用的 location 信息
182
+ 手工拼接时,子应用地址必须经过 `encodeURIComponent`:
183
+
184
+ ```ts
185
+ const childUrl = encodeURIComponent("http://localhost:5173/page?tab=1#/detail");
186
+ const url = `${location.origin}/console/?__XT_MICRO_order-app=${childUrl}#/micro/order-app`;
187
+ ```
188
+
189
+ ## 调试与运行时信息
190
+
191
+ ```ts
192
+ window.micro_api.debug(true); // 开启调试日志
193
+ window.micro_api.debug(false); // 关闭调试日志
194
+
195
+ window.__XT_MICRO_VERSION; // 当前框架版本
196
+ window.__XT_MICRO_APP; // 当前微应用信息及主应用 location
95
197
  ```
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ const CONSTANT = {
17
17
  DOM_ID_PREFIX: "xt-micro-ifr-",
18
18
  URL_PARAM_PREFIX: "__XT_MICRO_",
19
19
  MICRO_VERSION: "__XT_MICRO_VERSION"
20
- }, version = "1.2.0";
20
+ }, version = "1.2.2";
21
21
  var utils_exports = /* @__PURE__ */ __export({
22
22
  ConsoleUtil: () => ConsoleUtil,
23
23
  addOrReplaceUrlParam: () => addOrReplaceUrlParam,
@@ -65,7 +65,7 @@ const ipcSyncAppInfo = async (s) => new Promise((I, L) => {
65
65
  let R = window.setTimeout(() => {
66
66
  window.removeEventListener("message", z), L(/* @__PURE__ */ Error(`micro app info timeout: ${s}`));
67
67
  }, 1e4), z = (L) => {
68
- ConsoleUtil.trace("child ipcSyncAppInfo receive message", L.data), L.source === window.parent && L.data?.cmd === "$xt/micro/info" && L.data?.data?.name === s && L.data?.data?.mainLocation?.origin === L.origin && (window.clearTimeout(R), window.removeEventListener("message", z), L.data.data.mainMicroVersion !== "1.2.0" && ConsoleUtil.warn(`子应用版本与主应用版本不一致,${s}子应用版本:${version},主应用版本:${L.data.data.mainMicroVersion}`), I(L.data.data));
68
+ ConsoleUtil.trace("child ipcSyncAppInfo receive message", L.data), L.source === window.parent && L.data?.cmd === "$xt/micro/info" && L.data?.data?.name === s && L.data?.data?.mainLocation?.origin === L.origin && (window.clearTimeout(R), window.removeEventListener("message", z), L.data.data.mainMicroVersion !== "1.2.2" && ConsoleUtil.warn(`子应用版本与主应用版本不一致,${s}子应用版本:${version},主应用版本:${L.data.data.mainMicroVersion}`), I(L.data.data));
69
69
  };
70
70
  window.addEventListener("message", z), ConsoleUtil.trace("child ipcSyncAppInfo send message", s), window.parent.postMessage({
71
71
  cmd: "$xt/micro/info",
@@ -333,7 +333,7 @@ var isAppMessage = (s, I) => {
333
333
  const microInit = async (s = {}) => {
334
334
  window[CONSTANT.MICRO_TYPE] = "child";
335
335
  let I = getQueryParameter(CONSTANT.IS_MICRO), L = getQueryParameter(CONSTANT.MICRO_NAME), R = getQueryParameter(CONSTANT.MICRO_TOKEN);
336
- if (s.authHandler && await s.authHandler(R), ConsoleUtil.trace("child microInit", I, L), I && (window[CONSTANT.IS_MICRO] = I === "true", L && await syncAppInfo(L)), window[CONSTANT.IS_MICRO]) {
336
+ if (R && s.authHandler && await s.authHandler(R), ConsoleUtil.trace("child microInit", I, L), I && (window[CONSTANT.IS_MICRO] = I === "true", L && await syncAppInfo(L)), window[CONSTANT.IS_MICRO]) {
337
337
  function I(s) {
338
338
  var I = document.styleSheets[0];
339
339
  if (!I) {
@@ -390,7 +390,8 @@ var _apps = {}, defaultAppOption = {
390
390
  url: "",
391
391
  name: "",
392
392
  sync: !0,
393
- preload: !1
393
+ preload: !1,
394
+ keepAlive: !1
394
395
  };
395
396
  const setupApp = (s) => {
396
397
  if (!s.url || s.url === "" || s.url === "undefined") {
@@ -469,7 +470,10 @@ var XTMicroView_default = /* @__PURE__ */ defineComponent({
469
470
  let R = L, H = getApps(), U = {}, W = /* @__PURE__ */ new Set(), G = !1, K, q, J, Y = (s, I) => {
470
471
  I instanceof HTMLElement && (U[s] = I);
471
472
  }, X = async (s) => {
472
- for (let I of H) I.name !== s && I.instance.deactivate();
473
+ for (let I of H) if (I.name !== s) {
474
+ let s = I.instance.active;
475
+ I.instance.deactivate(), s && !I.keepAlive && I.instance.destroy();
476
+ }
473
477
  if (setActiveMicroApp(s), !s) return;
474
478
  let I = H.find((I) => I.name === s), L = U[s];
475
479
  !I || !L || (W.add(s), await I.instance.activate(L));
@@ -5,6 +5,7 @@ export interface AppOption {
5
5
  url: string;
6
6
  el?: HTMLElement | string;
7
7
  preload?: boolean;
8
+ keepAlive?: boolean;
8
9
  token?: string | (() => string);
9
10
  params?: {
10
11
  [key: string]: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingtukeji/micro",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "private": false,
5
5
  "description": "",
6
6
  "main": "dist/index.js",