qiankun 微前端
简介
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略
微前端具备以下特点:
- 技术栈无关:主框架不限制接入应用的技术栈,子应用具备完全自主权
- 独立开发、独立部署:既可以组合在一起运行,也可以单独运行
- 增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
- 独立运行时:每个微应用之间状态隔离,运行时状态不共享
搭建主应用 (vue3)
主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start (启动) 即可
1.安装qiankun
yarn add qiankun 或者 npm i qiankun -S
2.注册微应用
目录 src 下新建 src/qiankun/index.js
import {registerMicroApps, start} from 'qiankun'
registerMicroApps([
{
name: 'sub-vue', // 子应用名称
entry: '//localhost:5174', // 子应用入口
container: '#sub-app', // 子应用挂载的DOM节点
activeRule: '/sub-vue', // 子应用激活路径
//向子应用传递参数
props:{
...
}
},
{
name: 'sub-react', // 子应用名称
entry: '//localhost:3011', // 子应用入口
container: '#sub-app', // 子应用挂载的DOM节点
activeRule: '/sub-react', // 子应用激活路径
}
])
export default start;
3.添加子应用的容器并启动应用
<template>
//id 要与配置当中的 container 一致
<div id="sub-app"></div>
</template>
<script setup>
import start from '@/qiankun/index';
import { onMounted } from 'vue'
onMounted(()=>{
//启动qiankun
if(!window.qiankunStarted){
window.qiankunStarted = true
start({
sandbox: {
// 样式隔离特性
experimentalStyleIsolation: true,
}
})
}
})
</script>
4.添加配置路由
{
// 注册时的activeRule 配置的路径相同
//:pathMatch(.*)* 匹配任意路径 不然子应用的路由跳转会出错 (vue项目)
path: '/sub-vue:pathMatch(.*)*',
name: 'sub-vue',
component: () => import('@/views/qiankun/index.vue')
},...
5.解决路由跳转失败
安装lodash
npm i lodash
添加路由守卫
router.beforeEach((to, from, next) => {
if (_.isEmpty(history.state.current)) {
_.assign(history.state, { current: from.fullPath });
}
next();
});
搭建vue子应用 (vite)
1.下载插件
目前 qiankun 还没有兼容 vite,还需借助第三方插件 vite-plugin-qiankun来实现
npm i vite-plugin-qiankun // 安装
2.vite.config.js 配置
import { fileURLToPath, URL } from 'node:url'
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插件
qiankun('sub-vue', { // 微应用名字,与主应用注册的微应用名字保持一致
useDevMode: true
})
],
server: {
port: 5174,
// 如果出现图片资源无法加载 请加上跨域
//项目启动路径
origin: "http://localhost:5174",
cors: true,
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
3.修改路由配置
分两种情况:
1、独立运行. 2、微服务中运行
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({
// 子应用的路由配置判断
history: createWebHistory(
qiankunWindow.__POWERED_BY_QIANKUN__
? '/sub-vue' //配置的子应用的路径activeRule
: import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},...
]
})
export default router
4.mian.js添加生命周期
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
import App from './App.vue'
import router from './router'
let app;
const render = (container = undefined)=>{
app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount(container ? container.querySelector("#app") : '#app')
}
const initQianKun = ()=>{
renderWithQiankun({
//props为主应用传过来的data
// 渲染子应用
mount(props) {
const {container} = props;
render(container)
},
// 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
bootstrap() {
console.log('bootstrap')
},
// 卸载子应用
unmount() {
console.log('unmount')
},
})
}
//判断是否为qiankun环境
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()
搭建react子应用 (webpack)
1.在src目录下面新增public-path.js
如果不添加 可能会出现静态资源无法加载的问题,子应用在微服务中打开 它默认使用了父应用的前缀,导致子应用无法加载自身的静态资源
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2. index.js 导出生命周期函数,修改路由基础路径
import './public-path' //导入public-path.js
let root;
function createRoot(props) {
// container中包含了qiankun创建的dom,它会插入一个带有id为root的dom
const { container } = props;
root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.querySelector('#root'));
}
if (!window.__POWERED_BY_QIANKUN__) {
createRoot({});
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
}
// 初始化
export async function bootstrap() {
}
// 挂载
export async function mount(props) {
createRoot(props);
//qiankun环境中渲染
root.render(
//判断路由是否是qiankun环境并设置基础路径
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/sub-react' : '/'}>
<App />
</BrowserRouter>
)
}
//卸载
export async function unmount() {
}
3.修改App.js
//使用这种路由模式
<Routes
<Route path='/about' element={<div>about</div>}></Route>
<Route path='/' element={<Home/>}></Route>
</Routes>
4.修改webpack配置
安装插件@craco/craco
npm i @craco/craco --save
根目录新增 craco.config.js:
//package.json 的name必须和微应用的名称一致
const { name } = require("./package");
module.exports = {
webpack: {
configure: (webpackConfig) => {
webpackConfig.output.library = `${name}-[name]`;
webpackConfig.output.libraryTarget = "umd";
webpackConfig.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
return webpackConfig;
},
},
devServer: (devServerConfig) => {
devServerConfig.historyApiFallback = true;
devServerConfig.open = false;
devServerConfig.hot = false;
devServerConfig.watchFiles = [];
devServerConfig.headers = {
"Access-Control-Allow-Origin": "*",
};
return devServerConfig;
},
};
修改package.json
"scripts": {
"start": "set PORT=3011 && craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
数据共享
1.主应用传递数据
放在 registerMicroApps 后面
const state = {
counter: 0
}
//初始化
const actions = initGlobalState(state)
//监听数据变化
actions.onGlobalStateChange((value, prev) => {
console.log('主应用观察到全局状态变化!', value, prev)
})
//修改数据
actions.setGlobalState({counter: 1})
2.子应用
mount(props) {
//监听
props.onGlobalStateChange((state, prev) => {
console.log('主应用接收到的数据', state)
})
//修改
props.setGlobalState({
counter: 1
})
}
子应用之间跳转
1.主应用、子应用路由都是hash模式
主应用根据 hash 来判断微应用,无需考虑该问题
2.主应用、子应用路由都是history模式
-
history.pushState(state,title,url)方法
-
通过props传递主应用的路由实例 子应用挂载到全局 然后子应用 使用 主应用的router 跳转
{ name: 'sub-vue', // 子应用名称 entry: '//localhost:5174', // 子应用入口 container: '#sub-app', // 子应用挂载的DOM节点 activeRule: '/sub-vue', // 子应用激活路径 props: { //主应用的路由实例 router } },