微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。微前端的概念借鉴自后端的微服务,主要是为了解决大型工程在变更、维护、扩展等方面的困难问题。
简介
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。微前端的概念借鉴自后端的微服务,主要是为了解决大型工程在变更、维护、扩展等方面的困难问题。
微前端优势
- 技术栈技术栈无关,即子应用可自主选择技术框架如
Vue2、Vue3、React、Svelte、JQuery等 - 各主子应用独立开发、独立部署,代码库更小、内聚性更强
- 可维护性和扩展性更好,便于局部升级和增量升级,如对于巨型应用进行技术重构升级可通过微前端实现渐进式重构
- 独立运行时,每个微应用之间状态隔离,运行时状态不共享
实现方式
- iframe:硬隔离,上下文不共享,开发体检较差
- 基座模式:
qiankun和single-spa模式,主应用提供基座,子应用通过路由转发。 - 组合式集成:
npm包形式。 - Web Components
qiankun 使用
主应用搭建
安装 qiankun
$ npm i qiankun --save
配置
新建 src/microApps/index.js 文件,并写入如下内容:
import { addGlobalUncaughtErrorHandler, runAfterFirstMounted, initGlobalState, start } from 'qiankun';
import Layout from '@/layout';
import { getToken } from '../utils/auth';
// 配置子应用加载监听器
runAfterFirstMounted(() => {
console.log('dfcg-micro-front-main ### 子应用加载完毕');
});
// 配置全局异常捕获器
addGlobalUncaughtErrorHandler((event) => {
const { message } = event;
console.log('dfcg-micro-front-main ### addGlobalUncaughtErrorHandler -- ', event);
// 加载失败时提示
if (message && message.includes('died in status LOADING_SOURCE_CODE')) {
console.log('微应用加载失败_' + message);
}
});
// 主子应用间数据传递
// initGlobalState(state) state -- 数据交互对象
// 注:
// 1. 主子应用间数据共享值需在此处定义
// 2. 主应用中未定义的变量无法监听改变,同时无法进行数据传递
const actions = initGlobalState({
token: getToken(),
test: '1',
test1: {},
});
// 主子应用间数据传递 -- 数据改变回调
// 主应用更新值 actions.setGlobalState(state)
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log('主应用 ### onGlobalStateChange', state, prev);
});
// 启动 qiankun
start({
prefetch: false, // 开启预加载
sandbox: {
// strictStyleIsolation: true, // 严格样式隔离
experimentalStyleIsolation: true,
}
});
// 微应用配置对象
// appName: 微应用标题
// name: 微应用名称标识(全局唯一)
// entry: 微应用入口标识(全局唯一)
// container: 微应用渲染基座标识
// activeRule: 微应用入口
// props: 微应用加载传参
export const MICRO_APPS = {
PersonalApp: {
appName: '个人项目',
name: 'PersonalApp',
entry: '//localhost:5173',
container: '#app-vue3',
activeRule: '/PersonalApp/index',
props: { brand: 'qiankun' },
},
IndustryApp: {
appName: '行业项目',
name: 'IndustryApp',
entry: '//localhost:5174',
container: '#app-vue3',
activeRule: '/IndustryApp/index',
props: { brand: 'qiankun' },
},
};
// 依据-微应用配置对象-注册基座转发路由表
export const registerMicroAppRouters = (router) => {
const buildRoute = (config) => {
return {
path: `/${config.name}/:pathMatch(.*)?`,
name: `${config.name}`,
component: () => import('../views/qiankunEntry.vue'),
hidden: true,
meta: {
title: `${config.appName}`,
},
};
};
const route = {
path: '',
component: Layout,
redirect: '/index',
children: Object.keys(MICRO_APPS).map(app => buildRoute(MICRO_APPS[app])),
};
router.addRoute(route);
};
写入后于 main.js 中引入
import { registerMicroAppRouters } from './microApps';
registerMicroAppRouters(router);
子应用搭建(vite)
安装
$ npm i vite-plugin-qiankun --save
安装完成后于 vite.config.js 进行配置
import qiankun from 'vite-plugin-qiankun';
import {name} from './package.json';
export default defineConfig({
resolve: {
alias: {
// 设置路径
'~': path.resolve(__dirname, './'),
// 设置别名
'@': path.resolve(__dirname, './src'),
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},
plugins: [
vue(),
qiankun(`${name}`, {useDevMode: true}),// 配置 qiankun
],
server: {
port: 5173,
host: true,
origin: '//localhost:5173',// 配置 origin, 使微项目方式访问时静态资源可访问
proxy: {
'/dev-api': {
target: 'http://10.8.7.118:8080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, ''),
},
},
}
})
main.js 中添加配置
import { createApp } from 'vue';
import App from './App.vue';
import { initRouter, unmountRouter } from './router';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import CgUI from 'cg-ui';
import 'cg-ui/lib/index.css';
import './styles/index.css';
import { getToken } from '@/utils/auth';
import { useConfig } from './hooks/useConfig';
import pinia from '@/pinia/index';
let instance = null;
let container = null;
/**
* 渲染应用
* @param props
*/
function render (props = {}) {
// const { container } = props;
container = props.container;
console.log({ container });
useConfig().updateMicroProps(props);
instance = createApp(App);
initRouter(props, instance);
instance.use(pinia);
instance.use(CgUI, {
baseURL: import.meta.env.VITE_APP_BASE_API,
logExpired () {
console.log('登录过期');
},
getToken,
elementPlusConfig: {
size: 'default',
zIndex: 1000,
}
});
instance.mount(container ? container.querySelector('#app') : '#app');
}
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
console.log('独立应用运行');
useConfig().updateMicroModel(false);
render({});
} else {
useConfig().updateMicroModel(true);
}
renderWithQiankun({
// 生命周期 - 挂载后
async mount (props) {
console.log('dfcg-micro-front-personal -- mount', props);
render(props);
},
// 生命周期 - 挂载前
async bootstrap () {
console.log('dfcg-micro-front-personal -- bootstrap');
},
async update (props) {
console.log('dfcg-micro-front-personal -- update', props, instance);
instance.mount(container ? container.querySelector('#app') : '#app');
},
// 生命周期 - 解除挂载
async unmount (props) {
console.log('dfcg-micro-front-personal -- unmount', props);
instance.unmount();
instance._container.innerHTML = '';
instance = null;
unmountRouter();
},
});
子应用间/子主间页面跳转
history.pushState({}, '', '/PersonalApp/about');
子应用间/子主间同步数据
microProps.setGlobalState({test: '2', test1: {a: 2}})