Skip to content
当前目录

无界微前端实践

背景

团队主技术栈为 React,但存在部分特殊的老项目基于 Vue2.x 相关技术栈开发,存在以下痛点:

  • 单文件组件的维护性差,存在不少上千行的文件
  • 大量 mixin 代码维护性极差,难以排查问题,令人无所适从
  • Vue2 对 TypeScript 的兼容差,即使改造了一些模块后,开发体验仍然很差
  • 技术栈不统一造成心智负担重
  • ElementUI 年久失修,功能缺失且不再维护,很多 Antd 能够满足的需求到 Element 却需要自己实现

虽然存在这么多的问题,但平时的需求开发已经消耗掉了大半的精力,整个项目层面的技术重构专项是绝不会被排进迭代的,所以我们的目标就变成了:在平时的开发过程识别需求规模,当某一模块调整范围较大时对其进行重构,以达到渐进式升级的目的。这样一来会存在很长一段时间的老 Vue 和新 React 应用并行的阶段,这是无可厚非的。

第一阶段我们新建一个 React 项目,将一个较大模块下的所有页面从 Vue 迁移到了 React,在顶部菜单栏切换时在新老站点间进行跳转,但也导致了以下问题:

  • 由于跳转前后是两个应用,每次对于路由/菜单/权限等公共逻辑的修改需要在两个项目进行同步,较难维护
  • 站点间的跳转通过直接修改 window.location.href 实现,每次跳转过后头部及侧边栏菜单都会重新加载,无法实现平滑过渡
  • 由于前一条导致重构动力不足,进度迟缓

重构的目标是什么?提升开发体验 + 用户体验!但到现在为止,我们好像有点违背了初衷?

于是,微前端的接入需求就应运而生了。但正常的需求都做不完呢,我们需要一个低成本接入的方式。我们调研了市面上几乎所有的微前端框架:

  • Single-Spa
  • Qiankun
  • MicroApp
  • Wujie
  • IceStark
  • EMP
  • Garfish

Wujie 以超低成本接入,子应用几乎无需改造的优势脱颖而出。

Wujie 是什么

Wujie,中文名无界。它是一个基于 WebComponent 容器 + iframe 沙箱的微前端方案,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等问题,并且改造成本超低。

为什么选它

最重要的原因:接入低成本,主应用嵌入子应用组件,子应用监听路由跳转即可。

改造步骤

主应用改造

安装 wujie 基于 vue 的框架封装:

bash
pnpm add wujie-vue2

入口文件改造:

js
import WujieVue from 'wujie-vue2'
const { bus } = WujieVue

Vue.use(WujieVue)

/**
 * 初始化vue应用
 */
const initInstance = () =>
	new Vue({
		router,
		store,
		render: h => h(App),
	}).$mount('#app')

if (window.__POWERED_BY_WUJIE__) {
	let instance
	window.__WUJIE_MOUNT = () => {
		instance = initInstance()
	}
	window.__WUJIE_UNMOUNT = () => {
		instance.unmount()
	}
	/*
		由于vite是异步加载,而无界可能采用fiber执行机制
		所以mount的调用时机无法确认,框架调用时可能vite
		还没有加载回来,这里采用主动调用防止用没有mount
		无界mount函数内置标记,不用担心重复mount
	*/
	window.__WUJIE.mount()
} else {
	// 正常vue应用的初始化流程
	initInstance()
}

在需要渲染子应用的地方嵌入 wujie 组件:

html
<wujie-vue
	width="100%"
	:height="'100%'"
	name="wujie-react"
	:url="childUrl"
	:sync="true"
	:alive="true"
></wujie-vue>

组件属性说明:

  • name 子应用的唯一标识
  • url 子应用url
  • sync 开启路由同步
  • alive 子应用保活

主应用通知子应用路由改变:

js
import WujieVue from 'wujie-vue2'
const { bus } = WujieVue

router.beforeEach((to, from, next) => {

	// 通过wujie的事件中心通知子应用
	bus.$emit('ROUTE_CHANGE', {
		path: to.path,
		type: 'navigate',
	})

	// 自身的路由切换
	next()
})

主应用监听子应用内部发起的跳转:

js
import WujieVue from 'wujie-vue2'
const { bus } = WujieVue

bus.$on('CHILD_JUMP', pathname => {
	router.push({ path: pathname })
})

子应用改造

子应用监听主应用路由变化事件进行跳转:

ts
import { useNavigate } from 'react-router-dom'

const navigate = useNavigate()

useEffect(() => {
	// 子应用监听到主应用发送的路由变化事件后,内部跳转到对应的页面,实现路由同步
	window.$wujie?.bus.$on('ROUTE_CHANGE', function (params: { path: string }) {
		const { path } = params
		navigate(path)
	})
}, [])

当子应用内部需要跳转到其他页面时,需要通知主应用:

ts
// 子应用内部跳转其他页面时,需要通知主应用进行跳转,而不是自己跳
// 这样做的原因是需要通知主应用,如果非子应用页面,由主应用渲染,如果是子应用页面,则再通知回子应用进行跳转
export const _jump = (path: string) => {
	window.$wujie?.bus.$emit('CHILD_JUMP', path)
}

这样一来,所有的跳转都由主应用进行控制,从而形成路由闭环。

Created and maintained by Lexmin0412.