微前端(4)wujie

这篇记录 wujie 相关实验。

wujie

wujie 基于 webcomponent 容器 + iframe 沙箱,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户的核心诉求。

实验步骤

  1. 主应用是 vue 框架,可直接使用 wujie-vue(官方提供基于 vue 封装)
  2. 子应用的资源和接口的请求都在主域名发起,所以会有跨域问题,子应用必须做 cors 设置

代码实例

main-app/src/main.ts 主应用中使用 wujie-vue3,这是基于 vue3 已经封装好的语法糖。

1
2
3
4
import WujieVue from 'wujie-vue3'
const { bus, setupApp, preloadApp, destroyApp } = WujieVue

app.use(WujieVue).mount('#app')

main-app/src/views/layout/components/app-main.vue 中使用,可通过 props 传参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<WujieVue name="vue3" url=" http://127.0.0.1:8081/" :props="{ name: 'ginny' }"></WujieVue>
<!-- 完整参数如下: -->
<WujieVue
width="100%"
height="100%"
name="xxx"
url="xxx"
:sync="true"
:fiber="true"
:degrade="false"
:fetch="fetch"
:props="props"
:plugins="plugins"
:beforeLoad="beforeLoad"
:beforeMount="beforeMount"
:afterMount="afterMount"
:beforeUnmount="beforeUnmount"
:afterUnmount="afterUnmount"
></WujieVue>

child-app/src/XXX.vue 子系统获取传值如下:

1
console.log('子应用中通过 props 获取的值', window.$wujie.props.name)

通过 bus 通信

wujie 通信方式有通过 window.parent 直接通信,通过 Props 数据注入(见上文)和去中心化 EventBus 通信方式。
main-app/src/main.ts

1
2
3
4
5
6
7
8
const { bus } = WujieVue

// 主应用监听事件
bus.$on("事件名字", function (arg1, arg2, ...) {});
// 主应用发送事件
bus.$emit("事件名字", arg1, arg2, ...);
// 主应用取消事件监听
bus.$off("事件名字", function (arg1, arg2, ...) {});

child-app/src/main.ts

1
2
3
4
5
6
7
8
9
10
declare global {
interface Window {
$wujie: {
props: Record<string, any>
bus: {
$emit: any
}
}
}
}

child-app/src/XXX.vue

1
2
3
4
5
6
// 子应用监听事件
window.$wujie?.bus.$on("事件名字", function (arg1, arg2, ...) {});
// 子应用发送事件
window.$wujie?.bus.$emit("事件名字", arg1, arg2, ...);
// 子应用取消事件监听
window.$wujie?.bus.$off("事件名字", function (arg1, arg2, ...) {});

生命周期

beforeLoad:子应用开始加载静态资源前触发
beforeMount:子应用渲染前触发(生命周期改造专用)
afterMount:子应用渲染后触发(生命周期改造专用)
beforeUnmount:子应用卸载前触发(生命周期改造专用)
afterUnmount:子应用卸载后触发(生命周期改造专用)
activated:子应用进入后触发(保活模式专用)
deactivated:子应用离开后触发(保活模式专用)

Q & A

  1. 挂载到哪里最合适?
  2. 应用切换时 URL 不会发生改变,如果使用动态路由,怎么获取对应的后缀?通过 props 传值?
  3. “无界微前端不仅能够做到静态资源的预加载,还可以做到子应用的预执行”,怎么操作?

使用感受

  1. 改造成本不高,主要是主应用需要改造,子应用的适配只需要开启跨域支持即可(不涉及通信的情况下)

默认运行的方式是重建模式。当子应用设置为保活模式,切换子应用后仍然可以保持子应用的状态和路由不会丢失。
子应用适配生命周期适配进入不同的运行模式

公司应用配置

版本:”wujie-vue3”: “^1.0.22”

主应用

public/config.js中配置子应用信息

1
2
3
4
5
6
7
8
9
10
window.THIRD_APPS = [
{
name: 'controller-app',
url: 'http://***''
},
{
name: 'af3-app',
url: 'http://***'
}
]

main.ts中引入:

1
2
3
4
5
6
7
8
import WujieVue from 'wujie-vue3'
// 创建实例
const setupAll = async () => {
const app = createApp(App)
app.use(WujieVue).mount('#app')
}

setupAll()

应用中心,设置微前端入口

1
2
3
4
5
6
7
8
9
10
11
12
const gotoController = (pane: any, ev: any) => {
console.log('pane: ', pane)
console.log('ev: ', ev)
if (pane?.props?.name == 'controller') {
const { href } = router.resolve({
path: '/controller'
})
window.location.href = href
} else if (pane?.props?.name == 'app') {
router.push('/app/center')
}
}

/views/ThirdApp/page.vue 中,点击应用卡片,跳转到子应用,名字对应之前配置的地址。

1
2
3
4
5
6
7
8
9
10
11
12
const toTry = (name: string) => {
if (name === 'AF3') {
const { href } = router.resolve({
path: '/thirdApp',
query: {
app: 'af3-app'
}
})
console.log('href: ', href)
window.open(href, '_blank')
}
}

router/index.ts 中配置了子应用渲染的路由

1
2
3
4
5
6
7
8
{
path: '/thirdApp',
component: () => import('@/views/ThirdApp/index.vue'),
name: 'ThirdApp',
meta: {
hidden: true
}
}

ThirdApp/index.vue中,主应用向子应用传参数。

1
2
3
4
<template>
<!--单例模式,name相同则复用一个无界实例,改变url则子应用重新渲染实例到对应路由 -->
<WujieVue width="100%" height="100%" :name="name" :url="url" :props="{ userId: '12345678' }" />
</template>
1
2
3
4
5
6
7
import { useUserStoreWithOut } from '@/store/modules/user'

const userStore = useUserStoreWithOut()
const route = useRoute()
const name = route?.query?.app
const url = window?.THIRD_APPS?.find((item: any) => item.name == name)?.url
console.log('name: ', name)

子应用

子应用中main.ts中引入,可直接获取主应用的传值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import WujieVue from 'wujie-vue3'

// 创建实例
const setupAll = async () => {
app = createApp(App)
app.use(WujieVue).mount('#app')
}

console.log('__POWERED_BY_WUJIE__: ', window?.__POWERED_BY_WUJIE__)
if (window?.__POWERED_BY_WUJIE__) {
setToken(window.$wujie.props.token)
setUserInfo(window.$wujie.props.userInfo)
}
if (window?.__POWERED_BY_WUJIE__) {
window.__WUJIE_MOUNT = () => {
setupAll()
}
window.__WUJIE_UNMOUNT = () => {
app.unmount()
}

types/global.d.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  interface Window {
readonly APP_CONFIG: any
readonly BASIC_CONFIG: any
readonly THIRD_APPS: any
$wujie: any
// 是否存在无界
__POWERED_BY_WUJIE__?: boolean
// 子应用mount函数
__WUJIE_MOUNT: () => void
// 子应用unmount函数
__WUJIE_UNMOUNT: () => void
// 子应用无界实例
__WUJIE: { mount: () => void }
}
}

附录
无界(基于iframe)
微前端实际应用案例剖析(wujie)


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!