微前端实践

435 阅读3分钟

之前项目的后台管理前端系统是用vue2写的,由于当时是一个团队多个业务聚集在一起,就都放在了一个前端项目里管理。

image.png

通过顶部的tab进行切换

但是最近由于团队切分,以及业务增多,导致开发,测试,发布上线等都很麻烦,于是就想着用qiankun微前端将各个的业务切分出去。

废话不多说,开干。

首先、主应用引入qiankun插件

    npm i qiankun -S

然后按照官网的文档是需要在入口文件注册微应用的

    import { registerMicroApps, start } from 'qiankun';

    registerMicroApps([
      {
        name: 'react app', // app name registered
        entry: '//localhost:7100',
        container: '#yourContainer',
        activeRule: '/yourActiveRule',
      },
      {
        name: 'vue app',
        entry: { scripts: ['//localhost:7100/main.js'] },
        container: '#yourContainer2',
        activeRule: '/yourActiveRule2',
      },
    ]);

    start();

但是,由于我这边的子应用路由是动态路由,放在了router-view'里,如果在主应用注册,就会找不到子应用的容器。所以我这边把注册放到了layout组件里面。

image.png 然后在mounted生命周期中注册子应用。这样,每次刷新页面或者顶部tab切换路由都能找到子应用容器。

    mounted() {
        let urpt = document.getElementById('urpt')
        this.$nextTick(() => {
            console.log('子应用找到了urpt', urpt)
            if (urpt) {
                // 注册子应用
                registerMicroApps([
                    {
                        name: 'traffic.urpt.admin.web',
                        entry: process.env.NODE_ENV === 'production' ? `//${window.location.host}/urpt/` : '//localhost:8080/urpt/',
                        container: '#urpt',
                        activeRule: '/saas/platform/urpt'
                    }
                ])

                // 启动
                start()
            }
        })
    },

注册完子应用后,router.js添加子应用路由

export default [
      {
        path: '/platform',
        icon: 'key',
        name: 'platform',
        title: '城乡客运管理',
        access: 0,
        component: Main,
        tag: 'platform',
        children:[
            {
                path: 'urpt',
                title: '城乡客运',
                name: 'urpt'
            },
        ]
       }
]

这样,主应用基座就基本上ok了。

接下来,开始创建子应用项目,在入口文件main.js导出微应用生命周期。

// main.js
import './public-path'
import { lifeCycle, render } from './life-cycle'

/**
 * @name 统一注册插件、样式
 */
import './install'

/**
 * @name 导出微应用生命周期
 */
const { bootstrap, mount, unmount } = lifeCycle()
export { bootstrap, mount, unmount }

/**
 * @name 单独环境直接实例化vue
 */
const __qiankun__ = window.__POWERED_BY_QIANKUN__
__qiankun__ || render()

由于子应用是用vue3开发,life-cycle里面添加了element-plus库以及pinia状态管理插件等。

// life-cycle.js
import { createApp } from 'vue'

import 'normalize.css/normalize.css' // a modern alternative to CSS resets
import '@/styles/index.less'

import ElementPlus from 'element-plus' // element-ui for vue3
import 'element-plus/dist/index.css' // element-ui css
import zhCn from 'element-plus/es/locale/lang/zh-cn'
// import elementIcon from "@/plugins/svgicon";
import * as Icons from '@element-plus/icons-vue'

import { createRouter, createWebHashHistory } from 'vue-router'
import App from './App.vue'
import { createPinia } from 'pinia'
import { mainStore } from './store/store.js'
import selfRoutes from './router/routes'
import { getUserInfo } from '@/api/layoutAPI'

/**
 * @name 导入官方通信方法
 */
import appStore from './utils/app-store'

const __qiankun__ = window.__POWERED_BY_QIANKUN__
const pinia = createPinia()
let router = null
let instance = null

/**
 * @name 导出生命周期函数
 */
const lifeCycle = () => {
    return {
        /**
         * @name 微应用初始化
         * @param {Object} props 主应用下发的props
         * @description  bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发
         * @description 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等
         */
        async bootstrap(props) {
            console.log('props:', props)
            /* props.emits.forEach(i => {
        Vue.prototype[`$${i.name}`] = i;
      }); */
        },
        /**
         * @name 实例化微应用
         * @param {Object} props 主应用下发的props
         * @description 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
         */
        async mount(props) {
            console.log(props)
            // 注册应用间通信
            appStore(props)
            // 注册微应用实例化函数
            render(props)
        },
        /**
         * @name 微应用卸载/切出
         */
        async unmount() {
            instance.$destroy ? instance.$destroy() : ''
            instance = null
            router = null
        },
        /**
         * @name 手动加载微应用触发的生命周期
         * @param {Object} props 主应用下发的props
         * @description 可选生命周期钩子,仅使用 loadMicroApp 方式手动加载微应用时生效
         */
        async update(props) {
            console.log('update props', props)
        },
    }
}

/**
 * @name 子应用实例化函数
 * @param {Object} props param0 qiankun将用户添加信息和自带信息整合,通过props传给子应用
 * @description {Array} routes 主应用请求获取注册表后,从服务端拿到路由数据
 * @description {String} 子应用路由前缀 主应用请求获取注册表后,从服务端拿到路由数据
 */
const render = ({ container } = {}) => {
    router = createRouter({
        history: createWebHashHistory('/merchant'),
        routes: __qiankun__ ? selfRoutes : selfRoutes,
    })
    let app = createApp(App)

    // 全局注册图标
    for (let i in Icons) {
        app.component(i, Icons[i])
    }

    instance = app
        .use(ElementPlus, {
            size: 'default',
            locale: zhCn,
        })
        .use(router)
        .use(pinia)
        .mount(container ? container.querySelector('#app') : '#app')

    const store = mainStore()
}

export { lifeCycle, render, router }

注意:main.js里面引入了public-path文件,是为了解决主服务器加载js,css,图片等资源路径404的问题。

// public-path.js
    if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
// 解决主服务加载js.css 图片等资源路径404问题

最后在router路由页面配置一下路由就可以了

import AppMain from '../views/layout/index.vue'

const routes = [
    {
        path: '/',
        redirect: '/merchant',
    },
    {
        path: '/merchant',
        name: 'merchantApp',
        component: AppMain,
        children: [
            {
                path: '/merchant',
                name: 'merchant',
                component: () => import('../views/merchant/index.vue'),
            }
        ]
    },
    {
        path: '/login',
        name: 'login',
        component: () => import('../views/login/index.vue'),
    },
]

export default routes

image.png