返回文章

微前端

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。微前端的概念借鉴自后端的微服务,主要是为了解决大型工程在变更、维护、扩展等方面的困难问题。

简介

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。微前端的概念借鉴自后端的微服务,主要是为了解决大型工程在变更、维护、扩展等方面的困难问题。

微前端优势

  • 技术栈技术栈无关,即子应用可自主选择技术框架如 Vue2Vue3ReactSvelteJQuery
  • 各主子应用独立开发、独立部署,代码库更小、内聚性更强
  • 可维护性和扩展性更好,便于局部升级和增量升级,如对于巨型应用进行技术重构升级可通过微前端实现渐进式重构
  • 独立运行时,每个微应用之间状态隔离,运行时状态不共享

实现方式

  • iframe:硬隔离,上下文不共享,开发体检较差
  • 基座模式:qiankunsingle-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}})