vite/vue3配置MicroApp
安装依赖
npm i @micro-zoe/micro-app --save
路由router.js:
{
// 因为主应用为history路由,appname-vite子应用是hash路由,这里配置略微不同
// 已解决带参数时页面丢失的问题
path: '/app-vite:page*',
name: 'vite',
component: () => import('@/views/vite.vue')
}
vite路由页面:
<template>
<div>
<micro-app
name="appname-vite"
:url="url"
inline
disablesandbox
:data="microAppData"
@created="handleCreate"
@beforemount="handleBeforeMount"
@mounted="handleMount"
@unmount="handleUnmount"
@error="handleError"
@datachange="handleDataChange"
></micro-app>
</div>
</template>
<script lang="ts">
import { EventCenterForMicroApp } from '@micro-zoe/micro-app';
import config from '../config';
// @ts-ignore 因为vite子应用关闭了沙箱,我们需要为子应用appname-vite创建EventCenterForMicroApp对象来实现数据通信
window.eventCenterForAppNameVite = new EventCenterForMicroApp('appname-vite');
export default {
name: 'vite',
data () {
return {
url: `${config.vite}/child/vite/`,
microAppData: { msg: '来自基座的数据' }
};
},
methods: {
handleCreate (): void {
console.log('child-vite 创建了');
},
handleBeforeMount (): void {
console.log('child-vite 即将被渲染');
},
handleMount (): void {
console.log('child-vite 已经渲染完成');
setTimeout(() => {
// @ts-ignore
this.microAppData = { msg: '来自基座的新数据' };
}, 2000);
},
handleUnmount (): void {
console.log('child-vite 卸载了');
},
handleError (): void {
console.log('child-vite 加载出错了');
},
handleDataChange (e: CustomEvent): void {
console.log('来自子应用 child-vite 的数据:', e.detail.data);
}
}
};
</script>
main.ts中:
import microApp from '@micro-zoe/micro-app';
microApp.start({
plugins: {
modules: {
//咱们的子应用是vite项目,在这配置
'appname-vite': [
{
loader(code: string) {
if (process.env.NODE_ENV === 'development') {
// 这里 /basename/ 需要和子应用vite.config.js中base的配置保持一致
code = code.replace(/(from|import)(\s*['"])(\/child\/vite\/)/g, all => {
return all.replace('/child/vite/', 'http://localhost:4007/child/vite/');
});
}
return code;
}
}
]
}
}
});
vite.config中:
import { defineConfig,searchForWorkspaceRoot } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path'; // 编辑器提示 path 模块找不到,可以yarn add @types/node --dev
import stylelintPlugin from 'vite-plugin-stylelint';
export default defineConfig({
plugins: [vue({
template: {
compilerOptions: {
isCustomElement: tag => /^micro-app/.test(tag)
}
}
}),
stylelintPlugin({
include: ['src/**/*.css', 'src/**/*.less', 'src/**/*.scss', 'src/**/*.sass', 'src/**/*.vue'],
cache: false
})
],
resolve: {
alias: {
'@': resolve(__dirname, 'src') // 设置 `@` 指向 `src` 目录
}
},
server: {
port: 3000, // 设置服务启动端口号
open: true ,// 设置服务启动时是否自动打开浏览器
fs: {
allow: [
searchForWorkspaceRoot(process.cwd()),
'/'
]
}
},
base: '/main/',
build: {
outDir: 'main'
}
});
以上就是主应用的改造,如果不需要路由,子应用只作为一个组件的话,和引入一个vue组件的方式相似。
子应用的main.ts中
import { createApp, App as AppInstance } from 'vue'
import { createRouter, createWebHashHistory, RouterHistory, Router } from 'vue-router'
import App from './App.vue'
import routes from './router'
declare global {
interface Window {
eventCenterForAppNameVite: any
__MICRO_APP_NAME__: string
__MICRO_APP_ENVIRONMENT__: string
__MICRO_APP_BASE_APPLICATION__: string
}
}
// 与基座进行数据交互
function handleMicroData (router: Router) {
// eventCenterForAppNameVite 是基座添加到window的数据通信对象
if (window.eventCenterForAppNameVite) {
// 主动获取基座下发的数据
console.log('child-vite getData:', window.eventCenterForAppNameVite.getData())
// 监听基座下发的数据变化
window.eventCenterForAppNameVite.addDataListener((data: Record<string, unknown>) => {
console.log('child-vite addDataListener:', data)
if (data.path && typeof data.path === 'string') {
data.path = data.path.replace(/^#/, '')
// 当基座下发path时进行跳转
if (data.path && data.path !== router.currentRoute.value.path) {
router.push(data.path as string)
}
}
})
// 向基座发送数据
setTimeout(() => {
window.eventCenterForAppNameVite.dispatch({ myname: 'child-vite' })
}, 3000)
}
}
/**
* 用于解决主应用和子应用都是vue-router4时相互冲突,导致点击浏览器返回按钮,路由错误的问题。
* 相关issue:https://github.com/micro-zoe/micro-app/issues/155
* 当前vue-router版本:4.0.12
*/
function fixBugForVueRouter4 (router: Router) {
// 判断主应用是main-vue3或main-vite,因为这这两个主应用是 vue-router4
if (window.location.href.includes('/main-vue3') || window.location.href.includes('/main-vite')) {
/**
* 重要说明:
* 1、这里主应用下发的基础路由为:`/main-xxx/app-vite`,其中 `/main-xxx` 是主应用的基础路由,需要去掉,我们只取`/app-vite`,不同项目根据实际情况调整
*
* 2、因为vite关闭了沙箱,又是hash路由,我们这里写死realBaseRoute为:/app-vite#
*/
const realBaseRoute = '/app-vite#'
router.beforeEach(() => {
if (typeof window.history.state?.current === 'string') {
window.history.state.current = window.history.state.current.replace(new RegExp(realBaseRoute, 'g'), '')
}
})
router.afterEach(() => {
if (typeof window.history.state === 'object') {
window.history.state.current = realBaseRoute + (window.history.state.current || '')
}
})
}
}
// ----------分割线---umd模式------两种模式任选其一-------------- //
let app: AppInstance | null = null
let router: Router | null = null
let history: RouterHistory | null = null
// 将渲染操作放入 mount 函数
function mount () {
history = createWebHashHistory()
router = createRouter({
history,
routes,
})
app = createApp(App)
app.use(router)
app.mount('#vite-app')
console.log('微应用child-vite渲染了')
handleMicroData(router)
// fixBugForVueRouter4(router)
}
// 将卸载操作放入 unmount 函数
function unmount () {
app?.unmount()
history?.destroy()
// 卸载所有数据监听函数
window.eventCenterForAppNameVite?.clearDataListener()
app = null
router = null
history = null
console.log('微应用child-vite卸载了')
}
// 微前端环境下,注册mount和unmount方法
if (window.__MICRO_APP_BASE_APPLICATION__) {
// @ts-ignore
window['micro-app-appname-vite'] = { mount, unmount }
} else {
// 非微前端环境直接渲染
mount()
}
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { join } from 'path'
import { writeFileSync } from 'fs'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
(function () {
let basePath = ''
return {
name: "vite:micro-app",
apply: 'build',
configResolved(config) {
basePath = `${config.base}${config.build.assetsDir}/`
},
// writeBundle 钩子可以拿到完整处理后的文件,但已经无法修改
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)
}
}
}
},
}
})() as any,
],
server: {
port: 4007,
},
base: `${process.env.NODE_ENV === 'production' ? 'http://www.micro-zoe.com' : ''}/child/vite/`,
build: {
outDir: 'vite',
},
})
附录:MicroApp中文文档 http://cangdu.org/micro-app/docs.html#/zh-cn/framework/vue