微前端无界方案
介绍
无界利用 iframe 和 webcomponent 来搭建天然的 js 沙箱和 css 沙箱。
能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 支持、应用共享等用户的核心诉求。
无界的使用
主应用
1、引入
// 无框架时使用'wujie'
import Wujie from 'wujie';
// 当结合框架时使用'wujie-xxx'
// import Wujie from "wujie-vue2";
// import Wujie from "wujie-vue3";
// import Wujie from "wujie-react";
const {
bus, setupApp, preloadApp, startApp, destroyApp } = Wujie;
提示
如果主应用是 vue 框架可直接使用 wujie-vue,react 框架可直接使用 wujie-react
2、设置子应用
【非必须】由于 preloadApp
和 startApp
的参数重复,为了避免重复输入,可以通过 setupApp
来统一设置默认参数。
setupApp({
name: '唯一id',
url: '子应用地址',
exec: true,
el: '容器',
sync: true,
});
3-1、启动子应用
startApp({
name: '唯一id' });
3-2、预加载
preloadApp({
name: '唯一id' });
3-3、以组件形式调用
无界支持以组件的形式使用。
vue
安装
# vue2 框架
npm i wujie-vue2 -S
# vue3 框架
npm i wujie-vue3 -S
引入
// main.js
// vue2
import WujieVue from 'wujie-vue2';
// vue3
// import WujieVue from "wujie-vue3";
// 全局注册组件(以vue为例)
Vue.use(WujieVue);
const {
bus, setupApp, preloadApp, startApp, destroyApp } = WujieVue;
使用
使用 组件,相当于使用了startApp
来调用,因此可以忽略startApp
的使用了!!
<template>
<!-- 单例模式,name相同则复用一个无界实例,改变url则子应用重新渲染实例到对应路由 -->
<WujieVue
width="100%"
height="100%"
name="vue2"
:url="vue2Url"
:sync="true"
:fetch="fetch"
:props="props"
:beforeLoad="beforeLoad"
:beforeMount="beforeMount"
:afterMount="afterMount"
:beforeUnmount="beforeUnmount"
:afterUnmount="afterUnmount"
></WujieVue>
<!-- 子应用通过$wujie.bus.$emit(event, args)出来的事件都可以直接@event来监听 -->
</template>
<script>
// import hostMap from "./hostMap";
export default {
computed: {
vue2Url() {
// 这里拼接成子应用的域名(例如://localhost:7200/home)
return hostMap('//localhost:7200/') + `#/${
this.$route.params.path}`;
},
},
};
</script>
// hostMap.js
const map = {
'//localhost:7100/': '//wujie-micro.github.io/demo-react17/',
'//localhost:7200/': '//wujie-micro.github.io/demo-vue2/',
'//localhost:7300/': '//wujie-micro.github.io/demo-vue3/',
'//localhost:7500/': '//wujie-micro.github.io/demo-vite/',
};
export default function hostMap(host) {
if (process.env.NODE_ENV === 'production') return map[host];
return host;
}
WujieVue组件
接收的参数如下:
WujieVue组件
接收的参数基本上与startApp
的一致。
不同之处在于startApp
有html
、el
,没有width
、height
。
const wujieVueOptions = {
name: 'WujieVue',
props: {
width: {
type: String, default: '' },
height: {
type: String, default: '' },
name: {
type: String, default: '' },
loading: {
type: HTMLElement, default: undefined },
url: {
type: String, default: '' },
sync: {
type: Boolean, default: false },
prefix: {
type: Object, default: undefined },
alive: {
type: Boolean, default: false },
props: {
type: Object, default: undefined },
replace: {
type: Function, default: undefined },
fetch: {
type: Function, default: undefined },
fiber: {
type: Boolean, default: true },
degrade: {
type: Boolean, default: false },
plugins: {
type: Array, default: null },
beforeLoad: {
type: Function, default: null },
beforeMount: {
type: Function, default: null },
afterMount: {
type: Function, default: null },
beforeUnmount: {
type: Function, default: null },
afterUnmount: {
type: Function, default: null },
activated: {
type: Function, default: null },
deactivated: {
type: Function, default: null },
loadError: {
type: Function, default: null },
},
};
子应用改造
无界对子应用的侵入非常小,在满足跨域条件下子应用可以不用改造。
1、前提
子应用的资源和接口的请求都在主域名发起,所以会有跨域问题,子应用必须做cors 设置。
app.use((req, res, next) => {
// 路径判断等等
res.set({
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Origin': req.headers.origin || '*',
'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
'Content-Type': 'application/json; charset=utf-8',
});
// 其他操作
});
2、生命周期改造
改造入口函数:
- 将子应用路由的创建、实例的创建渲染挂载到
window.__WUJIE_MOUNT
函数上 - 将实例的销毁挂载到
window.__WUJIE_UNMOUNT
上 - 如果子应用的实例化是在异步函数中进行的,在定义完生命周期函数后,请务必主动调用无界的渲染函数
window.__WUJIE.mount()
具体操作可以参考下面示例
// vue 2
if (window.__POWERED_BY_WUJIE__) {
let instance;
window.__WUJIE_MOUNT = () => {
const router = new VueRouter({
routes });
instance = new Vue({
router, render: (h) => h(App) }).$mount('#app');
};
window.__WUJIE_UNMOUNT = () => {
instance.$destroy();
};
} else {
new Vue({
router: new VueRouter({
routes }), render: (h) => h(App) }).$mount('#app');
}
// vue 3
if (window.__POWERED_BY_WUJIE__) {
let instance;
window.__WUJIE_MOUNT = () => {
const router = createRouter({
history: createWebHistory(), routes });
instance = createApp(App);
instance.use(router);
instance.mount('#app');
};
window.__WUJIE_UNMOUNT = () => {
instance.unmount();
};
} else {
createApp(App)
.use(createRouter({
history: createWebHistory(), routes }))
.mount('#app');
}
// vite
declare global {
interface Window {
// 是否存在无界
__POWERED_BY_WUJIE__?: boolean;
// 子应用mount函数
__WUJIE_MOUNT: () => void;
// 子应用unmount函数
__WUJIE_UNMOUNT: () => void;
// 子应用无界实例
__WUJIE: {
mount: () => void };
}
}
if (window.__POWERED_BY_WUJIE__) {
let instance: any;
window.__WUJIE_MOUNT = () => {
const router = createRouter({
history: createWebHistory(), routes });
instance = createApp(App)
instance.use(router);
instance.mount("#app");
};
window.__WUJIE_UNMOUNT = () => {
instance.unmount();
};
/*
由于vite是异步加载,而无界可能采用fiber执行机制
所以mount的调用时机无法确认,框架调用时可能vite
还没有加载回来,这里采用主动调用防止用没有mount
无界mount函数内置标记,不用担心重复mount
*/
window.__WUJIE.mount()
} else {
createApp(App).use(createRouter({
history: createWebHistory(), routes })).mount