qiankun:vue3 + vite从开发到部署实现微前端
背景
由于业务需要,本人需要在某应用中增加菜单,在页面上直接展示另外两个应用,并且要求登录信息共用,一番衡量过后决定使用qiankun实现。(注:本文主要记录一次从开发到部署相关接入qiankun的过程,并不探究相关原理)
文末有源码,记得先点赞再看噢!
开始
首先准备好三个应用:
主应用main:vue3+vite+history路由模式
子应用app-vue3:vue3+vite+history路由模式
子应用app-vue2:vue2+webpack+hash路由模式
主应用
安装qiankun
npm i qiankun -S
根目录下新建src/config.js
用于存放子应用信息
export default {
subApps: [
{
name: 'app-vue3', // 子应用名称,跟package.json一致
entry: '//localhost:7001', // 子应用入口,本地环境下指定端口
container: '#sub-container', // 挂载子应用的dom
activeRule: '/app/app-vue3', // 路由匹配规则
props: {} // 主应用与子应用通信传值
},
{
name: 'app-vue2',
entry: '//localhost:7002',
container: '#sub-container',
activeRule: '/app/app-vue2',
props: {}
}
]
}
根目录下新建src/utils/qiankun
用于开启qiankun
import { registerMicroApps } from 'qiankun'
import config from '@/config'
const { subApps } = config
export function registerApps() {
try {
registerMicroApps(subApps, {
beforeLoad: [
app => {
console.log('before load', app)
}
],
beforeMount: [
app => {
console.log('before mount', app)
}
],
afterUnmount: [
app => {
console.log('before unmount', app)
}
]
})
} catch (err) {
console.log(err)
}
}
新建一个组件,用于加载子应用src/components/SubContainer.vue
<template>
<div id="sub-container"></div>
</template>
<script>
import { start } from 'qiankun'
import { registerApps } from '@/utils/qiankun'
export default {
mounted() {
if (!window.qiankunStarted) {
window.qiankunStarted = true
registerApps()
start({
sandbox: {
experimentalStyleIsolation: true // 样式隔离
}
})
}
}
}
</script>
配置一下路由src/router/routes
const routes = [
{
path: '',
redirect: { name: 'home' },
meta: { title: '首页' },
children: [
{
path: '/home',
name: 'home',
component: () => import('../views/home/index.vue')
},
{
// history模式需要通配所有路由,详见vue-router文档
path: '/app/app-vue3/:pathMatch(.*)*',
name: 'app-vue3',
meta: {},
component: () => import('@/components/SubContainer.vue')
},
{
path: '/app/app-vue2/',
name: 'app-vue2',
meta: {},
component: () => import('@/components/SubContainer.vue')
}
]
}
]
export default routes
最后在首页src/App.vue
放置<router-view />
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<router-link to="/app/app-vue2/#/">vue-app2</router-link>
<router-link to="/app/app-vue3">vue-app3</router-link>
<router-view />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
到此主应用配置修改完成,后续再补充router-link的路由跳转信息
子应用vue-app3
安装vite-plugin-qiankun
npm i vite-plugin-qiankun --save-dev
修改vite.config.js
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(),
qiankun('app-vue3', {
useDevMode: true
})
],
server: {
port: 7001,
headers: {
'Access-Control-Allow-Origin': '*'
}
}
})
由于路由模式为history,需要匹配子应用的入口规则,修改src/router/index
import {
createRouter,
createWebHistory
} from 'vue-router'
import routes from './routes'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({
history: createWebHistory(
qiankunWindow.__POWERED_BY_QIANKUN__
? '/app/app-vue3/'
: '/'
),
routes
})
export default router
最后在main.ts
里添加生命周期
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import {
renderWithQiankun,
qiankunWindow
} from 'vite-plugin-qiankun/dist/helper'
let app
const render = (container) => {
app = createApp(App)
app
.use(router)
.mount(container ? container.querySelector('#app') : '#app')
}
const initQianKun = () => {
renderWithQiankun({
mount(props) {
const { container } = props
render(container)
},
bootstrap() {},
unmount() {
app.unmount()
}
})
}
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()
单独运行,指定7001端口
微前端环境运行
子应用vue-app2
创建src/public-path
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
main.js
修改
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './public-path'
Vue.config.productionTip = false
let instance = null
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped')
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props)
}
export async function unmount() {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
vue.config.js
修改
const { name } = require('./package')
module.exports = {
devServer: {
port: 7002,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
}
单独运行,指定7002端口
微前端环境运行
至此,本地运行的所有配置全部修改完成
踩坑点:
子应用切换时出现以下警告:
解决方案:
主应用增加:
router.beforeEach((to, from, next) => {
if (!window.history.state.current) window.history.state.current = to.fullPath
if (!window.history.state.back) window.history.state.back = from.fullPath
// 手动修改history的state
return next()
})
部署相关
注意项:
1.修改子应用配置的entry
修改为部署到服务器上的index.html的入口
若把子应用部署到服务器根目录下的app-vue3文件夹,则修改为:
{
name: 'app-vue3', // 子应用名称,跟package.json一致
entry: process.env.NODE_ENV === 'development'
? '//localhost:7001'
: '/app-vue3/index.html', // 子应用入口,本地环境下指定端口
container: '#sub-container', // 挂载子应用的dom
activeRule: '/app/app-vue3', // 路由匹配规则
props: {} // 主应用与子应用通信传值
}
2.修改子应用配置的activeRule(取决于主应用在服务器上部署的路径)
由于主应用是history路由模式,activeRule需要完全匹配(hash模式只需匹配hash路由中#号开始部分)
如:
{
name: 'app-vue3', // 子应用名称,跟package.json一致
entry: process.env.NODE_ENV === 'development'
? '//localhost:7001'
: '/app-vue3/index.html', // 子应用入口,本地环境下指定端口
container: '#sub-container', // 挂载子应用的dom
activeRule: process.env.NODE_ENV === 'development'
? '/app/app-vue3'
: '/main/app/app-vue3', // 路由匹配规则
props: {} // 主应用与子应用通信传值
}
3.修改子应用vite/webpack/vue-cli静态资源路径的配置(详细可在文末源码查看)
vite应用:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'
// https://vitejs.dev/config/
export default defineConfig({
base: process.env.NODE_ENV === 'production' ? '/app-vue3/' : '/',
plugins: [
vue(),
qiankun('app-vue3', {
useDevMode: true
})
],
server: {
port: 7001,
headers: {
'Access-Control-Allow-Origin': '*'
}
}
})
总结
至此,我们简单的完成了一个微前端的搭建,当然这只是一个简单的demo,要达到部署生产真正投入使用,还有许多工作要做(主、子应用通信,子应用静态资源代理,子应用共享主应用登录凭证等问题)
后续我们将继续探索以上问题...
点个赞吧,求求了~~!
转载自:https://juejin.cn/post/7216536069285429285