搭建配置qiankun
什么是微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
优点:
- 与技术栈无关
- 独立开发、独立部署,部署完成后主框架自动同步更新
- 增量升级,兼容老项目
- 每个微应用之间状态隔离
方案
-
iframe:通过iframe标签嵌入父应用
url不同步,刷新页面时路由丢失;全局上下文隔离;window对象不一致;加载速度慢 -
single-spa:单页面应用,url改变时进行匹配
没有实现js隔离和css隔离;需求修改大量配置 -
qiankun:基于single-spa封装,提供API;和技术栈无关;使用方便;实现了js隔离和css隔离;资源预加载,浏览器空闲时间预加载未打开的资源应用
基座
- 安装qiankun
pnpm i qiankun
- main.js中进行改造
//main.js
import './assets/main.scss'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { registerMicroApps, start } from 'qiankun'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
registerMicroApps(
[
{
name: 'vue-app-1', // 必须与微应用注册名字相同
entry: 'http://localhost:9001', // 入口路径,开发时为微应用所启本地服务,上线时为微应用线上路径
container: '#sub-app', // 微应用挂载的节点
activeRule: '/vue-app-1', // 当访问路由为 /micro-vue 时加载微应用
props: {
msg: '我是来自主应用的值-vue' // 主应用向微应用传递参数
},
pathPrefix: '/vue-app-1' // 使用 pathPrefix 自动添加前缀
}
// {
// name: 'react-app',
// entry: 'http://127.0.0.1:5175',
// container: '#react-app-container',
// activeRule: '/micro-react',
// props: {
// msg: '我是来自主应用的值-react',
// },
// },
]
// {
// // 生命周期钩子函数
// beforeLoad: (app) => {
// console.log('beforeLoad', app)
// },
// beforeMount: (app) => {
// console.log('beforeMount ', app)
// },
// afterMount: (app) => {
// console.log('afterMount', app)
// },
// beforeUnmount: (app) => {
// console.log('beforeUnmount ', app)
// },
// afterUnmount: (app) => {
// console.log('afterUnmount', app)
// },
// }
)
//step3 设置默认进入微应用
//setDefaultMountApp('/vue3')
start() //启动微应用
- 在App.vue或其他位置挂载容器
<!-- 子应用渲染区域 -->
<div id="sub-app"></div>
子应用
安装插件 pnpm i vite-plugin-qiankun
- 修改vite.config.js内容
import { fileURLToPath, URL } from 'node:url'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
}),
qiankun('vue-app-1', {
// 微应用名字,与主应用注册的微应用名字保持一致
useDevMode: true
})
],
// 需要和基座中配置的activeRule一致
base: '/vue-app-1',
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
//建议设定固定的端口号,方便基座使用
server: {
open: true,
port: 9001,
cors: true
}
})
- 修改main.js内容
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 导入路由配置
import {
renderWithQiankun,
qiankunWindow
} from 'vite-plugin-qiankun/dist/helper'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(router) // 使用 Vue Router
app.use(createPinia())
// 清理容器
function clearContainer(container) {
while (container.firstChild) {
container.removeChild(container.firstChild)
}
}
const render = (props = {}) => {
const { container } = props
const appElement =
container?.querySelector('#app') || document.getElementById('app')
// 确保容器为空
if (appElement) {
clearContainer(appElement)
}
createApp(App).use(router).mount(appElement)
}
const initQianKun = () => {
renderWithQiankun({
bootstrap() {
console.log('微应用:bootstrap')
},
mount(props) {
// 获取主应用传入数据
console.log('微应用:mount', props)
render(props)
},
unmount(props) {
console.log('微应用:unmount', props)
},
update(props) {
console.log('微应用:update', props)
}
})
}
// 判断是否使用 qiankun ,保证项目可以独立运行
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()
主应用与子应用通信
- 使用initGlobalState进行通信
基座在src中新建个action.js文件
// 此action文件为定义微应用之间全局状态
// 引入qiankun的应用间通信方法initGlobalState
import { initGlobalState } from 'qiankun'
const initialState = {
//这里可以写初始化数据
// x:1
project_id: 'woq'
}
const actions = initGlobalState(initialState) //初始化state
export default actions
基座在main.js中引入action.js文件
import actions from '@/action.js'
registerMicroApps(
[
{
name: 'vue-app-1',
entry: '//localhost:9001',
container: '#sub-app',
activeRule: '/vue-app-1',
props: {
data: 'child子应用',
mainAppRouter: history,
router,
//这里添加
actions
},
pathPrefix: '/vue-app-1' // 使用 pathPrefix 自动添加前缀
}
]
)
子应用也在src中新建一个action.js文件
function emptyAction() {
// 警告:提示当前使用的是空 Action
console.warn('Current execute action is empty!')
}
// 我们首先设置一个用于通信的Actions类
class Actions {
actions = {
onGlobalStateChange: emptyAction,
setGlobalState: emptyAction
}
constructor() {}
// 默认值为空Action
// 设置actions
setActions(actions) {
this.actions = actions
}
// 映射
onGlobalStateChange(...args) {
return this.actions.onGlobalStateChange(...args)
}
// 映射
setGlobalState(...args) {
return this.actions.setGlobalState(...args)
}
}
const actions = new Actions()
export default actions
子应用在main.js文件中的mounted函数中挂载actions
actions.setActions(props)
子应用就可以在页面中使用基座传来的数据了
import actions from '../../action'
const projectId = ref('')
onMounted(() => {
actions.onGlobalStateChange((state) => {
projectId.value = state.project_id
console.log(state)
}, true) //onGlobalStateChange的第二个参数设置为true,则会立即触发一次观察者函数
console.log('-------------------------')
console.log(projectId.value)
})
基座如果要在页面中更改值也很简单,直接调用actions的setGlobalState方法即可
actions.setGlobalState({
userInfo: res.body
})
如果要在基座的某个页面中使用子应用
- 基座的router/index.js中这样写
//其中user-center为子应用的路由
{
path: '/user-center/:pathMatch(.*)*',
component: () => import('@/views/LayoutContainer/LayoutContainer.vue')
}
- 对应的LayoutContainer文件中写启动子应用
注意:就要对应删调main.js中的start()方法
import { start } from 'qiankun'
onMounted(() => {
//启动子应用
if (!window.qiankunStarted) {
window.qiankunStarted = true
start()
}
})
然后在该页面中需要的地方引入子应用
<!-- 子应用 -->
<div id="sub-app"></div>