微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端架构具备以下几个核心价值:
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
本次使用主应用与子应用都试用VUE3.0版本搭建,其他版本有时间再补充
主应用
$ yarn add qiankun # 或者 npm i qiankun -S```
// src/public/system/index
import { registerMicroApps, start,addGlobalUncaughtErrorHandler } from 'qiankun';
registerMicroApps([
{
name: 'app-system', // app name registered
entry: 'http://localhost:8080', // 微应用的端口号
container: '#container', // 容器名
activeRule: '/system', // 路径
},
]);
addGlobalUncaughtErrorHandler((event) => {
// console.log(event);
const { message } = event;
if (message && message.includes('died in status LOADING_SOURCE_CODE')) {
console.log('微应用加载失败,请检查应用是否可运行');
}
});
start();
3.导入到main.js
import '@/public/system/index'
// createApp(App).use(router).mount('#app')
import { createApp } from 'vue'
import router from '@/router/index.js';
import Antd from 'ant-design-vue';
import App from './App.vue'
import 'ant-design-vue/dist/antd.css';
import "default-passive-events"
// import { Button, message } from 'ant-design-vue';
createApp(App).use(router).use(Antd).mount('#app')
子应用
$ yarn add qiankun # 或者 npm i qiankun -S```
2.在src目录下配置 public-path.js文件
// /src/micro/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
3.配置main.js
import '@/micro/public-path';
import { createApp } from 'vue'
import App from './App.vue'
import routes from './router'
import { createRouter, createWebHistory } from 'vue-router';
let router = null;
let instance = null;
let history = null;
function render(props = {}) {
const { container } = props;
// let arr = routes.options.routes;
console.log(window.__POWERED_BY_QIANKUN__);
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/system/' : '/'); // 这里配置的路径要与主应用注册时保持一致
router = createRouter({
history,
routes,
});
instance = createApp(App);
instance.use(router);
// instance.use(store);
// instance.use(ElementPlus);
instance.mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('%c%s', 'color: green;', '微应用初始化vue3.0 app bootstraped');
}
function storeTest(props) {
props.onGlobalStateChange &&
props.onGlobalStateChange(
(value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true,
);
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name,
},
});
}
export async function mount(props) {
storeTest(props);
render(props);
instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange;
instance.config.globalProperties.$setGlobalState = props.setGlobalState;
}
export async function unmount() {
instance.unmount();
instance._container.innerHTML = '';
instance = null;
router = null;
history.destroy();
}
4.配置vue.config.js文件
const { name } = require('./package');
module.exports = {
devServer: {
port: 8080, // 启动项目时的端口号
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
chunkLoadingGlobal: `webpackJsonp_${name}`,
},
},
};
路由配置
主应用
src/router/index.js
import { createRouter, createWebHashHistory,createWebHistory } from 'vue-router'
import { routes } from './router.js';
const router = createRouter({
// history: createWebHashHistory(),
history: createWebHistory(process.env.BASE_URL),
base:process.env.BASE_URL,
routes
})
export default router;
src/router/routes.js
import Layout from '@/views/Layout.vue';
import Login from '@/views/login/index.vue';
export const routes = [{
path: '/',
component: Layout,
redirect:{path:'/home'},
children: [
{
path: '/home',
name: 'home',
meta: {
title: '首页',
requireAuth: true,
menu: 'home'
},
component: () => import('@/views/home/index.vue')
},
{
path: '/system/:pathMatch(.*)', // 配置微应用入口
name: 'system',
component: () => import('@/views/system/index.vue') // 创建一个文件作为微应用容器
},
{
path: '/config',
name: 'config',
meta: {
title: '配置',
requireAuth: true,
menu: 'config'
},
component: () => import('@/views/config/index.vue')
}
]
},
{
path: '/login',
name: 'login',
component: Login,
// meta: {
// title: '登录',
// requireAuth: false
// }
},
{
path: '/:pathMatch(.*)',
name: '404page',
component: () => import('@/views/errorPage/404.vue'),
meta: {
title: '404',
requireAuth: false
}
}
];
微应用容器文件配置 src/views/system/index.vue
<template>
<div class="system">
<div id="container"></div> //
</div>
</template>
<script>
import { start } from 'qiankun';
import { onMounted } from 'vue';
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
onMounted(()=>{
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start({
prefetch: true, // 开启预加载
sandbox: {
experimentalStyleIsolation: true,
}
});
}
})
return {
};
},
});
</script>
<style>
</style>
配置跳转路由,在点击菜单使用编程式导航跳转到微应用路由上
import { defineComponent, ref } from 'vue';
import { MailOutlined } from '@ant-design/icons-vue';
import {useRouter } from 'vue-router'
export default defineComponent({
name: 'siderMenu',
setup(){
const router = useRouter()
const onMenuClick = ({ item, key, keyPath })=>{
console.log( item, key, keyPath )
router.push({
path:'/system/rule'
})
}
return {
}
}
})
微应用路由配置
const Rule = () => import('@/views/rule/index')
const routes = [
{
path: '/rule',
component: Rule
}
]
export default routes
附demo地址
主应用:
https://github.com/qinmingliang/app-qiankun-main.git```
微应用
https://github.com/qinmingliang/app-qiankun-system.git
本文章仅用于学习交流,不正之处多多指教~~