• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

wl-ui/wl-mfe: 基于vue3+koa2+qiankun2的微前端后台管理系统项目实战

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

wl-ui/wl-mfe

开源软件地址:

https://github.com/wl-ui/wl-mfe

开源编程语言:

JavaScript 97.8%

开源软件介绍:

wl-mfe

基于 vue3.0-beta 及 qiankun2.0 极速尝鲜!微前端进阶实战项目。
项目地址:wl-mfe

微前端实战详细入门教程及解放方案请转至我另一篇文章:微前端实战看这篇就够了 - Vue项目篇
项目地址:wl-micro-frontends [wl-qiankun] && 在线访问
部署教程:使用各种姿势舒服的部署微前端项目

最终效果

wl-mfe

项目启动

npm run yinit    // 使用yarn下载依赖,推荐
npm run cinit    // 使用cnpm下载依赖
npm run init     // 或 使用npm下载依赖

npm run serve    // 运行全部项目
yarn serve y     // yarn运行全部项目

npm run build     // 打包全部项目
yarn build y      // 打包全部项目

npm run publish   // 执行发布脚本

注意:如果下载报错,报 bin/sh 找不到start命令,那你可能是mac or linux,那就进入目录一个一个下载运行吧。
另:执行批量服务耗时较久,请耐心等待,init与build成功会在控制台提示,serve稍加等待或刷新浏览器即可。

实战详解todo

  • 主应用基座构建
  • 子应用构建
  • 微应用间通信
  • 跨应用通信与vuex结合
  • 发布上线

主应用基座构建

主应用需要用到elementui,暂时使用vue2.0+qiankun2.0版本。vue3.0beta体验在下面【子应用构建】章节

主应用项目主要在5个文件:utils文件夹,app.vueappRegister.jsmain.jsrender.js

前提条件

cnpm i qiankun -S

在主应用下载qiankun,注意使用2.0以上版本

改造主应用app.vue

<template>
  <div class="main-container-view">
    <el-scrollbar class="wl-scroll">
      <!-- qiankun2.0  container 模式-->
      <div id="subapp-viewport" class="app-view-box"></div>
      <!-- qiankun1.0  render 模式-->
      <div v-html="appContent" class="app-view-box"></div>
      <div v-if="loading" class="subapp-loading"></div>
    </el-scrollbar>
  </div>
</template>

<script>
  export default {
    name: "rootView",
    props: {
      loading: Boolean,
      appContent: String
    }
  };
</script>

注意这里,qiankun2.0是根据 container字段对应的dom id来注册子应用盒子的,因此只用qiankun2.0的话不需要考虑render注测子应用盒子的情况,下面那两个dom和script里的props都可以不要!只留一个<div id="subapp-viewport"></div>即可!
另外:注册子应用时每个子应用都可以指定一个不同的container,因此如果想做每个子应用的keep-alive,则可能需要每个子应用对应一个<div id="subapp-viewport-ui"></div><div id="subapp-viewport-blog"></div>盒子

将实例化vue方法提取至render.js

import Vue from "vue"
import router from './router'
import store from './store'
import App from './App.vue'

/**
 * @name 提取vue示例化方法
 */
export function vueRender() {
  Vue.config.productionTip = false
  new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount("#main-container");  
}

为什么要仅仅将这段代码从main.js摘出呢?一方面是尽量清洁main.js;另一方面,就是为了兼容qiankun1.0的render方法。
因为qiankun1.0需要在注册vue实例时显式的将appContent传入app.vue,如果你不用qiankun1.0版本,则完全不需要以下代码:

/**
 * @description 实例化vue,并提供子应用 render函数模式的装载能力
 * @description 如果使用qiankun2.0 版本,只需正常实例化vue即可 不需要存在此render函数
 * @param {Object} param0 
 * @description {String} appContent 子应用内容
 * @description {Boolean} loading 是否显示加载动画(需手动实现loading效果)
 * @param {Boolean} notCompatible true则不兼容qiankun1.0 【此参数为示例添加,实际应用自酌】
 */
