相关博文:
https://juejin.cn/post/7216536069285429285?searchId=202403091501088BACFF113F980BA3B5F3
https://www.bilibili.com/video/BV12T411q7dq/?spm_id_from=333.337.search-card.all.click
qiankun结构:
主应用base:vue3+history+vite
子应用sub-react:react+history+webpack
子应用sub-vue:vue3+history+vite
子应用sub-vue2:vue2+base+webpack
❀ 主应用base
1、安装qiankun
bash
yarn add qiankun # 或者 npm i qiankun -S
2、存放子应用信息
/src/utils/qiankun-config.js
typescript
export default {
subApps: [
{
name: 'sub-react', // 子应用名称,跟package.json一致
entry: process.env.NODE_ENV === 'development'
? '//localhost:3101'
: '//react.qk.com', // 子应用入口,本地环境下指定端口
container: '#sub-app', // 挂载子应用的dom
activeRule: '/sub-react', // 路由匹配规则
props: {} // 主应用与子应用通信传值
},
{
name: 'sub-vue',
entry: process.env.NODE_ENV === 'development'
? '//localhost:3100'
: '//vue3.qk.com',
container: '#sub-app',
activeRule: '/sub-vue',
props: {}
},
{
name: 'sub-vue2',
entry: process.env.NODE_ENV === 'development'
? '//localhost:3103'
: '//vue2.qk.com',
container: '#sub-app',
activeRule: '/sub-vue2',
props: {}
}
]
}
3、开启qiankun
/src/utils/qiankun
typescript
import { registerMicroApps, initGlobalState } from 'qiankun'
import config from './qiankun-config'
const { subApps } = config
export function registerApps() {
try {
registerMicroApps(subApps, {
beforeLoad: [
app => {
console.log('before load', app)
return Promise.resolve()
}
],
beforeMount: [
app => {
console.log('before mount', app)
return Promise.resolve()
}
],
afterUnmount: [
app => {
console.log('before unmount', app)
return Promise.resolve()
}
]
})
const actions = initGlobalState(state);
// 主项目项目监听和修改
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log('主项目项目监听变化:',state, prev);
});
actions.setGlobalState(state);
} catch (err) {
console.log('qiankunError',err)
}
}
4、新建一个组件,用于加载子应用
/src/page/container.vue
typescript
<template>
<div id="sub-app"></div>
</template>
<script lang="ts">
import { start } from 'qiankun'
import { registerApps } from '@/utils/qiankun'
export default {
mounted() {
if (!window.__POWERED_BY_QIANKUN__) {
window.qiankunStarted = true
registerApps();
start({
sandbox: {
experimentalStyleIsolation: true // 样式隔离
}
})
}else{
window.__POWERED_BY_QIANKUN__.on('mount', loadMicroApps); 如果一个页面同时展示多个微应用,需要使用 loadMicroApp 来加载。
}
}
}
</script>
5、配置路由
typescript
export const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
redirect: '/home',
meta: {
show: false
}
},
{
path: '/home',
name: 'home',
meta: {
cnName: '首页',
show: true
},
component: () => import('@/page/home.vue')
},
{
path: '/about',
name: 'about',
meta: {
cnName: '关于',
show: true,
keepAlive: true
},
component: () => import('@/page/about.vue')
},
{
path: '/news/:id/:title/:content',
name: 'news',
meta: {
cnName: '新闻',
show: true
},
component: () => import('@/page/news.vue')
},
{
path: '/communication',
name: 'communication',
meta: {
cnName: '练习',
show: true
},
component: () => import('@/page/communication.vue')
},
{
// history模式需要通配所有路由,详见vue-router文档
path: '/sub-react/:pathMatch(.*)*',
name: 'sub-react',
meta: {
path:'/sub-react', // 注意:meta里的这个path是用来供router-link使用的,不用上面的path是因为点击主应用菜单跳转到子应用的时候默认带上了通配符
cnName: 'sub-react',
show: true,
key: 'sub-react',
},
component: () => import('@/page/container.vue'),
},
{
// history模式需要通配所有路由,详见vue-router文档
path: '/sub-vue/:pathMatch(.*)*',
name: 'sub-vue',
meta: {
path:'/sub-vue',
cnName: 'sub-vue',
show: true,
key: 'sub-vue',
},
component: () => import('@/page/container.vue'),
}
,
{
path: '/sub-vue2',
name: 'sub-vue2',
meta: {
path:'/sub-vue2/', // 如果不加上最后的"/",则域名会变成:http://localhost:8080/sub-vue2#/login
cnName: 'sub-vue2',
show: true,
key: 'sub-vue2',
},
component: () => import('@/page/container.vue'),
}
]
})
*注意 :meta里的这个path是用来供router-link使用的,不用上面的path是因为点击主应用菜单跳转到子应用的时候默认带上了通配符
6、首页 src/App.vue 放置 router-view
typescript
<template>
<header>
<router-link
v-for="item in routes"
:key="item.path"
:to="item.meta.path ? item.meta.path : item.path"
>
<span>{{ item.meta?.cnName }}</span>
</router-link>
<router-view v-slot="{Component}" v-show="!$route.meta.key">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>
</header>
</template>
❀ vite子应用配置(sub-vue)
一、安装vite-plugin-qiankun(vue项目才需要安装)
typescript
npm i vite-plugin-qiankun --save-dev
二、修改vite.config.js
sub-vue
修改vite.config.js
typescript
export default defineConfig({
// base: '/sub-vue',
plugins: [
vue(),
vueJsx(),
qiankun('sub-vue', { // 配置qiankun插件
useDevMode: true
}),
],
server: {
host: 'localhost',
port: 3100,
origin: 'http://localhost:3100/',
headers: {
'Access-Control-Allow-Origin': '*'
}
}
})
❀ webpack子应用配置(sub-react、sub-vue2)
一、修改webpack配置文件
sub-react
javascript
// 在根目录下新增config-overrides.js文件并新增如下配置
const { name } = require("./package");
module.exports = {
webpack: (config) => {
config.output.library = `sub-react`;
config.output.libraryTarget = "umd";
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
return config;
},
devServer: (_) => {
const config = _;
// config.publicPath = "http://loaclhost:3101";
config.headers = {
'Access-Control-Allow-Origin': '*',
};
return config;
},
};
sub-vue2
javascript
const { name } = require('./package.json')
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
output: {
library: `sub-vue2`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`, // webpack5没有jsonpFunction这个options
chunkLoadingGlobal: `webpackJsonp_${name}`
},
},
devServer:{
host: 'localhost',
port: 3103,
headers: {
'Access-Control-Allow-Origin': '*',
}
}
})
二、配置public-path(解决子应用图片默认取主应用的路径)
sub-react
、sub-vue2
src文件下添加public-path.js
javascript
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
并在入口文件的第一行
!!!引入它
sub-react
------src------index.js
sub-vue2
------src------main.js
❀ history路由入口规则(sub-vue、sub-react)
sub-vue
typescript
import { createRouter, createWebHistory } from 'vue-router'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const routes = [
{
path: '/',
redirect: { name: 'List' },
meta: { title: '首页' },
children: [
{
path: '/list',
name: 'List',
component: () => import('../views/List.vue')
},
{
path: '/detail',
name: 'Detail',
component: () => import('../views/Detail.vue')
}
]
},
]
const router = createRouter({
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/sub-vue/' : '/'),
routes
})
export default router
sub-react
好像不需要配置,直接在App.js添加router和link
javascript
<div className='menu'>
<Link to={'/'}>list</Link>
<Link to={'/detail'}>detail</Link>
<a onClick={goVue}>vue列表页</a>
</div>
<Routes>
<Route path='/' element={<List />} />
<Route path='/detail' element={<Detail />} />
</Routes>
❀ 配置生命周期(所有子组件)
sub-react
------src------index.js
javascript
import './public-path.js' // public-path处理子应用里的图片地址默认取主应用的路径
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom'
import reportWebVitals from './reportWebVitals';
let root;
// 将render方法用函数包裹,供后续主应用与独立运行调用
function render(props) {
const { container } = props
const dom = container ? container.querySelector('#root') : document.getElementById('root')
root = createRoot(dom)
if(!container){
root.render(
<BrowserRouter>
<App/>
</BrowserRouter>
)
return
}
root.render(
<BrowserRouter basename='/sub-react'> // 设置路由base
<App/>
</BrowserRouter>
)
}
// 判断是否在qiankun环境下,非qiankun环境下独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
// 各个生命周期
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
export async function bootstrap() {
console.log('react app bootstraped');
}
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props) {
render(props);
}
// 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
export async function unmount(props) {
root.unmount();
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
sub-vue
------src------main.js
javascript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import {
renderWithQiankun,
qiankunWindow
} from 'vite-plugin-qiankun/dist/helper'
let app:any
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()
sub-vue2
------src------main.js
javascript
import './public-path'
import Vue from 'vue'
import App from './App.vue'
import router from './router';
Vue.config.productionTip = false
let instance = null
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log(' vue2 app bootstraped')
}
export async function mount(props) {
console.log(' props from main framework', props);
render(props)
}
export async function unmount() {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
❀ 踩坑点:
子应用切换时出现以下警告:(我没遇到)
主应用增加:
javascript
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()
})