背景
公司内部突然有个微前端想法,整合项目, 但是我们新项目都是
vue3 + vite, 就简单考察了下微前端框架。
- 需求应用间耦合性小
- 项目方便升级,可以独立发布,主要能兼容
vite - 最终暂定了microp
本文主要介绍的是vite环境的一些兼容micro-app配置
1、实现效果
2、项目主要结构
- 基座应用为
main-app, 环境为vite + vue3 - 子应用为
child-app-1, 环境为vite + vue3...
└─ main-app (基座-history路由- vite+ vue3)
└─child-app-1 (子应用- hash路由 - vite+ vue3)
└─child-app-2 (子应用- hash路由 - vite+ vue3)
3、父子应用版本 & 仓库
- 版本
"@micro-zoe/micro-app": "^0.8.6",
"vite": "^2.9.5",
4、代码配置
主应用 main-app, history路由
子应用 child-app-1, hash路由
4.1 基座应用main-app
- 安装
npm i @micro-zoe/micro-app --save
4.1.1 main.js
main.js中注入子应用 , 我这里子应用叫child-app-1
import microApp from '@micro-zoe/micro-app'
// 微前端-microApp-注入
microApp.start({
plugins: {
modules: {
// appName即应用的name值
'child-app-1': [
{
loader(code) {
if (process.env.NODE_ENV === 'development') {
// 这里 basename 需要和子应用vite.config.js中base的配置保持一致
// eslint-disable-next-line no-param-reassign
code = code.replace(/(from|import)(\s*['"])(/child-app-1/)/g, all => {
return all.replace('/child-app-1/', 'http://localhost:4001/child-app-1/')
})
}
return code
}
}
],
bdcp: [
{
loader(code) {
if (process.env.NODE_ENV === 'development') {
// 这里 basename 需要和子应用vite.config.js中base的配置保持一致
// eslint-disable-next-line no-param-reassign
code = code.replace(/(from|import)(\s*['"])(/bdcp/)/g, all => {
return all.replace('/bdcp/', 'http://localhost:5001/bdcp/')
})
}
return code
}
}
]
}
}
})
4.1.2 路由配置
- 配置路由, 让包含
child-app-1的路由 都重定向到这里来
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
name: 'vue3Vite',
// 👇 非严格匹配,/my-page/* 都指向 vue3-vite 页面
path: '/child-app-1/:page*',
component: '自己填路径'
},
]
const router = createRouter({
// history模式
history: createWebHistory(import.meta.env.BASE_URL),
routes // `routes: routes` 的缩写
})
4.1.3 渲染路由
- html
url根据开发和生产环境自己切换 这边注意 vite需要关闭沙箱
<micro-app
name="child-app-1"
url="http://localhost:4001/child-app-1/"
inline
disableSandbox
></micro-app>
4.1.4 菜单-sideBar
- 流程
由于父子应用都是
vite, 关闭了沙箱,我们需要父应用控制子应用进行路由跳转
- 子应用跳转关键代码
import microApp, { EventCenterForMicroApp, getActiveApps } from '@micro-zoe/micro-app'
// 因为vite子应用关闭了沙箱,我们需要为子应用child-app-1 创建EventCenterForMicroApp对象来实现数据通信
window.eventCenterForChildApp_1 = new EventCenterForMicroApp('child-app-1')
/**
* 当子应用还未渲染,通过基座控制路由跳转,子应用在初始化时会自己根据url渲染对应页面
* 当子应用已经渲染,则直接控制子应用进行内部跳转
*
* getActiveApps: 用于获取正在运行的子应用
*/
const menuItemClick = item => {
console.log(96, '获取已经渲染的子应用', getActiveApps())
// eslint-disable-next-line prefer-const
let { appFrom: appName, path } = item // item为菜单点击的对象
console.log(114, '应用名和地址', appName, path)
// 子应用未加载
if (!getActiveApps().includes(appName)) {
// 这里默认是hash, 初始化path 这里拼接一下hash值
const pushPath = `/${appName}/#${path}`
console.log('主应用控制跳转--------->')
// 主应用跳转
router.push(pushPath)
}
// 子应用已加载
else {
// 传递事件给子应用, 然后让子应用跳转
console.log('子应用控制跳转--------->')
// 向子应用传递路由 让子路由进行跳转
microApp.setData(appName, { path: item.path })
}
}
4.2 子应用child-app-1
4.2.1 vite.config.js
plugins: [
vue(),
// 自定义插件
(function () {
let basePath = "";
return {
name: "child-app-1", //组件名称
apply: "build",
configResolved(config) {
basePath = `${config.base}${config.build.assetsDir}/`;
},
writeBundle(options, bundle) {
for (const chunkName in bundle) {
if (Object.prototype.hasOwnProperty.call(bundle, chunkName)) {
const chunk = bundle[chunkName];
if (chunk.fileName && chunk.fileName.endsWith(".js")) {
chunk.code = chunk.code.replace(
/(from|import()(\s*['"])(..?/)/g,
(all, $1, $2, $3) => {
return all.replace($3, new URL($3, basePath));
}
);
const fullPath = join(options.dir, chunk.fileName);
writeFileSync(fullPath, chunk.code);
}
}
}
},
};
})(),
],
server: {
host: '0.0.0.0',
port: 4001
},
base: `${process.env.NODE_ENV === 'production' ? 'http://www.lufangzhou.top:3307' : ''}/child-app-1/`,
4.2.2 main.js
- 进行监听基座应用菜单点击事件,进行跳转
// 监听跳转
if(window.eventCenterForChildApp_1){
window.eventCenterForChildApp_1.addDataListener((data) => {
// 当基座下发跳转指令时进行跳转
if (data.path && typeof data.path === 'string') {
console.log(12, data.path)
data.path = data.path.replace(/^#/, '')
// console.log(13, router.currentRoute.value.path)
// 当基座下发path时进行跳转
if (data.path && data.path !== router.currentRoute.value.path) {
// console.log('child-app-1响应事件', data)
router.push(data.path)
}
// console.log('------------------------')
}
}, true)
}
// 监听销毁事件
/** ********************监听事件***********************/
window.addEventListener(`unmount-child-app-1`, destroyWindowListen, true)
function destroyWindowListen() {
window.eventCenterForChildApp_1?.clearDataListener()
// console.log(34, '监听卸载事件')
window.removeEventListener('unmount-child-app-1', destroyWindowListen, true)
}
4.2.3 应用名
由于没有沙箱, 把应用名改为
child-app-1, 确保唯一性
<div id="child-app-1"></div>
5、问题
5.1 [Vue warn]: Failed to resolve component: micro-app
- 注入自定义组件识别
plugins: [
vue({
template: {
compilerOptions: {
// 注册自定义组件micro-app 防止控制台警告
isCustomElement: tag => /^micro-app/.test(tag)
}
}
}),
],
5.2 路由回退问题
5.3 子应用本地资源404
- 使用路径包装下
const logoPath = new URL('../src/assets/logo-child.png', import.meta.url).href