export function vueRender({ appContent, loading }, notCompatible) {
  Vue.config.productionTip = false

  // 实际上本实例只用到此if内的代码
  // 本文件其他代码只为做兼容qiankun1.0 render挂载子应用的参考
  if (notCompatible) {
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount("#main-container");
    return;
  }

  return new Vue({
    router,
    store,
    data() {
      return {
        appContent,
        loading,
      };
    },
    render(h) {
      return h(App, {
        props: {
          appContent: this.content,
          loading: this.loading
        }
      });
    }
  }).$mount('#main-container');
}

let app = null;

/**
 * @name 提供render装载子应用方法
 * @param {Object} param0 
 * @description {String} appContent 子应用内容
 * @description {Boolean} loading 是否显示加载动画(需手动实现loading效果)
 */
export default function render({ appContent, loading }) {
  if (!app) {
    app = vueRender({ appContent, loading });
  } else {
    app.appContent = appContent;
    app.loading = loading;
  }
}

此处是给兼容qiankun1.0 registerMicroApps方法render字段一种方案,事实上升级到2.0完全无压力,因此建议不需要留下臃肿的render方法。

将注册子应用的逻辑抽离到appRegister.js

下面用了一个方法将qiankun需要用到的方法全部包装起来,以便后续将注册子应用放到获取后端注册表数据后执行。

/**
 * @name 启用qiankun微前端应用
 * @param {*} list 
 * @param {*} defaultApp 
 */
const useQianKun = (list, defaultApp) => {
  /**
  * @name 注册子应用
  * @param {Array} list subApps
  */
  registerMicroApps(
    [
       {
        name: 'subapp-ui', // 子应用app name 推荐与子应用的package的name一致
        entry: '//localhost:6751', // 子应用的入口地址,就是你子应用运行起来的地址
        container: '#yourContainer', // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】
        activeRule: '/ui', // 子应用的路由前缀
      },
    ],
    {
      beforeLoad: [
        app => {
          console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
        },
      ],
      beforeMount: [
        app => {
          console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
        },
      ],
      afterUnmount: [
        app => {
          console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
        },
      ],
    },
  )

  /**
   * @name 设置默认进入的子应用
   * @param {String} 需要进入的子应用路由前缀
   */
  setDefaultMountApp('ui');
  /**
   * @name 启动微前端
   */
  start();
  /**
   * @name 微前端启动进入第一个子应用后回调函数
   */
  runAfterFirstMounted(() => {
    console.log('[MainApp] first app mounted');
  });
}

结合请求后端注册表,并给子应用分发路由及数据改造后的完整代码:

import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from "qiankun";
import store from "./store";
/**
 * @name 导入render函数兼容qiakun1.0装载子应用方法,如果使用2.0container装载则不需要此方法,此处留着注释代码提供兼容qiankun1.0的示例
 * @description 此处留下注释代码仅为提供兼容qiankun1.0示例
 */
// import render from './render';
/**
 * @name 导入接口获取子应用注册表
 */
import { getAppConfigsApi } from "./api/app-configs"
/**
 * @name 导入消息组件
 */
import { wlMessage } from './plugins/element';
/**
 * @name 导入想传递给子应用的方法,其他类型的数据皆可按此方式传递
 * @description emit建议主要为提供子应用调用主应用方法的途径
 */
import emits from "./utils/emit"
/**
 * @name 导入qiankun应用间通信机制appStore
 */
import appStore from './utils/app-store'
/**
 * @name 声明子应用挂载dom,如果不需要做keep-alive,则只需要一个dom即可;
 */
const appContainer = "#subapp-viewport";
/**
 * @name 声明要传递给子应用的信息
 * @param data 主应要传递给子应用的数据类信息
 * @param emits 主应要传递给子应用的方法类信息
 * @param utils 主应要传递给子应用的工具类信息(只是一种方案)
 * @param components 主应要传递给子应用的组件类信息(只是一种方案)
 */
let props = {
  data: store.getters,
  emits
}

/**
 * @name 请求获取子应用注册表并注册启动微前端
 */
