qiankun微前端架构
本教程是视频教程对应的笔记,配合视频教程更容易理解
架构概念
比后端的微服务架构晚一些。
以前开发项目后端都是一个项目。团队开发管理自己分支。合并在一起打包运行。
随着后端业务越来越复杂,一个项目业务容易出现相互影响。
一个大的项目拆分微很多单体项目(服务),在通过微服务器架构实现,每个应用之间的通信。
养车系统后端采用微服务架构。
核心概念:
- 解决一个项目里面需要涵盖多种技术的问题。可以采用微前端的方式设计
- 项目业务模块非常多,后续可能拆分应用。或者模块单独的销售等等。
搭建流程
项目创建三个
- Vue3的技术栈 Vue+ts来作为主应用。
- Vue2技术栈+js来实现的子应用一
- React+js技术栈来实现的子应用二
会将Vue3作为主应用。
React和Vue2作为子应用。
主应用开发(Vue3)
主应用中你自己搭建好 路由环境、状态机环境、Elementplus的环境。
(1)依赖下载
在应用中下载qiankun插件
yarn add qiankun
(2)配置变量
在src/quankun/index.ts
/**
* apps里面存放的就是子应用
*/
const apps: any = [
]
import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from "qiankun"
/**
* 注册微应用
*/
registerMicroApps(apps, {
// qiankun 生命周期钩子 - 微应用加载前
beforeLoad: (app) => {
console.log("before load", app.name);
return Promise.resolve();
},
// qiankun 生命周期钩子 - 微应用挂载后
afterMount: (app) => {
console.log("after mount", app.name);
return Promise.resolve();
},
});
/**
* 处理全局异常:子应用可能出现加载失败
*/
addGlobalUncaughtErrorHandler((event) => {
console.error(event);
const { message: msg } = event;
// 加载失败时提示
if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
alert("子应用加载失败")
}
});
export default start
(3)加载qiankun
src/main.ts代码中配置qiankun
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router"
import startQuankun from "./qiankun"
const app = createApp(App)
app.use(router)
app.mount('#app')
startQuankun()
startQuankun就是启动的函数。
(4)配置加载容器
<template>
<div class="wrapper">
<ul>
<li>主应用</li>
<li>React微应用</li>
<li>Vue微应用</li>
</ul>
<div>
<!-- router-view渲染当前项目路由 -->
<router-view v-show="$route.name"></router-view>
<!-- 加载子应用 -->
<div v-show="!$route.name" id="container"></div>
</div>
</div>
</template>
<script lang='ts' setup>
import {useRouter,useRoute} from "vue-router"
import {onMounted} from "vue"
const $router = useRouter()
const $route = useRoute()
</script>
<style scoped>
.wrapper{
width:100%;
height:100vh;
display:flex;
}
.wrapper ul{
width:200px;
height:100vh;
background-color:pink;
}
</style>
核心概念:主应用有自己的路由,子应用也需要渲染。在父应用中考虑到底渲染自己页面,还是加载子应用页面
React子应用
微前端项目中,每个应用是可以独立运行的。独立打包
也可以放在一起运行。配置了qiankun的信息,不影响我们项目自己启动运行访问
项目创建完毕后,自己先搭建好路由。或者状态机
(1)修改端口
有可能你们有多个React子应用。每个React项目启动端口默认都是3000
需要在React项目根目录下面创建.env
PORT=8000
(2)配置好React自己路由
在App.jsx这个文件中配置
import React from 'react'
import { Link, BrowserRouter, Routes, Route } from "react-router-dom"
import Shop from './pages/Shop'
import Category from './pages/Category'
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route index element={<Shop></Shop>}></Route>
<Route path="/category" element={<Category></Category>}></Route>
</Routes>
</BrowserRouter>
)
}
(3)注册微应用
你想要qiankun能加载我们当前这个项目,并作为子应用。
那就必须在主应用中,将这个项目的信息配置注册到主应用
Vue3项目/src/qiankun/index.ts
/**
* apps里面存放的就是子应用
* name:子应用名字。这个名字在子应用会订好,不是随便写的名字
* entry:两个应用通信地址,这个地址可能远程服务器
* container:子应用加载完毕后,在哪个容器中渲染。
* activeRule:触发子应用加载的路由
*/
const apps: any = [
{
name:"ReactApp",
entry:"//localhost:8000",
container:"#container",
activeRule:"/react"
}
]
(4)配置React子应用
src/index.js文件
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
if (window.__POWERED_BY_QIANKUN__) {
// 动态设置 webpack publicPath,防止资源加载出错
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
/**
* 渲染函数
* 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
*/
function render() {
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
}
// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
/**
* 生命周期函数:由qiankun产生的
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log("ReactApp bootstraped");
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log("ReactApp mount", props);
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log("ReactApp unmount");
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
(5)配置路由基础路径
import React from 'react'
import { Link, BrowserRouter, Routes, Route } from "react-router-dom"
import Shop from './pages/Shop'
import Category from './pages/Category'
const BASE_NAME = window.__POWERED_BY_QIANKUN__?"/react":"/"
export default function App() {
return (
<BrowserRouter basename={BASE_NAME}>
<Routes>
<Route index element={<Shop></Shop>}></Route>
<Route path="/category" element={<Category></Category>}></Route>
</Routes>
</BrowserRouter>
)
}
子应用独立访问的时候,默认/就是基础路径,如果qiankun来访问,默认带着/react来访问我们路由。
我们需要判断设置基础路径。
(6)webpack的配置
父应用要和子应用通信。
- 跨域问题。
- 子应用打包出来代码,是否能够被qiankun加载
需要在子应用配置webpack
react/webpack配置
npm install react-app-rewired -D
在React/package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
在React项目中根目录下面创建一个config-overrides.js
module.exports = {
webpack: (config) => {
config.output.library = `ReactApp`;
config.output.libraryTarget = 'umd';
// webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
config.output.chunkLoadingGlobal = `webpackJsonp_ReactApp`;
config.output.globalObject = 'window';
return config;
},
devServer: (_) => {
const config = _;
config.headers = {
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback = true;
config.hot = false;
config.watchContentBase = false;
config.liveReload = false;
return config;
},
};
打包后项目名字ReactApp
打包后结果是umd格式
配置这台80000服务器,允许跨域访问。
(7)切换保存
错误信息如下
根据错误信息排查出来,问题出在ReactApp这个子应用中。
排查问题:
ReactApp这个应用被卸载完成后ReactDOM已经被资源回收了。无法在执行下面代码
export async function unmount() {
console.log("ReactApp unmount");
ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}
解决方案:
let root = null
export async function unmount() {
console.log("ReactApp unmount");
// ReactDOM.unmountComponentAtNode(document.getElementById("root"));
if (root) {
//销毁资源
root.unmount();
root = null;
}
}
(8)指定在某个路由中映射React应用
(1)在主应用中配置路由
const routes = [
{
path: "/login",
name: "Login",
component: LoginVue
},
{
path: "/home",
name: 'Home',
component: HomeVue,
children: [
{
path: "menu",
name: "Menu",
component: Menu
},
{
path: 'react',
redirect: "/home/react/"
},
{
path: 'react/:pathMatch(.*)',
name: 'React',
component: () => import('../views/subs/React.vue'),
},
]
}
]
我想要React子应用在Home组件中进行渲染。路由就应该配置/home中
并且当我们访问/home/react这个路由的时候。默认代表进入React.vue这个组件
(2)在React.vue组件中加载子应用
<template>
<div id="container"></div>
</template>
<script lang='ts' setup>
</script>
<style lang='scss' scoped>
</style>
(3)配置子应用启动路径
const apps: any = [
{
name:"ReactApp",
entry:"//localhost:8000",
container:"#container",
activeRule:"/home/react"
}
]
activeRule这个属性要变成/home/react才能触发子应用加载
(4)在React子应用配置
const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/home/react" : "/"
保持主应用访问的路由和子应用能够映射在一起。
子应用路由才能访问成功,否则无法匹配。