微前端框架qiankun学习 —— 搭建框架

什么是微前端?

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端架构具备以下几个核心价值:

  1. 技术栈无关 主框架不限制接入应用的技术栈,微应用具备完全自主权
  2. 独立开发、独立部署 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  3. 增量升级
  4. 在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  5. 独立运行时每个微应用之间状态隔离,运行时状态不共享

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

qiankun是什么

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

快速上手

本次使用主应用与子应用都试用VUE3.0版本搭建,其他版本有时间再补充

主应用

  1. 安装 qiankun
$ yarn add qiankun # 或者 npm i qiankun -S```
  1. 在主应用中注册微应用
// 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')

子应用

  1. 安装qiankun
$ 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

踩坑记录

  1. 创建vue项目时默认使用hash模式,配置好框架后路由跳转无反应也无报错,需要配置成history
    微前端框架qiankun学习 —— 搭建框架_第1张图片

  2. 主应用注册配置应与微应用保持一致

附demo地址
主应用:

https://github.com/qinmingliang/app-qiankun-main.git```

微应用

https://github.com/qinmingliang/app-qiankun-system.git

本文章仅用于学习交流,不正之处多多指教~~

你可能感兴趣的:(qiankun,微前端,前端框架,javascript,学习)