微前端 Micro app
qiankun官网
wujie官网
Micro app 官网
Micro app + vite 子应用DEMO
通过调研:乾坤、wujie 、microapp 官网、github、搜索相关资料,发现目前micro app 对现在的vite支持的最好。
验证Micro app当子应用是vite的时候是否会对沙箱有影响;
下载Micro app + vite 子应用DEMO 子应用DEMO本地启动服务;
node:v18.17.0
micro app:^1.0.0-rc.4
vite:5.0.11
在父应用添加:
window.testChildValue = {
txt: 'testTxt'
};
console.log('父应用输出:',window.testChildValue.txt ); // 父应用输出:testTxt
在子应用添加:
setTimeout(() => {
console.log('子应用输出:', window.testChildValue ); // 子应用输出:undefined
})
注意事项
- 父子应用:locstorage、cookie、sessionStorage 存储命名添加项目头部,防止命名冲突。
- 如全局样式、!important 规则会绕过css沙箱机制,导致父应用使用!important的全局样式影响到子应用,父应用尽量避免全局样式使用 !important。
- 父应用与子应用的根节点的ID名称(index.html根节点的ID名称)不能重复
- 子应用router必须使用history模式
- 子应用elment-plus的样式不能在main.ts直接引用,因为element会声明很多全局对象,影像父应用的样式
使用 vite-plugin-style-import@2.0.0 版本 按需加elment-plus的样式,vite.config.ts:
import { createStyleImportPlugin, ElementPlusResolve } from "vite-plugin-style-import"
export default defineConfig((mode): any => {
const env = loadEnv(mode.mode, process.cwd())
return {
plugins: [
......
createStyleImportPlugin({
resolves: [ElementPlusResolve()],
libs: [
{
libraryName: "element-plus",
esModule: true,
resolveStyle: (name) => {
// 屏蔽element-plus不需要按需加载样式的选项
if (name === "click-outside" || name === "dayjs") {
return ""
}
return `element-plus/es/components/${name.replace(/^el-/, "")}/style/css`
}
}
]
}),
......
}
})
子应用接入micro-app
main.ts
import { createApp, toRaw } from "vue"
import App from "@/App.vue"
import { setupStore } from "@/store"
import { constantRouterMap } from "@/router"
import { createRouter, createWebHistory } from "vue-router"
import type { RouteRecordRaw } from "vue-router"
import { filterAsyncRoutes } from "@/utils/permission"
import "@/styles/index.scss"
import "virtual:svg-icons-register"
/**
* 解决火狐(版本大于107)兼容性问题
* 代理 instanceof Element 方法, 手动监测 element 是否是 dom 元素
* */
if (
navigator.userAgent.indexOf('Firefox') > -1 &&
+navigator.userAgent.match(/Firefox\/([\d.]+)/)![1] > 107
) {
Object.defineProperty(Element, Symbol.hasInstance, {
value: function (node: any) {
if (typeof node !== 'object' || !node) return false;
node = node.__proto__;
while (node) {
// 重写 instanceof 判断逻辑
if (
Object.prototype.toString.call(node) ===
this.prototype.toString()
)
return true;
node = node.__proto__;
}
return false;
},
});
}
declare global {
interface Window {
microApp: any
mount: CallableFunction
unmount: CallableFunction
__MICRO_APP_ENVIRONMENT__: string
__MICRO_APP_BASE_ROUTE__: string
__MICRO_APP_NAME__: string
__MICRO_APP_PUBLIC_PATH__: string
__MICRO_APP_BASE_APPLICATION__: string
}
}
function handleMicroData() {
// 监听基座下发的数据变化
// window.microApp?.addDataListener((data: any) => {
// console.log('child-vite addDataListener:', data)
// }, true)
// 向基座发送数据
setTimeout(() => {
window.microApp?.dispatch({ myname: '应用名称' })
}, 3000)
}
let app: any = null
let router: any = null
let history: any = null
// 将渲染操作放入 mount 函数
window.mount = () => {
if(window.__MICRO_APP_ENVIRONMENT__){
const data = window.microApp.getData()
if(constantRouterMap[0].path === "/" && data.routes){
// filterAsyncRoutes:根据如有表寻找 src/views/ 文件,并绑定到路由表上的component属性上
constantRouterMap[0].children = [ ...filterAsyncRoutes(toRaw(data.routes)) ]
}
}
history = createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || import.meta.env.BASE_URL)
router = createRouter({
history: history,
strict: true,
routes: constantRouterMap as RouteRecordRaw[],
scrollBehavior: () => ({ left: 0, top: 0 })
})
app = createApp(App)
setupStore(app)
app.use(router)
app.mount("#xxxApp")
handleMicroData()
}
// 将卸载操作放入 unmount 函数
window.unmount = () => {
app && app.unmount()
history && history.destroy()
app = null
router = null
history = null
console.log('微应用vite卸载了 -- UMD模式');
}
// 非微前端环境直接渲染
if (!window.__MICRO_APP_ENVIRONMENT__) {
window.mount()
}
vite.config.ts 本地设置跨域支持。
...
// https://vitejs.dev/config/
export default defineConfig({
base: "/xxx/", //项目基础路径 micro app name
...
server: {
port: xxxx,
host: true,
headers: {
'Access-Control-Allow-Origin': '*'
},
...
}
})