@xingtukeji/micro 1.0.3 → 1.0.4
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/package.json +1 -1
- package/vite.config.ts +0 -1
- package/src/api.ts +0 -108
- package/src/child.ts +0 -186
- package/src/components/XTMicroView.vue +0 -72
- package/src/index.d.ts +0 -29
- package/src/index.ts +0 -16
- package/src/parent.ts +0 -268
- package/src/types.ts +0 -8
- package/src/utils.ts +0 -94
package/package.json
CHANGED
package/vite.config.ts
CHANGED
package/src/api.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import type {AppOption, App} from './index.d'
|
|
2
|
-
import {MicroApp} from './parent'
|
|
3
|
-
import {microInit} from './child'
|
|
4
|
-
|
|
5
|
-
let _apps: Record<string, App> = {}
|
|
6
|
-
|
|
7
|
-
const defaultAppOption: AppOption = {
|
|
8
|
-
url: '',
|
|
9
|
-
name: '',
|
|
10
|
-
sync: true
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* 安装微应用
|
|
14
|
-
* @param app
|
|
15
|
-
*/
|
|
16
|
-
export const setupApp = (app: AppOption): App | undefined => {
|
|
17
|
-
// 如果app的url属性不存在或者为空字符串或者为'undefined',则打印错误信息并返回
|
|
18
|
-
if(!app.url || app.url === '' || app.url === 'undefined'){
|
|
19
|
-
console.error('app url is required', app)
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
// 将defaultAppOption对象中的属性赋值给app对象
|
|
23
|
-
const op = Object.assign({}, app, defaultAppOption)
|
|
24
|
-
// 将app对象中的属性和MicroApp类的实例赋值给_apps对象中的app.name属性
|
|
25
|
-
_apps[app.name] = {
|
|
26
|
-
...op,
|
|
27
|
-
instance: new MicroApp(app),
|
|
28
|
-
}
|
|
29
|
-
// 返回_apps对象中的app.name属性
|
|
30
|
-
return _apps[app.name]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 导出一个函数,用于获取指定名称的应用
|
|
34
|
-
export const getApp = (name: string): App => {
|
|
35
|
-
// 返回指定名称的应用
|
|
36
|
-
return _apps[name]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 设置应用配置
|
|
41
|
-
* @param {App} app - 应用对象
|
|
42
|
-
* @param {Partial<AppOption>} options - 应用选项(可选)
|
|
43
|
-
* @returns {App} - 设置后的应用对象
|
|
44
|
-
*/
|
|
45
|
-
const setApp = (app: App, options?: Partial<AppOption>) => {
|
|
46
|
-
// 返回一个对象,该对象将app和options合并
|
|
47
|
-
return (_apps[app.name] = {
|
|
48
|
-
...app,
|
|
49
|
-
...options,
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* 启动微应用
|
|
54
|
-
* @param name 应用名称
|
|
55
|
-
* @param options 应用配置
|
|
56
|
-
* @returns
|
|
57
|
-
*/
|
|
58
|
-
export const startApp = (name: string, options?: Partial<AppOption>) => {
|
|
59
|
-
// 返回一个Promise对象
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
// 调用getApp函数,传入name参数,获取app对象
|
|
62
|
-
let app = getApp(name)
|
|
63
|
-
// 如果app对象存在
|
|
64
|
-
if (app) {
|
|
65
|
-
// 如果options参数存在
|
|
66
|
-
if (options) {
|
|
67
|
-
// 调用app对象的changeOptions方法,传入options参数
|
|
68
|
-
app.instance.changeOptions(options)
|
|
69
|
-
// 调用setApp函数,传入app对象和options参数,重新设置app对象
|
|
70
|
-
app = setApp(app, options)
|
|
71
|
-
}
|
|
72
|
-
// 如果app对象的preload属性存在
|
|
73
|
-
if (app.preload) {
|
|
74
|
-
// 调用app对象的preloadRender方法,传入app对象的el属性
|
|
75
|
-
app.instance.preloadRender(app.el)
|
|
76
|
-
} else {
|
|
77
|
-
// 否则,调用app对象的mount方法,传入app对象的el属性
|
|
78
|
-
app.instance.mount(app.el)
|
|
79
|
-
}
|
|
80
|
-
// 调用_postMicro函数,传入app对象
|
|
81
|
-
// _postMicro(app)
|
|
82
|
-
// 调用resolve方法,传入app对象
|
|
83
|
-
resolve(app)
|
|
84
|
-
} else {
|
|
85
|
-
// 否则,调用reject方法,传入'app not found'字符串
|
|
86
|
-
reject('app not found')
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 停止指定名称的应用程序
|
|
93
|
-
* @param {string} name - 应用程序的名称
|
|
94
|
-
*/
|
|
95
|
-
export const stopApp = (name: string) => {
|
|
96
|
-
// 获取名为name的app
|
|
97
|
-
let app = getApp(name)
|
|
98
|
-
// 如果app存在
|
|
99
|
-
if (app) {
|
|
100
|
-
// 调用app的unmount方法
|
|
101
|
-
app.instance.unmount()
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
export {
|
|
107
|
-
microInit
|
|
108
|
-
}
|
package/src/child.ts
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import {getQueryParameter} from './utils'
|
|
2
|
-
import {CONSTANT} from './types'
|
|
3
|
-
import {MicroApp} from './parent'
|
|
4
|
-
import {MicroInitOptions} from './index.d'
|
|
5
|
-
/**
|
|
6
|
-
* 微应用-子应用初始化
|
|
7
|
-
* 此时方法上下文是子应用
|
|
8
|
-
* 非必须,但建议使用
|
|
9
|
-
* @param options
|
|
10
|
-
*/
|
|
11
|
-
export const microInit = async (options: MicroInitOptions = {}) => {
|
|
12
|
-
//处理url参数
|
|
13
|
-
const micro = getQueryParameter(CONSTANT.IS_MICRO)
|
|
14
|
-
const microName = getQueryParameter(CONSTANT.MICRO_NAME)
|
|
15
|
-
//设置环境变量
|
|
16
|
-
if (micro) {
|
|
17
|
-
window[CONSTANT.IS_MICRO] = true
|
|
18
|
-
}
|
|
19
|
-
// console.log(`子应用「${microName}」初始化`, document)
|
|
20
|
-
// 尝试全局隐藏 header,该方式有一定的入侵,
|
|
21
|
-
function addGlobalStyle(css: string) {
|
|
22
|
-
var styleSheet = document.styleSheets[0];
|
|
23
|
-
if (!styleSheet) {
|
|
24
|
-
var style = document.createElement('style');
|
|
25
|
-
document.head.appendChild(style);
|
|
26
|
-
styleSheet = style.sheet!;
|
|
27
|
-
}
|
|
28
|
-
styleSheet.insertRule(css, styleSheet.cssRules.length);
|
|
29
|
-
}
|
|
30
|
-
// addGlobalStyle('._hedaui-header { display: none !important; }')
|
|
31
|
-
if(options.hideHeaderCssSelector) {
|
|
32
|
-
addGlobalStyle(`${options.hideHeaderCssSelector} { display: none !important; }`)
|
|
33
|
-
}
|
|
34
|
-
// 子应用监听message,设置环境变量
|
|
35
|
-
window.addEventListener('message', (e) => {
|
|
36
|
-
if (e.data.cmd === `$xt/micro/${microName}`) {
|
|
37
|
-
console.log('子应用收到消息:', microName, )
|
|
38
|
-
window[CONSTANT.MICRO_APP] = { ...e.data.data, mainBaseURL: e.origin }
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
patchIframeHistory(window)
|
|
42
|
-
|
|
43
|
-
// window.addEventListener('load', () => {
|
|
44
|
-
// console.log('子应用加载完成:', microName, window.location.href)
|
|
45
|
-
// // 监听 popstate 事件
|
|
46
|
-
// // window.addEventListener('popstate', function (event) {
|
|
47
|
-
// // console.log('History state changed:', event.state);
|
|
48
|
-
// // // 在这里可以根据新的历史状态进行相应的页面更新操作
|
|
49
|
-
// // });
|
|
50
|
-
// // patchIframeHistory(window, "http://localhost:5173", "http://localhost:8888")
|
|
51
|
-
// })
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//处理token
|
|
56
|
-
const token = getQueryParameter(CONSTANT.MICRO_TOKEN)
|
|
57
|
-
options.authHandler && token && await options.authHandler(token)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
//子应用给父应用同步路由
|
|
61
|
-
const _syncMicroPath = (app: MicroApp) => {
|
|
62
|
-
// console.log(`子应用给父应用「${app.app.name}」同步路由`, app.dom)
|
|
63
|
-
const parentWindow = window.parent;
|
|
64
|
-
const message = 'Hello from child!';
|
|
65
|
-
parentWindow.postMessage(message, 'http://localhost:8080');
|
|
66
|
-
app.dom?.contentWindow?.postMessage
|
|
67
|
-
({
|
|
68
|
-
cmd: `$xt/micro/${app.app.name}/sync`,
|
|
69
|
-
data: {
|
|
70
|
-
name: app.app.name,
|
|
71
|
-
url: app.app.url,
|
|
72
|
-
preload: app.app.preload,
|
|
73
|
-
// params: app.params
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 对iframe的history的pushState和replaceState进行修改
|
|
80
|
-
* 将从location劫持后的数据修改回来,防止跨域错误
|
|
81
|
-
* 同步路由到主应用
|
|
82
|
-
* @param iframeWindow
|
|
83
|
-
* @param appHostPath 子应用的 host path
|
|
84
|
-
* @param mainHostPath 主应用的 host path
|
|
85
|
-
*/
|
|
86
|
-
function patchIframeHistory(iframeWindow: Window): void {
|
|
87
|
-
const history = iframeWindow.history;
|
|
88
|
-
const rawHistoryPushState = history.pushState;
|
|
89
|
-
const rawHistoryReplaceState = history.replaceState;
|
|
90
|
-
history.pushState = function (data: any, title: string, url?: string): void {
|
|
91
|
-
// debugger;
|
|
92
|
-
// const baseUrl =
|
|
93
|
-
// mainHostPath + iframeWindow.location.pathname + iframeWindow.location.search + iframeWindow.location.hash;
|
|
94
|
-
// const mainUrl = getAbsolutePath(url?.replace(appHostPath, ""), baseUrl);
|
|
95
|
-
// const ignoreFlag = url === undefined;
|
|
96
|
-
|
|
97
|
-
rawHistoryPushState.call(history, data, title, url);
|
|
98
|
-
// if (ignoreFlag) return;
|
|
99
|
-
// updateBase(iframeWindow, appHostPath, mainHostPath);
|
|
100
|
-
syncUrlToWindow();
|
|
101
|
-
};
|
|
102
|
-
history.replaceState = function (data: any, title: string, url?: string): void {
|
|
103
|
-
// debugger;
|
|
104
|
-
// const baseUrl =
|
|
105
|
-
// mainHostPath + iframeWindow.location.pathname + iframeWindow.location.search + iframeWindow.location.hash;
|
|
106
|
-
// const mainUrl = getAbsolutePath(url?.replace(appHostPath, ""), baseUrl);
|
|
107
|
-
// const ignoreFlag = url === undefined;
|
|
108
|
-
|
|
109
|
-
rawHistoryReplaceState.call(history, data, title, url);
|
|
110
|
-
// if (ignoreFlag) return;
|
|
111
|
-
// updateBase(iframeWindow, appHostPath, mainHostPath);
|
|
112
|
-
syncUrlToWindow();
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* 同步子应用路由到主应用路由
|
|
117
|
-
*/
|
|
118
|
-
// 导出一个函数,用于同步url到window
|
|
119
|
-
export function syncUrlToWindow(): void {
|
|
120
|
-
if(!window[CONSTANT.MICRO_APP]) return
|
|
121
|
-
const { name, sync, mainBaseURL } = window[CONSTANT.MICRO_APP];
|
|
122
|
-
// console.trace('syncUrlToWindow')
|
|
123
|
-
//todo 此处应该postmessage到主应用,主应用再进行同步路由
|
|
124
|
-
const curUrl = window.location.pathname + window.location.search + window.location.hash;
|
|
125
|
-
window.parent.postMessage({
|
|
126
|
-
cmd: '$xt/micro/sync',
|
|
127
|
-
data: {
|
|
128
|
-
name: name,
|
|
129
|
-
url: window.encodeURIComponent(curUrl)
|
|
130
|
-
}
|
|
131
|
-
}, mainBaseURL);
|
|
132
|
-
|
|
133
|
-
// debugger
|
|
134
|
-
// let winUrlElement = anchorElementGenerator(window.parent.location.href);
|
|
135
|
-
// const queryMap = getAnchorElementQueryMap(winUrlElement);
|
|
136
|
-
// // 非同步且url上没有当前id的查询参数,否则就要同步参数或者清理参数
|
|
137
|
-
// if (!sync && !queryMap[name]) {
|
|
138
|
-
// (winUrlElement = null)
|
|
139
|
-
// return
|
|
140
|
-
// };
|
|
141
|
-
|
|
142
|
-
// const curUrl = iframeWindow.location.pathname + iframeWindow.location.search + iframeWindow.location.hash;
|
|
143
|
-
// let validShortPath = "";
|
|
144
|
-
// // 同步
|
|
145
|
-
// if (sync) {
|
|
146
|
-
// queryMap[name] = window.encodeURIComponent(
|
|
147
|
-
// curUrl
|
|
148
|
-
// );
|
|
149
|
-
// // 清理
|
|
150
|
-
// } else {
|
|
151
|
-
// delete queryMap[name];
|
|
152
|
-
// }
|
|
153
|
-
// const newQuery =
|
|
154
|
-
// "?" +
|
|
155
|
-
// Object.keys(queryMap)
|
|
156
|
-
// .map((key) => key + "=" + queryMap[key])
|
|
157
|
-
// .join("&");
|
|
158
|
-
// winUrlElement.search = newQuery;
|
|
159
|
-
// console.log('window', iframeWindow.parent.location.href)
|
|
160
|
-
// // if (winUrlElement.href !== iframeWindow.parent.location.href) {
|
|
161
|
-
// // console.log('window', iframeWindow.parent)
|
|
162
|
-
// // // iframeWindow.parent.history.replaceState(null, "", winUrlElement.href);
|
|
163
|
-
// // // window.history.replaceState(null, "", winUrlElement.href);
|
|
164
|
-
// // }
|
|
165
|
-
// winUrlElement = null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 同步主应用路由到子应用
|
|
170
|
-
*/
|
|
171
|
-
export function syncUrlToIframe(iframeWindow: Window): void {
|
|
172
|
-
// 获取当前路由路径
|
|
173
|
-
const { pathname, search, hash } = iframeWindow.location;
|
|
174
|
-
const { id, url, sync, execFlag, prefix, inject } = iframeWindow.__WUJIE;
|
|
175
|
-
|
|
176
|
-
// 只在浏览器刷新或者第一次渲染时同步
|
|
177
|
-
const idUrl = sync && !execFlag ? getSyncUrl(id, prefix) : url;
|
|
178
|
-
// 排除href跳转情况
|
|
179
|
-
const syncUrl = (/^http/.test(idUrl) ? null : idUrl) || url;
|
|
180
|
-
const { appRoutePath } = appRouteParse(syncUrl);
|
|
181
|
-
|
|
182
|
-
const preAppRoutePath = pathname + search + hash;
|
|
183
|
-
if (preAppRoutePath !== appRoutePath) {
|
|
184
|
-
iframeWindow.history.replaceState(null, "", inject.mainHostPath + appRoutePath);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import { onMounted, ref, watch, onUnmounted } from 'vue';
|
|
3
|
-
import { startApp, stopApp } from '../api'
|
|
4
|
-
const props = defineProps({
|
|
5
|
-
appId: String
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
const refPanel = ref()
|
|
9
|
-
const init = () => {
|
|
10
|
-
props.appId && startApp(props.appId, {
|
|
11
|
-
el: refPanel.value
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
watch(() => props.appId, (newVal, oldVal) => {
|
|
16
|
-
if(newVal !== oldVal && oldVal){
|
|
17
|
-
stopApp(oldVal)
|
|
18
|
-
}
|
|
19
|
-
init()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
onMounted(() => {
|
|
24
|
-
init()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
onUnmounted(()=>{
|
|
28
|
-
console.log('微应用销毁', props.appId)
|
|
29
|
-
})
|
|
30
|
-
</script>
|
|
31
|
-
|
|
32
|
-
<template>
|
|
33
|
-
<div ref="refPanel" class="xt-micro w-100% h-100% overflow-hidden"></div>
|
|
34
|
-
</template>
|
|
35
|
-
|
|
36
|
-
<style lang="postcss" scoped></style>
|
|
37
|
-
<style>
|
|
38
|
-
.xt-micro{
|
|
39
|
-
background-color: #fff;
|
|
40
|
-
}
|
|
41
|
-
.xt-micro::before{
|
|
42
|
-
content: '加载中';
|
|
43
|
-
position: absolute;
|
|
44
|
-
top: 0;
|
|
45
|
-
left: 0;
|
|
46
|
-
right:0;
|
|
47
|
-
bottom: 0;
|
|
48
|
-
display: flex;
|
|
49
|
-
justify-content: center;
|
|
50
|
-
align-items: center;
|
|
51
|
-
z-index: -1;
|
|
52
|
-
}
|
|
53
|
-
.micro-iframe{
|
|
54
|
-
width: 100%;
|
|
55
|
-
height: 100%;
|
|
56
|
-
border: none;
|
|
57
|
-
margin: 0;
|
|
58
|
-
padding: 0;
|
|
59
|
-
}
|
|
60
|
-
.micro-iframe::after{
|
|
61
|
-
content: '加载中';
|
|
62
|
-
position: absolute;
|
|
63
|
-
top: 0;
|
|
64
|
-
left: 0;
|
|
65
|
-
right:0;
|
|
66
|
-
bottom: 0;
|
|
67
|
-
display: flex;
|
|
68
|
-
justify-content: center;
|
|
69
|
-
align-items: center;
|
|
70
|
-
background: #fff;
|
|
71
|
-
}
|
|
72
|
-
</style>
|
package/src/index.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {MicroApp} from './parent'
|
|
3
|
-
|
|
4
|
-
export interface AppOption {
|
|
5
|
-
name: string
|
|
6
|
-
url: string
|
|
7
|
-
el?: HTMLElement | string
|
|
8
|
-
preload?: boolean
|
|
9
|
-
token?: () => string | string
|
|
10
|
-
params?: { [key: string]: any }
|
|
11
|
-
sync?: boolean
|
|
12
|
-
}
|
|
13
|
-
export interface App extends AppOption {
|
|
14
|
-
instance: MicroApp
|
|
15
|
-
}
|
|
16
|
-
export interface MicroInitOptions {
|
|
17
|
-
hideHeaderCssSelector?: string
|
|
18
|
-
authHandler?: (token: string) => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// 扩展 Window 接口,防止ts报错
|
|
22
|
-
declare global {
|
|
23
|
-
interface Window {
|
|
24
|
-
[key: string]: any
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export * from './api'
|
package/src/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import type { App } from 'vue'
|
|
4
|
-
import XTMicroView from './components/XTMicroView.vue'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {mainMicroInit} from './parent'
|
|
9
|
-
export * from './api'
|
|
10
|
-
|
|
11
|
-
export default {
|
|
12
|
-
install: (app: App, _options: any = {}) => {
|
|
13
|
-
app.component("xt-micro-view", XTMicroView);
|
|
14
|
-
mainMicroInit()
|
|
15
|
-
}
|
|
16
|
-
}
|
package/src/parent.ts
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { ref } from 'vue'
|
|
2
|
-
import {AppOption, App} from './index.d'
|
|
3
|
-
import {CONSTANT} from './types'
|
|
4
|
-
import {getQueryParameter, addOrReplaceUrlParam, getAbsolutePath} from './utils'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 判断是否在微应用中
|
|
8
|
-
*/
|
|
9
|
-
const _isMicro = ref(false)
|
|
10
|
-
Object.defineProperty(window, CONSTANT.IS_MICRO, {
|
|
11
|
-
get: function () {
|
|
12
|
-
return _isMicro.value
|
|
13
|
-
},
|
|
14
|
-
set: function (newValue) {
|
|
15
|
-
_isMicro.value = newValue
|
|
16
|
-
},
|
|
17
|
-
})
|
|
18
|
-
export const isMicro = _isMicro
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 生成微应用id
|
|
22
|
-
* 增加前缀
|
|
23
|
-
* @param name 微应用名称(唯一)
|
|
24
|
-
* @returns 返回dom id
|
|
25
|
-
*/
|
|
26
|
-
const generateId = (name: string) => {
|
|
27
|
-
return `${CONSTANT.DOM_ID_PREFIX}${name}`
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 处理微应用url
|
|
31
|
-
* @param url 微应用url
|
|
32
|
-
* @param params 携带参数
|
|
33
|
-
* @returns 拼接后的url
|
|
34
|
-
*/
|
|
35
|
-
const generateURL = (app: AppOption) => {
|
|
36
|
-
// 判断变量是否为对象
|
|
37
|
-
function isObject(variable: unknown) {
|
|
38
|
-
return variable !== null && typeof variable === 'object' && !Array.isArray(variable)
|
|
39
|
-
}
|
|
40
|
-
// 获取params参数
|
|
41
|
-
const _parmas = typeof app.params === 'function' ? app.params() : isObject(app.params) ? app.params : {}
|
|
42
|
-
// 获取token参数
|
|
43
|
-
const _token = typeof app.token === 'function' ? app.token() : isObject(app.token) ? app.token : ''
|
|
44
|
-
// 创建URL对象
|
|
45
|
-
const url = new URL(app.url.indexOf('http') > -1 ? app.url : window.location.origin + app.url)
|
|
46
|
-
// 获取microBakUrl参数
|
|
47
|
-
const microBakUrl = getQueryParameter(app.name)
|
|
48
|
-
// 如果microBakUrl存在
|
|
49
|
-
if(microBakUrl){
|
|
50
|
-
// 获取绝对路径
|
|
51
|
-
const temp = getAbsolutePath(microBakUrl, app.url, true)
|
|
52
|
-
// 如果绝对路径存在
|
|
53
|
-
if(temp){
|
|
54
|
-
// 创建URL对象
|
|
55
|
-
const tempURL = new URL(temp);
|
|
56
|
-
// 获取searchParams参数
|
|
57
|
-
const tempSearchParams = tempURL.searchParams
|
|
58
|
-
// 遍历searchParams参数
|
|
59
|
-
tempSearchParams.forEach((value, key) => {
|
|
60
|
-
// 设置url的searchParams参数
|
|
61
|
-
url.searchParams.set(key, value)
|
|
62
|
-
})
|
|
63
|
-
// 设置url的hash参数
|
|
64
|
-
url.hash = tempURL.hash
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 设置url的searchParams参数
|
|
69
|
-
url.searchParams.set(CONSTANT.IS_MICRO, true+'')
|
|
70
|
-
url.searchParams.set(CONSTANT.MICRO_NAME, app.name)
|
|
71
|
-
_token && url.searchParams.set(CONSTANT.MICRO_TOKEN, _token)
|
|
72
|
-
|
|
73
|
-
if(_parmas){
|
|
74
|
-
// 设置url的searchParams参数
|
|
75
|
-
for(let key of Object.keys(_parmas)){
|
|
76
|
-
url.searchParams.set(key, _parmas[key])
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 清空主路由上的子应用路由sync参数
|
|
81
|
-
const mainURL = new URL(window.location.href)
|
|
82
|
-
mainURL.searchParams.delete(app.name)
|
|
83
|
-
window.history.replaceState({}, '', mainURL.href)
|
|
84
|
-
// 返回url的href属性
|
|
85
|
-
return url.href
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export class MicroApp {
|
|
89
|
-
app: App | AppOption
|
|
90
|
-
dom: HTMLIFrameElement | undefined
|
|
91
|
-
panel: HTMLElement | undefined
|
|
92
|
-
|
|
93
|
-
constructor(app: AppOption) {
|
|
94
|
-
this.app = app
|
|
95
|
-
app.preload && this.preload()
|
|
96
|
-
this.dom = undefined
|
|
97
|
-
this.panel = undefined
|
|
98
|
-
return this
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
mount(el: HTMLElement | string | undefined, isHide = false) {
|
|
102
|
-
let panel: HTMLElement | null
|
|
103
|
-
if (el) {
|
|
104
|
-
if (typeof el === 'string') {
|
|
105
|
-
panel = document.querySelector(el)
|
|
106
|
-
} else {
|
|
107
|
-
panel = el
|
|
108
|
-
}
|
|
109
|
-
//这里清空了容器的内容,为了方便,如果还有其他元素需要特别处理,而且也影响元素再复用
|
|
110
|
-
panel!.innerHTML = ''
|
|
111
|
-
} else {
|
|
112
|
-
panel = document.body
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (panel) {
|
|
116
|
-
let ifr = <HTMLIFrameElement>document.getElementById(generateId(this.app.name))
|
|
117
|
-
if (!ifr) {
|
|
118
|
-
ifr = document.createElement('iframe')
|
|
119
|
-
ifr.id = generateId(this.app.name)
|
|
120
|
-
const url = generateURL(this.app)
|
|
121
|
-
ifr.src = url
|
|
122
|
-
ifr.classList.add('micro-iframe')
|
|
123
|
-
ifr.addEventListener('load', (event) => {
|
|
124
|
-
this.onLoad.call(this, event)
|
|
125
|
-
})
|
|
126
|
-
ifr.addEventListener('error', this.onError)
|
|
127
|
-
ifr.addEventListener('unload', this.onUnload)
|
|
128
|
-
this.dom = ifr
|
|
129
|
-
}
|
|
130
|
-
isHide ? (ifr.style.display = 'none') : ifr.style.removeProperty('display')
|
|
131
|
-
this.panel = panel
|
|
132
|
-
panel.appendChild(ifr)
|
|
133
|
-
} else {
|
|
134
|
-
throw new Error('el not found')
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
unmount() {
|
|
139
|
-
const appDom = document.getElementById(generateId(this.app.name))
|
|
140
|
-
if (appDom) {
|
|
141
|
-
if (this.app.preload) {
|
|
142
|
-
// if (this.preload){
|
|
143
|
-
appDom.style.display = 'none'
|
|
144
|
-
document.body.appendChild(appDom)
|
|
145
|
-
} else {
|
|
146
|
-
this.panel?.removeChild(appDom)
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
throw new Error('app dom not found')
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 预加载渲染
|
|
155
|
-
* TODO 还差用户切走之后,如何将ifr还原回去,等待下次载入
|
|
156
|
-
* @param el 挂载dom对象
|
|
157
|
-
*/
|
|
158
|
-
preloadRender(el: HTMLElement | string | undefined) {
|
|
159
|
-
// let appDom: HTMLElement | undefined = this.dom
|
|
160
|
-
this.mount(el)
|
|
161
|
-
|
|
162
|
-
// const appDom = document.getElementById(generateId(this.app.name))
|
|
163
|
-
// if (!appDom) {
|
|
164
|
-
//
|
|
165
|
-
// this.render(el)
|
|
166
|
-
// return
|
|
167
|
-
// }
|
|
168
|
-
//
|
|
169
|
-
// let panel
|
|
170
|
-
// if (typeof el === 'string') {
|
|
171
|
-
// panel = document.querySelector(el)
|
|
172
|
-
// } else {
|
|
173
|
-
// panel = el
|
|
174
|
-
// }
|
|
175
|
-
// appDom!.style.removeProperty('display')
|
|
176
|
-
// panel?.appendChild(appDom)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* 预加载app,先放到body,待启动时,挪过来
|
|
181
|
-
*/
|
|
182
|
-
preload() {
|
|
183
|
-
this.mount(undefined, true)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
changeOptions(options: Partial<AppOption>) {
|
|
187
|
-
this.app = {
|
|
188
|
-
...this.app,
|
|
189
|
-
...options,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
onLoad(_event: Event) {
|
|
194
|
-
// console.log('子应用 onload', this.app.name)
|
|
195
|
-
// console.log('onLoad',this.app, this.panel, this.dom)
|
|
196
|
-
// if(this.dom){
|
|
197
|
-
// const headerDom = this.dom.contentWindow.document.querySelector('._hedaui-header')
|
|
198
|
-
// headerDom && (headerDom.style.display = 'none')
|
|
199
|
-
// }
|
|
200
|
-
|
|
201
|
-
//监听message,设置环境变量
|
|
202
|
-
// console.log('window', window['__XT_MICRO'])
|
|
203
|
-
// window.addEventListener('message', (e) => {
|
|
204
|
-
// console.log('1111', this.app.name, window.document)
|
|
205
|
-
// if (e.data.cmd === `$xt/micro/${this.app.name}`) {
|
|
206
|
-
// // console.log('micro', e.data.data)
|
|
207
|
-
// window[CONSTANT.MICRO_APP] = e.data.data
|
|
208
|
-
// const headerDom = document.querySelector('._hedaui-header')
|
|
209
|
-
// headerDom && (headerDom.style.display = 'none')
|
|
210
|
-
// }
|
|
211
|
-
// })
|
|
212
|
-
_postMicro(this)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
onUnload(_event: Event) {}
|
|
216
|
-
|
|
217
|
-
onError() {}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* 父应用给子应用发送消息
|
|
226
|
-
* @param app 微应用对象
|
|
227
|
-
*/
|
|
228
|
-
const _postMicro = (app: MicroApp) => {
|
|
229
|
-
// console.log(`父应用给子应用「${app.app.name}」发送消息`, app.dom)
|
|
230
|
-
app.dom?.contentWindow?.postMessage(
|
|
231
|
-
{
|
|
232
|
-
cmd: `$xt/micro/${app.app.name}`,
|
|
233
|
-
data: {
|
|
234
|
-
name: app.app.name,
|
|
235
|
-
url: app.app.url,
|
|
236
|
-
preload: app.app.preload,
|
|
237
|
-
sync: app.app.sync || true
|
|
238
|
-
// params: app.params
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
'*'
|
|
242
|
-
)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* 初始化主应用微前端
|
|
249
|
-
*/
|
|
250
|
-
export const mainMicroInit = () => {
|
|
251
|
-
// 监听window对象的消息事件
|
|
252
|
-
window.addEventListener('message', (e)=>{
|
|
253
|
-
// 同步子应用路由到主应用上
|
|
254
|
-
if (e.data.cmd === '$xt/micro/sync') {
|
|
255
|
-
// 过滤掉微应用的默认参数
|
|
256
|
-
const whitelist = [CONSTANT.IS_MICRO, CONSTANT.MICRO_NAME, CONSTANT.MICRO_TOKEN]
|
|
257
|
-
const tempUrl = new URL(window.decodeURIComponent(e.data.data.url), e.origin)
|
|
258
|
-
for(let key of whitelist){
|
|
259
|
-
tempUrl.searchParams.delete(key)
|
|
260
|
-
}
|
|
261
|
-
console.log('tempUrl', tempUrl.href)
|
|
262
|
-
// 调用addOrReplaceUrlParam函数,将e.data.data.name和e.data.data.url作为参数传入
|
|
263
|
-
const curUrl = addOrReplaceUrlParam(window.location.href, e.data.data.name, tempUrl.href)
|
|
264
|
-
// 使用replaceState方法替换当前页面的URL
|
|
265
|
-
window.history.replaceState(null, "", curUrl);
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export const CONSTANT: Record<string, string> = {
|
|
2
|
-
KEY: '$xtm', // 父应用注入子应用实例的key
|
|
3
|
-
IS_MICRO: '__XT_MICRO', // 是否在微应用中
|
|
4
|
-
MICRO_NAME: '__XT_MICRO_NAME', // 微应用名称
|
|
5
|
-
MICRO_TOKEN: '__XT_MICRO_TOKEN', // 微应用token
|
|
6
|
-
MICRO_APP: '__XT_MICRO_APP', // 微应用实例
|
|
7
|
-
DOM_ID_PREFIX: 'xt-micro-ifr-', // dom id前缀
|
|
8
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// 获取URL中的查询参数
|
|
2
|
-
// 导出一个函数,用于获取查询参数
|
|
3
|
-
export function getQueryParameter(name: string, url = window.location.href): string | null {
|
|
4
|
-
// 将参数名中的中括号替换为转义字符
|
|
5
|
-
name = name.replace(/[\[\]]/g, '\\$&')
|
|
6
|
-
// 创建正则表达式,匹配查询参数
|
|
7
|
-
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
|
8
|
-
results = regex.exec(url)
|
|
9
|
-
// 如果没有匹配到查询参数,返回null
|
|
10
|
-
if (!results) return null
|
|
11
|
-
// 如果没有匹配到参数值,返回空字符串
|
|
12
|
-
if (!results[2]) return ''
|
|
13
|
-
// 对参数值进行解码,并去除首尾空格
|
|
14
|
-
return decodeURIComponent(results[2].replace(/\+/g, ' ')).trim()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// 导出一个函数,用于获取绝对路径
|
|
18
|
-
export function getAbsolutePath(url: string|undefined, base: string, hash?: boolean): string | undefined {
|
|
19
|
-
try {
|
|
20
|
-
// 为空值无需处理
|
|
21
|
-
if (url) {
|
|
22
|
-
// 需要处理hash的场景
|
|
23
|
-
if (hash && url.startsWith("#")) return url;
|
|
24
|
-
return new URL(url, base).href;
|
|
25
|
-
} else return url;
|
|
26
|
-
} catch {
|
|
27
|
-
return url;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 导出一个函数,用于生成一个HTMLAnchorElement元素
|
|
32
|
-
export function anchorElementGenerator(url: string): HTMLAnchorElement {
|
|
33
|
-
// 创建一个a标签元素
|
|
34
|
-
const element = window.document.createElement("a");
|
|
35
|
-
// 设置a标签的href属性为传入的url
|
|
36
|
-
element.href = url;
|
|
37
|
-
element.href = element.href; // hack ie
|
|
38
|
-
return element;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 导出一个函数,用于获取锚点元素的查询字符串映射
|
|
42
|
-
export function getAnchorElementQueryMap(anchorElement: HTMLAnchorElement): { [key: string]: string } {
|
|
43
|
-
// 获取锚点元素的查询字符串
|
|
44
|
-
const queryString = anchorElement.search || "";
|
|
45
|
-
// 将查询字符串转换为URLSearchParams对象,并使用reduce方法将其转换为键值对映射
|
|
46
|
-
return [...new URLSearchParams(queryString).entries()].reduce((p, c) => {
|
|
47
|
-
// 将键值对添加到映射中
|
|
48
|
-
p[c[0]] = c[1];
|
|
49
|
-
// 返回映射
|
|
50
|
-
return p;
|
|
51
|
-
}, {} as Record<string, string>);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//防抖函数,支持传过来一个函数,返回一个防抖后的函数
|
|
56
|
-
export function debounce<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => void {
|
|
57
|
-
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
58
|
-
|
|
59
|
-
return function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
|
|
60
|
-
const context = this;
|
|
61
|
-
|
|
62
|
-
if (timer) {
|
|
63
|
-
clearTimeout(timer);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
timer = setTimeout(() => {
|
|
67
|
-
func.apply(context, args);
|
|
68
|
-
}, delay);
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function addOrReplaceUrlParam(url: string, paramName: string, paramValue: string): string {
|
|
73
|
-
// 先分割出 URL 的基础部分和哈希部分
|
|
74
|
-
const [baseUrl, hash] = url.split('#');
|
|
75
|
-
const urlObj = new URL(baseUrl);
|
|
76
|
-
const searchParams = urlObj.searchParams;
|
|
77
|
-
|
|
78
|
-
// 如果参数已经存在,则替换其值
|
|
79
|
-
if (searchParams.has(paramName)) {
|
|
80
|
-
searchParams.set(paramName, paramValue);
|
|
81
|
-
} else {
|
|
82
|
-
// 如果参数不存在,则添加该参数
|
|
83
|
-
searchParams.append(paramName, paramValue);
|
|
84
|
-
}
|
|
85
|
-
// 如果原 URL 有哈希部分,再把它加回去
|
|
86
|
-
if (hash) {
|
|
87
|
-
urlObj.hash = hash
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
urlObj.search = searchParams.toString();
|
|
91
|
-
let newUrl = urlObj.toString();
|
|
92
|
-
|
|
93
|
-
return newUrl;
|
|
94
|
-
}
|