无界微前端
无界微前端技术
官网:无界
和其他微前端技术一样,该有的都有。和qiankun的对比。

无界

iframe负责逻辑,webComponent负责Dom。
如果是新项目,且相当使用比较新的浏览器,可以考虑无界,开箱即用,当然无界也提供了兼容的降级处理。
快速使用
无界已经帮我们封装好了vue和react的使用。
vue
react
新建三个项目
新建一个文件夹wujie,执行下面的命令三次,主应用选择Vue,其他两个项目分别为vue和react。
npm init vite@latest

安装无界
在主应用中,安装wujie
bash
# vue2 框架
npm i wujie-vue2 -S
# vue3 框架
npm i wujie-vue3 -S
分别启动三个项目。
npm run dev
port:我这里的主应用为5173,子vue为5174,子react为5175。
使用
在主应用的App.vue中
html
<script setup>
import WujieVue from 'wujie-vue3';
</script>
<template>
<div>
<WujieVue url="http://localhost:5174" name="vue" />
</div>
</template>
在子vue应用App.vue


把主应用Aoo.vue中的子应用链接改为5175

增加路由
主应用和vue子应用安装 vue-router
npm install vue-router@4
主应用路由
在src/router/index.js
js
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
},
{
path: '/vue/:pathMatch(.*)*',
name: 'vue',
component: () => import('../views/AppContainer.vue'),
},
],
});
export default router;
新增文件

在AppContainer.vue中
html
<template>
<WujieVue :url="path" name="vue" />
</template>
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const baseUrl = 'http://localhost:5174/';
const route = useRoute();
const path = computed(() => {
const pathMatch = route?.params?.pathMatch || [];
return baseUrl + pathMatch.join('/');
});
</script>
<style></style>
在main.js引入路由

在 App.vue
html
<script setup></script>
<template>
<div>
<div>
<router-link to="/">home | </router-link>
<router-link to="/about">about | </router-link>
<router-link to="/vue/about">vue | </router-link>
<router-link to="/vue/">vue1 </router-link>
</div>
<div>
<router-view v-slot="{ Component }">
<transition name="fade">
<keep-alive :include="['Home', 'AppContainer']">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</div>
</div>
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
子应用vue路由

Home.vue
html
<template>
<div>
首页
<button @click="toAbout">去About</button>
<HelloWorld msg="Vite + Vue" />
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
import HelloWorld from '../components/HelloWorld.vue';
const router = useRouter();
const toAbout = () => {
router.push('/about');
};
</script>
<style></style>
App.vue
html
<div>
<router-view></router-view>
</div>
尝试下

但是这里的话,在vue1这里,进入子应用About,刷新后会在vue子应用首页。



通信
父子应用之间的通信。
文档
props 通信
父应用传递给子应用数据和方法。
父AppContainer.vue
html
<template>
<div>
<h4>父:{{ count }}</h4>
<WujieVue
:url="path"
:props="{ data: { count }, add }"
:sync="true"
name="vue"
/>
</div>
</template>
<script setup>
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
const count = ref(0);
const add = () => {
count.value++;
};
const baseUrl = 'http://localhost:5174/';
const route = useRoute();
const path = computed(() => {
const pathMatch = route?.params?.pathMatch || [];
return baseUrl + pathMatch.join('/');
});
</script>
<style></style>
子About,vue
html
<template>
<div>
<h5>子{{ props.data.count }}</h5>
<button @click="countAdd">Count++</button>
</div>
</template>
<script setup>
const props = window.$wujie?.props;
const countAdd = () => {
props.add();
};
</script>
<style></style>

子中数据没更新。
eventBus 通信
我们可以让父应用发出数据更新的事件,让子应用接受到事件后更新显示。
更新AppContainer.vue代码

更新子应用About.vue代码
html
<template>
<div>
<h5>子{{ count }}</h5>
<button @click="countAdd">Count++</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const props = window.$wujie?.props;
const count = ref(props?.data?.count || 0);
const countAdd = () => {
props.add();
};
window.$wujie?.bus.$on('add', function (num) {
count.value = num;
});
</script>
<style></style>

window 通信
在子应用About.vue中

在主应用AppContainer.vue中



兼容
降级处理。

无界会自动处理,如果你想强制开启

开启后变成这样了
要做一些样式调整。
比如给容器加个高度

主应用style.css调整iframe样式
css
/* 主页面 CSS */
iframe {
/* 消除边框 */
border: none;
/* 消除内边距和外边距 */
padding: 0;
margin: 0;
/* 可选:设置宽高(避免默认尺寸过小) */
width: 100%;
height: 100%;
/* 可选:隐藏溢出内容(避免默认滚动条) */
overflow: hidden;
}
子应用的宽高调整等等,但是最好判断下是不是无界启动,还是单独启动。


生命周期
需要在子应用进行生命周期改造。


改造子应用
这里和qiankun的很像。
js
if (window.__POWERED_BY_WUJIE__) {
console.log('wujie vue child');
let instance;
window.__WUJIE_MOUNT = () => {
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(router).mount('#app');
}

改造主应用



运行模式

这里我们每次切换路由,都会触发生命周期函数。
保活模式



开启保活模式后 只在首次触发。但是他会额外的触发

如果我们做B端,比如重构的时候,运行在新项目,但是新项目是需要时间来重构的,怎么办呢,按照官方说法,不推荐保活模式,推荐单例模式。
单例模式
我们刚刚进行了生命周期改造,把保活模式去掉,就是单例模式。

但是按照刚刚的生命周期方法做,路由显示会有问题。

可能我的打开方式不对。
在主应用中通知路径,在子应用mount之前跳转。




重建模式
不配置保活,也不配置生命周期改造。