@xingtukeji/micro 1.0.1 → 1.0.3

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/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .xt-micro{background-color:#fff}.xt-micro:before{align-items:center;content:"加载中";display:flex;justify-content:center;inset:0;position:absolute;z-index:-1}.micro-iframe{border:none;height:100%;margin:0;padding:0;width:100%}.micro-iframe:after{align-items:center;background:#fff;content:"加载中";display:flex;justify-content:center;inset:0;position:absolute}
@@ -0,0 +1,21 @@
1
+ import { AppOption, App } from './index.d';
2
+ import { microInit } from './child';
3
+ /**
4
+ * 安装微应用
5
+ * @param app
6
+ */
7
+ export declare const setupApp: (app: AppOption) => App | undefined;
8
+ export declare const getApp: (name: string) => App;
9
+ /**
10
+ * 启动微应用
11
+ * @param name 应用名称
12
+ * @param options 应用配置
13
+ * @returns
14
+ */
15
+ export declare const startApp: (name: string, options?: Partial<AppOption>) => Promise<unknown>;
16
+ /**
17
+ * 停止指定名称的应用程序
18
+ * @param {string} name - 应用程序的名称
19
+ */
20
+ export declare const stopApp: (name: string) => void;
21
+ export { microInit };
@@ -0,0 +1,16 @@
1
+ import { MicroInitOptions } from './index.d';
2
+ /**
3
+ * 微应用-子应用初始化
4
+ * 此时方法上下文是子应用
5
+ * 非必须,但建议使用
6
+ * @param options
7
+ */
8
+ export declare const microInit: (options?: MicroInitOptions) => Promise<void>;
9
+ /**
10
+ * 同步子应用路由到主应用路由
11
+ */
12
+ export declare function syncUrlToWindow(): void;
13
+ /**
14
+ * 同步主应用路由到子应用
15
+ */
16
+ export declare function syncUrlToIframe(iframeWindow: Window): void;
@@ -0,0 +1,6 @@
1
+ declare const _default: import('vue').DefineComponent<{
2
+ appId: StringConstructor;
3
+ }, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{
4
+ appId: StringConstructor;
5
+ }>>, {}, {}>;
6
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { MicroApp } from './parent';
2
+ export interface AppOption {
3
+ name: string
4
+ url: string
5
+ el?: HTMLElement | string
6
+ preload?: boolean
7
+ token?: () => string | string
8
+ params?: { [key: string]: any }
9
+ sync?: boolean
10
+ }
11
+ export interface App extends AppOption {
12
+ instance: MicroApp
13
+ }
14
+ export interface MicroInitOptions {
15
+ hideHeaderCssSelector?: string
16
+ authHandler?: (token: string) => void
17
+ }
18
+
19
+ // 扩展 Window 接口,防止ts报错
20
+ declare global {
21
+ interface Window {
22
+ [key: string]: any
23
+ }
24
+ }
25
+
26
+
27
+ export * from './api'
@@ -0,0 +1,28 @@
1
+ import { AppOption, App } from './index.d';
2
+ export declare const isMicro: import('vue').Ref<boolean>;
3
+ export declare class MicroApp {
4
+ app: App | AppOption;
5
+ dom: HTMLIFrameElement | undefined;
6
+ panel: HTMLElement | undefined;
7
+ constructor(app: AppOption);
8
+ mount(el: HTMLElement | string | undefined, isHide?: boolean): void;
9
+ unmount(): void;
10
+ /**
11
+ * 预加载渲染
12
+ * TODO 还差用户切走之后,如何将ifr还原回去,等待下次载入
13
+ * @param el 挂载dom对象
14
+ */
15
+ preloadRender(el: HTMLElement | string | undefined): void;
16
+ /**
17
+ * 预加载app,先放到body,待启动时,挪过来
18
+ */
19
+ preload(): void;
20
+ changeOptions(options: Partial<AppOption>): void;
21
+ onLoad(_event: Event): void;
22
+ onUnload(_event: Event): void;
23
+ onError(): void;
24
+ }
25
+ /**
26
+ * 初始化主应用微前端
27
+ */
28
+ export declare const mainMicroInit: () => void;
@@ -0,0 +1 @@
1
+ export declare const CONSTANT: Record<string, string>;
@@ -0,0 +1,8 @@
1
+ export declare function getQueryParameter(name: string, url?: string): string | null;
2
+ export declare function getAbsolutePath(url: string | undefined, base: string, hash?: boolean): string | undefined;
3
+ export declare function anchorElementGenerator(url: string): HTMLAnchorElement;
4
+ export declare function getAnchorElementQueryMap(anchorElement: HTMLAnchorElement): {
5
+ [key: string]: string;
6
+ };
7
+ export declare function debounce<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => void;
8
+ export declare function addOrReplaceUrlParam(url: string, paramName: string, paramValue: string): string;
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "@xingtukeji/micro",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "private": false,
5
5
  "description": "",
6
- "main": "index.ts",
6
+ "main": "dist/index.js",
7
+ "types": "dist/types/index.d.ts",
7
8
  "author": "",
8
9
  "license": "ISC",
9
10
  "publishConfig": {
10
11
  "access": "public",
11
12
  "registry": "https://registry.npmjs.org/"
12
13
  },
13
- "scripts": {}
14
+ "devDependencies": {
15
+ "vite-plugin-dts": "^4.5.0"
16
+ },
17
+ "scripts": {
18
+ "build": "vite build"
19
+ }
14
20
  }
@@ -0,0 +1,23 @@
1
+ const defaultList = [
2
+ require('postcss-import'), // 使css支持@import相互引入
3
+ // require('postcss-simple-vars'), // 使css支持变量($blue: #056ef0;),老项目如果用到了sass里的变量,需要加载该插件
4
+ require('postcss-utilities'), // 支持常用工具(具体参照: https://github.com/ismamz/postcss-utilities#:~:text=postcss-utilities%20is%20a%20PostCSS%20plugin%20that%20includes%20the,started%20using%20postcss-utilities%20Try%20it%20in%20the%20browser%21)
5
+ require('postcss-nested'), // 支持嵌套语法,貌似postcss-preset-env里含语法嵌套,出现嵌套问题再尝试加载此插件
6
+ require('postcss-preset-env')({
7
+ stage: 0
8
+ }), // 对css4语法进行降阶(如自定义选择器),已经包含autoprefixer
9
+ ]
10
+ const developmentList = [
11
+
12
+ ]
13
+
14
+ const productionList = [
15
+ require('cssnano'), // css压缩
16
+ ]
17
+ module.exports = {
18
+ parser: 'postcss-scss',
19
+ plugins: [
20
+ ...defaultList,
21
+ ...(process.env.NODE_ENV === 'development' ? developmentList : productionList)
22
+ ]
23
+ }
package/src/api.ts ADDED
@@ -0,0 +1,108 @@
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 ADDED
@@ -0,0 +1,186 @@
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
+ }
@@ -0,0 +1,72 @@
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 ADDED
@@ -0,0 +1,29 @@
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 ADDED
@@ -0,0 +1,16 @@
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
+ }