Wujie 使用
基本使用
安装 wujie
npm i wujie在主应用中引入(以 Vue 应用为例)
index.js
import { createApp } from "vue"; import App from "./App.vue"; import { startApp } from "wujie"; // 引入wujie,通过其中的startApp来设置子应用 const app = createApp(App); setupApp({ name: "唯一id", url: "子应用地址", exec: true, el: "容器", sync: true, }); app.mount("#app");
Vue 组件封装
如果使用的是 vue2 或 vue3 的应用,可以直接使用封装好的组件。
安装 wujie-vue2/ wujie-vue3
# vue2 框架 npm i wujie-vue2 -S # vue3 框架 npm i wujie-vue3 -S在主应用中引入并挂载
import { createApp } from "vue"; import App from "./App.vue"; import Wujie from "wujie-vue3"; // 引入wujie-vue3 const app = createApp(App); app.use(Wujie); // 注册 app.mount("#app");在主应用中使用,接入子应用
在此之前要先启动子应用。
<WujieVue url="http://127.0.0.1:5147" name="vue3"></WujieVue> <!--子应用vue3--> ... <WujieVue width="100%" height="100%" name="xxx" :url="xxx" :sync="true" :fetch="fetch" :props="props" :beforeLoad="beforeLoad" :beforeMount="beforeMount" :afterMount="afterMount" :beforeUnmount="beforeUnmount" :afterUnmount="afterUnmount" </WujieVue> ...
封装原理
wujie-vue3是按照一个组件的原理去实现的。
import {
defineComponent,
h,
getCurrentInstance,
onMounted,
watch,
onBeforeUnmount,
} from "vue";
import { startApp, bus } from "wujie";
import type { PropType } from "vue";
import { Props } from "./type";
const WujieVue = defineComponent({
props: {
width: { type: String, default: "" },
height: { type: String, default: "" },
name: { type: String, default: "", required: true },
loading: { type: HTMLElement, default: undefined },
url: { type: String, default: "", required: true },
sync: { type: Boolean, default: undefined },
prefix: { type: Object, default: undefined },
alive: { type: Boolean, default: undefined },
props: { type: Object, default: undefined },
attrs: { type: Object, default: undefined },
replace: {
type: Function as PropType<Props["replace"]>,
default: undefined,
},
fetch: { type: Function as PropType<Props["fetch"]>, default: undefined },
fiber: { type: Boolean, default: undefined },
degrade: { type: Boolean, default: undefined },
plugins: { type: Array as PropType<Props["plugins"]>, default: null },
beforeLoad: {
type: Function as PropType<Props["beforeLoad"]>,
default: null,
},
beforeMount: {
type: Function as PropType<Props["beforeMount"]>,
default: null,
},
afterMount: {
type: Function as PropType<Props["afterMount"]>,
default: null,
},
beforeUnmount: {
type: Function as PropType<Props["beforeUnmount"]>,
default: null,
},
afterUnmount: {
type: Function as PropType<Props["afterUnmount"]>,
default: null,
},
activated: {
type: Function as PropType<Props["activated"]>,
default: null,
},
deactivated: {
type: Function as PropType<Props["deactivated"]>,
default: null,
},
},
setup(props, { emit }) {
const init = () => {
const instance = getCurrentInstance();
startApp({
// 微前端初始化所需方法
name: props.name,
url: props.url,
el: instance?.refs.wujie as HTMLElement,
loading: props.loading,
alive: props.alive,
fetch: props.fetch,
props: props.props,
attrs: props.attrs,
replace: props.replace,
sync: props.sync,
prefix: props.prefix,
fiber: props.fiber,
degrade: props.degrade,
plugins: props.plugins,
beforeLoad: props.beforeLoad,
beforeMount: props.beforeMount,
afterMount: props.afterMount,
beforeUnmount: props.beforeUnmount,
afterUnmount: props.afterUnmount,
activated: props.activated,
deactivated: props.deactivated,
});
};
// name和url可能是动态的,所以需要监听它们的变化,然后再初始化
watch([props.name, props.url], () => {
init();
});
// 传参函数
const hanlderEmit = (event: string, ...args: any[]) => {
emit(event, ...args); // 暴露给父组件(给bus做了一个中转)
};
onMounted(() => {
bus.$onAll(hanlderEmit); // 监听所有事件
init();
});
onBeforeUnmount(() => {
bus.$offAll(hanlderEmit); // 销毁
});
return () =>
h("div", {
style: {
width: props.width,
height: props.height,
},
ref: "wujie", // 便于之后获取到这个dom
});
},
});
WujieVue.install = function (app) {
app.component("WujieVue", WujieVue); // 组件注册
};
export default WujieVue;
注意:
- PropType:有很多复杂的数据结构是办法没有标注类型的(在 ts 中类型与接口不对应会报错),所以 vue3 提供一个 PropType 来与定义的结构相对应。(此处的类型对应的就是导出的 Props 里的类型,见下文)
- getCurrentInstance:在
setup()中的this还是 undefined,所以没有办法通过this.$refs来读取。在 vue3 中可以使用getCurrentInstance()来获取组件的实例。- bus:wujie 封装的用于传参的对象。
定义所需参数(wujie 的参数)
import type { plugin } from "wujie";
type lifecycle = (appWindow: Window) => any;
interface Props {
/** 唯一性用户必须保证 */
name: string;
/** 需要渲染的url */
url: string;
/** 需要渲染的html, 如果用户已有则无需从url请求 */
html?: string;
/** 渲染的容器 */
loading?: HTMLElement;
/** 路由同步开关, false刷新无效,但是前进后退依然有效 */
sync?: boolean;
/** 子应用短路径替换,路由同步时生效 */
prefix?: { [key: string]: string };
/** 子应用保活模式,state不会丢失 */
alive?: boolean;
/** 注入给子应用的数据 */
props?: { [key: string]: any };
/** js采用fiber模式执行 */
fiber?: boolean;
/** 子应用采用降级iframe方案 */
degrade?: boolean;
/** 自定义运行iframe的属性 */
attrs?: { [key: string]: any };
/** 自定义降级渲染iframe的属性 */
degradeAttrs?: { [key: string]: any };
/** 代码替换钩子 */
replace?: (codeText: string) => string;
/** 自定义fetch,资源和接口 */
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
/** 子应插件 */
plugins: Array<plugin>;
/** 子应用生命周期 */
beforeLoad?: lifecycle;
/** 没有做生命周期改造的子应用不会调用 */
beforeMount?: lifecycle;
afterMount?: lifecycle;
beforeUnmount?: lifecycle;
afterUnmount?: lifecycle;
/** 非保活应用不会调用 */
activated?: lifecycle;
deactivated?: lifecycle;
}
export { Props };
React 组件封装
wujie 基于react的组件封装,使用参数基本和 Vue 的封装类似,只是换了 react 的写法。
预加载
- 解构出 preloadApp()
- 使用 preloadApp(),将
exec设置为 true 开启预加载
import { createApp } from "vue";
import App from "./App.vue";
import Wujie from "wujie-vue3";
const { preloadApp } = Wujie; // 解构出preloadApp()
const app = createApp(App);
app.use(Wujie);
app.mount("#app");
preloadApp({ name: "react", url: "http://127.0.0.1:5147", exec: true }); // 开启预加载(参数和setupApp相同,多了个exec)
应用通信
wujie 提供三种通信方式:
window 通信
由于子应用运行的iframe的src和主应用是同域的,所以相互可以直接通信。
主应用调用子应用的全局数据
window.document.querySelector("iframe[name=子应用id]").contentWindow.xxx;子应用调用主应用的全局数据
window.parent.xxx;
例子:
主应用定义一个全局变量:
var a = "abc";子应用通过
window.parent.a访问:const getParentA = () => { console.log(alter(window.parent.a)); };
注意:由于同源策略的限制,如果 iframe 的源与主页面的源不同,那么在控制 iframe 中的元素时可能会受到限制。(如果需要跨域控制 iframe 中的元素,可以考虑使用 postMessage API 来进行通信)
props 通信
主应用可以通过
props注入数据和方法<WujieVue name="xxx" url="xxx" :props="{ data: xxx, methods: xxx }" ></WujieVue>例子:
<template> <WujieVue url="http://127.0.0.1:5147" name="vue3" :props="{age: 23, name: '张三'}" ></WujieVue> </template>子应用可以通过
$wujie来获取:无界对子应用注入了
$wujie对象,可以通过$wujie或者window.$wujie获取。const props = window.$wujie?.props; // {data: xxx, methods: xxx}注意:直接写
window.$wujie.props可能能用,但是会报错,这是因为没有写$wujie的声明文件,在全局声明一下就好了:declare global { interface Window { $wujie: { props: Record<string, any>; }; } }
eventBus 通信
无界提供一套去中心化的通信方案,主应用和子应用、子应用和子应用都可以通过这种方式进行通信。
通过
$on监听,$emit触发双向传递消息。
主应用使用
// 如果使用wujie import { bus } from "wujie"; // 如果使用wujie-vue import WujieVue from "wujie-vue"; const { bus } = WujieVue; // 如果使用wujie-react import WujieReact from "wujie-react"; const { bus } = WujieReact; // 主应用监听事件 bus.$on("事件名字", function (arg1, arg2, ...) {}); // 主应用发送事件 bus.$emit("事件名字", arg1, arg2, ...); // 主应用取消事件监听 bus.$off("事件名字", function (arg1, arg2, ...) {});例子:
bus.$on("vue3", (data: any) => { console.log(date, "我是主应用"); });子应用使用
// 子应用监听事件 window.$wujie?.bus.$on("事件名字", function (arg1, arg2, ...) {}); // 子应用发送事件 window.$wujie?.bus.$emit("事件名字", arg1, arg2, ...); // 子应用取消事件监听 window.$wujie?.bus.$off("件名字", function (arg1, arg2, ...) {});例子:
const send = () => { window.$wujie.bus.$emit("vue3", "我是子应用"); };