文章目录

简介
主要介绍以qiankun框架为基础,vite 搭建vue3 项目为主应用,wepack vue2 和 webpack react 搭建的子应用,形成的一个微前端框架。
主应用 qiankun-main vue3 vite
先用vite 创建一个vue3的标准框架 vue-router typescript 这些都可以选上
yarn create vue
主应用还要装上qiankun依赖
yarn add qiankun
创建一个qiankun-config.ts的文件
import { registerMicroApps, start } from 'qiankun'
const beforeLoad: (...args: any[]) => any = (app: any) => {
console.log('before load', app)
}
const beforeUnmount: (...args: any[]) => any = (app: any) => {
console.log('after unmount', app)
}
const beforeMount: (...args: any[]) => any = (app: any) => {
console.log('before mount', app)
}
export function registerApps() {
try {
registerMicroApps(
[
{
name: 'app-vue3', // 子应用名称,跟package.json一致
entry: '//localhost:7001', // 子应用入口,本地环境下指定端口
container: '#sub-container', // 挂载子应用的dom
activeRule: '/app/app-vue3', // 路由匹配规则
props: {}, // 主应用与子应用通信传值
},
{
name: 'app-react',
entry: '//localhost:3000',
container: '#sub-container',
activeRule: '/app/app-react',
props: {},
},
{
name: 'app-vue2',
entry: '//localhost:7003',
container: '#sub-container',
activeRule: '/app/app-vue2',
props: {},
}
],
// 生命周期函数
{
beforeLoad: [beforeLoad],
beforeMount: [beforeMount],
afterUnmount: [beforeUnmount],
},
)
start({
sandbox: {
experimentalStyleIsolation: true, // 沙箱样式隔离
},
})
} catch (err) {
console.log(err)
}
}
将上面的文件引入main.ts中
import './assets/main.css'
import { registerApps } from './qiankun-config'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#qiankun') // 记得把html的挂载id dom改一改
registerApps()
创建一个专门用来存放子应用的组件 SubContainer.vue
<script setup lang="ts">
</script>
<template>
<h1>Container</h1>
<div id="sub-container"></div>
</template>
改造主应用的路由router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// import Layout from '../layout/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
// history模式需要通配所有路由,详见vue-router文档
path: '/app/app-vue3/:pathMatch(.*)*',
name: 'app-vue3',
meta: {},
component: () => import('@/views/SubContainer.vue'),
},
{
path: '/app/app-react/:pathMatch(.*)*',
name: 'app-react',
meta: {},
component: () => import('@/views/SubContainer.vue'),
},
{
path: '/app/app-vue2/:pathMatch(.*)*',
name: 'app-vue2',
meta: {},
component: () => import('@/views/SubContainer.vue'),
},
],
})
export default router
主应用到这里开发环境基本就改造完成了 yarn dev启动开发环境服务器
子应用 qiankun-app-vue2 webpack5
安装vue2 webpack的项目
全局先安装vue-cli
npm install -g @vue/cli
# OR
yarn global add @vue/cli
配置电脑的环境变量 让vscode能够正确使用vue命令符
https://blog.csdn.net/yi_zongjishi/article/details/124831223
上述引用的文章里说以管理员权限启动vscode来处理vscode不能使用vue 的情况有点问题,实际上是以管理员权限启动powershell来执行那些命令后就可以了。
安装vue2框架
vue create qiankun-vue2
安装vue-router
vue2只能安装vue-router@3
yarn add vue-router@3
改webpack的output配置 和devServer配置
由于vue.confg.js存在,所以改造它就等于改造webpack
// const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package')
module.exports = {
devServer: {
port: 7003, // 与主应用配置子应用的端口号一致就行
headers: {
'Access-Control-Allow-Origin': '*', // 处理开发环境因为端口号不同而产生的跨域问题
},
},
configureWebpack: {
// output为quankun官方要求的必须的
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
chunkLoadingGlobal: `webpackJsonp_${name}`,
},
},
transpileDependencies: true,
}
根目录下创建一个名为 public-path.js的文件
// 在window上为qiankun挂载关键参数
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
在main.js第一行引入,并对main.js改造
import './public-path'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// import router from './router'
// import store from './store'
Vue.config.productionTip = false // 关闭生产提示
let instance = null
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
// store,
render: h => h(App)
// container 表示 是qiankun主应用访问,限制一下搜索子应用挂载dom的范围,最好能改一下这个id的名称
}).$mount(container ? container.querySelector('#vue-app2') : '#vue-app2')
}
// 如果不是qiankun主应用下访问,而是子应用独立部署,正常执行render
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 根据qiankun官方要求,子应用需要导出三个生命周期钩子函数bootstrap mount unmount
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() {
// unmount 的时候摧毁掉整个子应用
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
记得改掉html文件挂载dom的id为vue-app2
对router.js进行改造
import VueRouter from "vue-router";
import Vue from "vue";
Vue.use(VueRouter);
import Home from "./views/Home.vue";
import About from "./views/About.vue";
const routes = [
{
path: "/",
redirect: "/home",
},
{
path: "/home",
component: Home,
},
{
path: "/about",
component: About,
},
];
const router = new VueRouter({
mode: "history",
base: window.__POWERED_BY_QIANKUN__ ? 'app/app-vue2' : '', // 通过qiankun主应用访问子应用时,base需要设置成为主应用专门挂载子用用所在的路径
routes,
});
export default router;
webpack 5 下的vue2框架项目在开发环境下的改造就差不多完成了。
子应用 qiankun-react webpack5
先从零安装一个react
https://blog.csdn.net/glorydx/article/details/115104561
安装 react-router-dom
改造App.js
import "./App.css";
import About from "./views/About.tsx";
import Home from "./views/Home.tsx";
import { NavLink, useRoutes, Outlet} from "react-router-dom";
function App() {
const base = window.__POWERED_BY_QIANKUN__ ? "/app/app-react/" : "/";
const routes = useRoutes([
{ path: base, redirect: base + "home", element: <Home /> },
{ path: base + "home", element: <Home /> },
{ path: base + "about", element: <About /> },
]);
return (
<div className="app">
<h1>React Router Dom</h1>
<div className="main">
<div className="leftToolBar">
<NavLink to={base + "home"}>Home</NavLink>
{/* 或者如下写法传入标签体 */}
<NavLink to={base + "about"}>About</NavLink>
</div>
<div className="rightContent">{routes}</div>
</div>
<Outlet />
</div>
);
}
export default App;
根目录下的 public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
改造index.js 最好替换一下挂载的dom id ,同样的需要引入public-path.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "./public-path.js";
import { BrowserRouter } from "react-router-dom";
function render(props) {
const container = props?.container;
const root = ReactDOM.createRoot(
container
? container.querySelector("#app-react")
: document.querySelector("#app-react")
);
root.render(
<BrowserRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</BrowserRouter>
);
}
// 然后加入生命周期即可 同样的三个生命周期导出
export async function bootstrap() {
console.log("ReactMicroApp bootstraped");
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log("ReactMicroApp mount", props);
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log("ReactMicroApp unmount");
ReactDOM.unmountComponentAtNode(document.getElementById("#app-react"));
}
// 如果不是qiankun,正常挂载
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 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();
改造webpack.config.js 前提是react不会自动暴露这东西,在这之前,你必须执行过 npm run eject
或者yarn eject
去暴露webpack的配置文件
主要是给 output加上qiankun需要的配置
const packageName = require('../package.json').name;
...
output: {
....
library: `${packageName}-[name]`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${packageName}`,
},
记得把html挂载的id给换了
<div id="app-react"></div>
如果要引入图片或者其他的什么静态资源
比如react项目本身的logo.svg无法正常显示了
需要在react子项目先把打包方式的hash给干掉
然后把这个svg不改名的情况下,按照路径给copy到主应用中
这样qiankun主应用vue3的项目就能正常显示这张图片 如果是其它格式的静态资源也是差不多类似上面的操作
子应用 quankun-vue3 vite
还是同样的方式,先安装一个标准的vite vue3项目 需要有vue-router ts 就行
与webpack 5 的配置方式不同,需要安装依赖 vite-plugin-qiankun
yarn add vite-plugin-qiankun
还是先改造main.ts 记得引入public-path.js 文件内容跟之前一样
import './public-path'
import './assets/main.css'
// alert(1111)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let app: any
function render(props = {}) {
// 每一次render都需要创建一个新的
app = createApp(App)
app.use(createPinia()).use(router)
const { container } = props as any
app.mount(container ? container.querySelector('#app-vue3') : '#app-vue3')
}
const bootstrap: (...args: any[]) => any = () => {
console.log('vue app bootstraped')
}
const mount: (...args: any[]) => any = (props) => {
const { container } = props
render(container)
console.log('vue3 app mount')
}
const unmount: (...args: any[]) => any = () => {
app.unmount()
}
const initQianKun = () => {
// @ts-ignore
renderWithQiankun({
mount,
bootstrap,
unmount,
})
// render()
}
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()
改造一下router
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/app/app-vue3/' : '/'),
routes: [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'home',
component: HomeView,
children: [],
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'),
},
],
})
export default router
配置一下vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// import vueDevTools from 'vite-plugin-vue-devtools'
import qiankun from 'vite-plugin-qiankun'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
// vueDevTools(),
qiankun('app-vue3', {
useDevMode: true,
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 7001,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
})
基本上就改造完成了 然后启动就可以正常访问
遇到的问题
当子应用切换路由后,主应用再切换路由,会报错
当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285
解决的办法,如链接所示,在主应用加一个router的hook
router.beforeEach((to, from, next) => {
// 当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
// 详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285
if (window.history.state === null) {
history.replaceState({
back: from.path,
current: to.path,
forward: null,
position: NaN,
replaced: false,
scroll: null
}, 'localhost:5173' + to.path);
}
next();
});
在子应用当路由变化时 执行 window.history.pushState(null, "", "");
比如react
import { useEffect } from "react";
import "./App.css";
import About from "./views/About.tsx";
import Home from "./views/Home.tsx";
import { NavLink, useRoutes } from "react-router-dom";
function App() {
const base = window.__POWERED_BY_QIANKUN__ ? "/app/app-react/" : "/";
const routes = useRoutes([
{ path: base, redirect: base + "home", element: <Home /> },
{ path: base + "home", element: <Home /> },
{ path: base + "about", element: <About /> },
]);
// 当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
// 详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285
useEffect(() => {
if(window.__POWERED_BY_QIANKUN__) {
window.history.pushState(null, "", "");
}
}, [routes]);
return (
<div className="app">
<h1>React Router Dom</h1>
<div className="main">
<div className="leftToolBar">
<NavLink to={base + "home"}>Home</NavLink>
{/* 或者如下写法传入标签体 */}
<NavLink to={base + "about"}>About</NavLink>
</div>
<div className="rightContent">{routes}</div>
</div>
</div>
);
}
export default App;
比如vue2
<template>
<div id="app">
<h1>
qiankun vue2
</h1>
<RouterLink to="/home">home</RouterLink>
<RouterLink to="/about">about</RouterLink>
<RouterView />
</div>
</template>
<script>
import {RouterView, RouterLink} from 'vue-router'
export default {
name: 'App',
components: {
RouterView,
RouterLink
},
watch : {
$route () {
// 当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
// 详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285
if(window.__POWERED_BY_QIANKUN__) {
window.history.pushState(null,'','')
}
}
}
}
</script>
<style>
</style>