getAppConfigsApi().then(({ data }) => {
  // 验证请求错误
  if (data.code !== 200) {
    wlMessage({
      type: 'error',
      message: "请求错误"
    })
    return;
  }
  // 验证数据有效性
  let _res = data.data || [];
  if (_res.length === 0) {
    wlMessage({
      type: 'error',
      message: "没有可以注册的子应用数据"
    })
    return;
  }
  // 处理菜单并存入主应用Store
  store.dispatch('menu/setMenu', _res);
  // 处理子应用注册表数据。详细数据见 master mock
  let apps = []; // 子应用数组盒子
  let defaultApp = null; // 默认注册应用
  let isDev = process.env.NODE_ENV === 'development'; // 根据开发环境|线上环境加载不同entry
  _res.forEach(i => {
    apps.push({
      name: i.module, // 子应用名
      entry: isDev ? i.devEntry : i.depEntry, // 根据环境注册生产环境or开发环境地址
      container: appContainer,  // 绑定dom
      activeRule: i.routerBase, // 绑定子应用路由前缀
      props: { ...props, routes: i.children, routerBase: i.routerBase } // 将props及子应用路由,路由前缀由主应用下发
    })
    if (i.defaultRegister) defaultApp = i.routerBase; // 记录默认启动子应用
  });
  // 启用qiankun微前端应用
  useQianKun(apps, defaultApp);
})

/**
 * @name 启用qiankun微前端应用
 * @param {*} list 
 * @param {*} defaultApp 
 */
const useQianKun = (list, defaultApp) => {
  /**
  * @name 注册子应用
  * @param {Array} list subApps
  */
  registerMicroApps(
    list,
    {
      beforeLoad: [
        app => {
          console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
        },
      ],
      beforeMount: [
        app => {
          console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
        },
      ],
      afterUnmount: [
        app => {
          console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
        },
      ],
    },
  )

  /**
   * @name 设置默认进入的子应用
   * @param {String} 需要进入的子应用路由前缀
   */
  setDefaultMountApp(defaultApp);

  /**
   * @name 启动微前端
   */
  start();

  /**
   * @name 微前端启动进入第一个子应用后回调函数
   */
  runAfterFirstMounted(() => {
    console.log('[MainApp] first app mounted');
  });
}

/**
 * @name 启动qiankun应用间通信机制
 */
appStore(initGlobalState);

注册应用间通信机制 utils文件夹

上面注册子应用时,我们看到代码里有传给子应用的props和一个appStore通信函数。

  1. 关于props,看过我上个文章的朋友都知道我将props分为那几个模块,实际上,我真正用到的可能就是主应用请求获取下来的routesrouterbase下发给子应用。
  2. 关于appStore方法,我是将官方通信机制提取至utils文件夹下的app-store.js文件,并和vuex相结合。代码如下:
import store from "@/store";

/**
 * @name 启动qiankun应用间通信机制
 * @param {Function} initGlobalState 官方通信函数
 * @description 注意:主应用是从qiankun中导出的initGlobalState方法,
 * @description 注意:子应用是附加在props上的onGlobalStateChange, setGlobalState方法(只用主应用注册了通信才会有)
 */
const appStore = (initGlobalState) => {
  /**
   * @name 初始化数据内容
   */
  const { onGlobalStateChange, setGlobalState } = initGlobalState({
    msg: '来自master初始化的消息',
  });

  /**
   * @name 监听数据变动
   * @param {Function} 监听到数据发生改变后的回调函数
   * @des 将监听到的数据存入vuex
   */
  onGlobalStateChange((value, prev) => { 
    console.log('[onGlobalStateChange - master]:', value, prev);
    store.dispatch('appstore/setMsg', value.msg)
  });

  /**
   * @name 改变数据并向所有应用广播
   */
  setGlobalState({
    ignore: 'master',
    msg: '来自master动态设定的消息',
  });
}

export default appStore;

【注意:如未在主应用注册通信,则在子应用也获取不到通信方法】

改造main.js

终于我们来到了最后一步,主应用一切改造完成之后,我们将其引入到main.js并执行:

/**
 * @name 统一注册外部插件、样式、服务等
 */
import './install'

/**
 * @name 微前端基座主应用vue实例化
 * @description 为了兼容 qiankun1.0 的render函数装载子应用能力
 * @description 2.0版本正常实例化vue即可,不需要此render函数
 * @description qiankun registerMicroApps方法 render用到,如果使用container装载子应用,无需此render函数
 * @deprecated 本示例只针对 qiankun2.0 因此只留下注释后的代码在此提醒各位读者如何兼容qiankun1.0
 */
/* import render from './render';
render({ loading: true }) */
import { vueRender } from './render'
vueRender({}, true)

/**
 * @name 注册微应用并启动微前端
 */
import './appRegister'

热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap