微前端框架 qiankun 项目实战(一)--本地开发篇-程序员宅基地

技术标签: java  gwt  web  vue  xhtml  

作者:黑化程序员

https://juejin.cn/post/6970310177517993998

大家好,我是小黑。

公司使用技术栈是vue,最近遇到了一个需求,要把原有后台管理系统的功能模块搬迁到新的后台管理系统上面去。原本这没有多复杂的事,直接复制粘贴改改就可以,但是有这么几个坑点,我瞬间陷入了沉思:

  1. 新的后台使用的是vue3,原有的后台使用的是vue2

  2. 新的后台有自己的一套登录角色权限管理方案,旧的后台也有

  3. 由于vue3和vue2区别还是比较大的,vue3相当于整个vue重写了,虽说做了向下兼容,但是直接复制粘贴过去不太现实(主要是我试过复制了一个模块过去devtools的红色惨不忍睹)

怎么办,把vue2写好的模块重新用vue3写一次(令人窒息)?正准备含泪敲键盘的时候,我想到了以前看过的微前端的相关文章,不如试试这个玩意吧,然后,微前端正式踩坑。

什么是微前端?

按照网上的说法和小黑的理解,微前端就是应用分割独立运行独立部署,将原本把所有功能集中于一个项目中的方式转变为把功能按业务划分成一个主项目和多个子项目,每个子项目负责自身功能,同时具备和其它子项目和主项目进行通信的能力,达到更细化更易于管理的目的。总的来说微前端就是

一个完整应用划分成一个主应用和一个或多个微应用,应用间相互独立,可相互通信。

如何实现微前端?

符合上面条件,最容易想到的就是iframe,下面贴上两段最简单的iframe及其通讯代码

// parent.html
<div>我是parent</div>
<button id="parentBtn">parent btn</button>
<iframe src="./child.html" id="frame"></iframe>

<script>  
function parentFunc(msg) {    
  console.log("parent 的方法:", msg)  
}  

var btn = document.querySelector("#parentBtn")  
btn.addEventListener('click', function() {    
  console.log("我是parent的button")    
  console.log("我调用了:")    
  document.getElementById('frame').contentWindow.childFunc('parent');  
})
</script>

// child.html
<div>我是child</div>
<button id="childBtn">child btn</button>
<script>  
function childFunc(msg) {    
  console.log("child 的方法:", msg)  
}  

var btn = document.querySelector("#childBtn")  
btn.addEventListener('click', function() {    
  console.log("我是child的button")    
  console.log("我调用了:")    
  parent.window.parentFunc('child');   
})
</script>

以上两段代码放到本地服务器中就是这样的然后点击两个按钮,就可以互相通信传参了

以上的两个html必须放到有域名的环境中运行,否则会报错。

当然了,这次的项目迁移我不是直接用iframe改造的,而是站在巨人的肩膀上,我用了一个叫qiankun的微前端框架改造,因为公司的代码我不能贴上来,下面我会建一个vue3项目和一个vue2项目来大概还原一下我是如何改造公司项目的,还有我遇到的坑是怎么填的。

微前端框架qiankun

首先,用vue官方的脚手架建立一个vue3的基本后台界面和一个vue2的基本后台界面,注意这里因为vue3打包使用了vite的原因,所以qiankun框架不能使用vue3作为微应用,这里我们主应用是vue3,微应用是vue2,这跟我改造的也是一致的,两个项目大概结构是一样的,如下:

为了方便大家,贴上我建好的模板仓库地址

vue3模板:https://gitee.com/jimpp/vue3-main-app(主应用,主应用必须安装qiankun)

vue2模板:https://gitee.com/jimpp/vue2-micro-app(微应用)

上面master分支都是未改造前能独立运行的项目,dev分支是最终改造后的项目,当然自己从头到尾建立也是可以的,但是要保证两个仓库都具备router,store,登录拦截的功能

两个模板都具备这样的界面

1.登录界面

咳咳,简陋了点,为了显示请不要打我哈哈。

2.左侧菜单和router-view界面

好了,下面开始基于qiankun框架改造两个项目

主应用启动qiankun

这里我使用了qiankun官网的registerMicroApps注册微应用

在主应用的src文件夹下新建一个micros文件夹,在micros文件夹新建index.js,app.js

// index.js
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import {  
  registerMicroApps,  
  addGlobalUncaughtErrorHandler,  
  start,
} from "qiankun";// 微应用注册信息
import apps from "./app";

registerMicroApps(apps, {  
  beforeLoad: (app) => {    
    // 加载微应用前,加载进度条    
    NProgress.start();    
    console.log("before load", app.name);    
    return Promise.resolve();  
  },  
  afterMount: (app) => {    
    // 加载微应用前,进度条加载完成    
    NProgress.done();    
    console.log("after mount", app.name);    
    return Promise.resolve();  
  },
});

addGlobalUncaughtErrorHandler((event) => {  
  console.error(event);  
  const { message: msg } = event  
  if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {    
  console.error("微应用加载失败,请检查应用是否可运行");  
  }
});
export default start;

首先yarn add nprogress安装nprogress这个库,是为了到时候在加载微应用的时候有进度条显示,这里用到了官方的几个api

  1. registerMicroApps:包含两个参数,第一个参数是微应用的一些注册信息,第二个参数是全局的微应用生命周期钩子。

  2. addGlobalUncaughtErrorHandler:全局的未捕获异常处理器,微应用发生报错的时候亦可以用这个api捕捉。

  3. start:我们用来启动qiankun的方法,包含一个参数,具体的参数用途不再详述。

以上详细的api请点击这里:

// app.js

const apps = [  
  {    
    name: "vue-micro-app",    
    entry: "//localhost:8081",    
    container: "#micro-container",    
    activeRule: "#/vue2-micro-app",  
  },
];
export default apps;

app.js导出的是上面registerMicroApps的第一个参数,是一个对象数组,其中数组每个字段的作用如下:

  1. name:微应用的名称,后面改造微应用的时候一定要与这个name对应

  2. entry:微应用运行的域名加端口,我用的是本地8081端口

  3. container:启动微应用需要一个dom容器,里面就是这个dom容器的id,用class应该也是可以的

  4. activeRule:触发启动微应用的规则,当检测到url中含有activeRule的值时,将启动微应用

添加完上述两个js后,我们回到main.js,目前的main.js应该是这样的

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/main.css'

createApp(App).use(store).use(router).mount('#app')

改造也非常简单,把上面micros中的index.js引入,然后运行一下start函数就大功告成了

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/main.css'
import start from '@/micros'


createApp(App).use(store).use(router).mount('#app')

start()

刷新一下浏览器,发现主应用和改造前并无差异!

主应用添加微应用容器和微应用菜单

目前主应用app的菜单代码结构如下

<div class="nav" v-if="token">    
  <div class="menu">      
    <router-link to="/">Parent Home</router-link>    
  </div>    
  <div class="menu">      
    <router-link to="/about">Parent About</router-link>    
  </div>
</div>

现在我们添加两个菜单,分别对应子应用的homeabout

<div class="nav" v-if="token">    
  <div class="menu">      
    <router-link to="/">Parent Home</router-link>    
  </div>    
  <div class="menu">      
    <router-link to="/about">Parent About</router-link>    
  </div>
    <!--- 新添加 --->
  <div class="menu">      
    <router-link to="/vue2-micro-app">Child Home</router-link>    
   </div>    
  <div class="menu">      
    <router-link to="/vue2-micro-app/about">Child About</router-link>    
   </div>
</div>

<div class="container">   
  <div class="header" v-if="token">Child Header</div>   
  <div class="router-view">      
    <router-view />      
    <!-- 新添加,微应用的容器 -->      
    <div id="micro-container"></div>   
  </div>
</div>

相信你也发现了,to中多了上面app.jsactiveRule字段中对应的值(去掉了#号),因为#/vue2-micro-app正是触发启动微应用的条件

这是刷新我们的微应用,然后点击一下Child Home菜单,你会发现有两个报错

第一个是跨域报错,因为我们主应用运行在8080端口,微应用是8081端口,后面用nginx做一下代理就好

第二个报错就是源自于我们的微应用还未改造,所以还等什么,赶紧改造微应用

微应用改造

官网写了,微应用入口必须导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用

这是微应用改造前的main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/main.css'
Vue.config.productionTip = false
new Vue({  
  router,  
  store,  
  render: h => h(App)
}).$mount('#app')

下面我们来改造一下main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/main.css'
Vue.config.productionTip = false

// 新增:用于保存vue实例
let instance = null; 

// 新增:动态设置 webpack publicPath,防止资源加载出错
if (window.__POWERED_BY_QIANKUN__) {  
  // eslint-disable-next-line no-undef  
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

/** * 新增: * 渲染函数 * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行 */
function render() {  
// 挂载应用  
  instance = new Vue({    
  router,    
  store,    
  render: (h) => h(App),  
}).$mount("#micro-app");}


/** 
* 新增: 
* bootstrap 只会在微应用初始化的时候调用一次,
  下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 
*/
export async function bootstrap() {  
  console.log("VueMicroApp bootstraped");
}

/** 
* 新增: 
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 
*/
export async function mount(props) {  
  console.log("VueMicroApp mount", props);  
  render(props);
}
/** 
* 新增: 
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 
*/
export async function unmount() {  
  console.log("VueMicroApp unmount");  
  instance.$destroy();  
  instance = null;
}

// 新增:独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {  
  render();
}

// 这是原本启动的代码
// new Vue({
//   router,
//   store,
//   render: h => h(App)
// }).$mount('#app')

请注意,render方法中我把$mount后的参数改为了#micro-app,这是为了区分主应用和微应用中index.html的根id,所以微应用中的public文件夹的index.html也要改为micro-app

然后还要对webpack配置进行改造,微应用根目录添加vue.config.js文件

const path = require("path");

module.exports = {
  devServer: {
    // 监听端口
    port: 8081,
    // 关闭主机检查,使微应用可以被 fetch
    disableHostCheck: true,
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
    output: {
      // 微应用的包名,这里与主应用中注册的微应用名称一致
      library: "vue-micro-app",
      // 将你的 library 暴露为所有的模块定义下都可运行的方式
      libraryTarget: "umd",
      // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
      jsonpFunction: `webpackJsonp_vue-micro-app`,
    },
  },
};

然后还要改造一下我们的路由

if (window.__POWERED_BY_QIANKUN__) {  
  microPath = '/vue2-micro-app'
}

const routes = [
  {    
    path: microPath + '/login',    
    name: 'login',    
    component: Login  
  },  
  {    
    path: microPath + '/',    
    redirect: microPath + '/home'  
  },  
  {    
    path: microPath + '/home',    
    name: 'Home',    
    component: Home  
  },  
  {    
    path: microPath + '/about',    
    name: 'About',
    component: () => import( /* webpackChunkName: "about" */ '../views/About.vue')  
  }
]

router.beforeEach((to, from, next) => {  
  if (to.path !== (microPath + '/login')) {    
    if (store.state.token) {      
      next()    
    } else {      
      next(microPath + '/login')    
    }  
  } 
  else {    
    next()  
  }
})

路由主要的改动就是每个path都添加了一个microPath变量,用于检测是否由微前端改动,相应的路由守卫也要添加microPath变量,另外微应用的login跳转的时候也要加上microPath判断

最后重启一下我们的微应用,再去我们的主应用点击一下Child Home菜单,如无意外你就会得到和我下面截图一样的界面没错,你已经成功了!vue2的项目已经成功嵌入到vue3中去了

但是,细心的你也发现了,我已经登录了一次了,为什么又要登录一次呀,所以,接下来我们要利用通信去解决掉这个问题。

主应用和微应用通信

应用间的通信,我们要利用qiankun框架的initGlobalStateMicroAppStateActions api,相关的api介绍如下:

setGlobalState:设置 globalState - 设置新的值时,内部将执行浅检查,如果检查到globalState发生改变则触发通知,通知到所有的观察者函数。

onGlobalStateChange:注册观察者函数 - 响应globalState变化,在globalState发生改变时触发该观察者函数。

offGlobalStateChange:取消观察者函数 - 该实例不再响应globalState变化。

所以我们再次改造一下两个项目,首先是主应用的micros/index.js

import {  
registerMicroApps,  
addGlobalUncaughtErrorHandler,  
start,  
initGlobalState // 新增
} from "qiankun";

const state = {} 
const actions = initGlobalState(state);

export {  actions }

以上新增了并导出了actions,然后去到login.vue

import { actions } from "@/micros"; //新增

const login = () => {      
  if (username.value && password.value) {  
    store.commit("setToken", "123456");        
    // 新增
    actions.setGlobalState({globalToken: "123456"});        
    router.push({path: "/"});
  }
};

引入actions并新增了actions.setGlobalState方法

然后是子应用的main.js

function render(props) {  
  console.log("子应用render的参数", props) 
  // 新增 
  props.onGlobalStateChange((state, prevState) => {    
    // state: 变更后的状态; prev 变更前的状态    
    console.log("通信状态发生改变:", state, prevState);    
    // 这里监听到globalToken变化再更新store
    store.commit('setToken', '123456')  }, true); 
   // 挂载应用  
  instance = new Vue({    
    router,    
    store,    
    render: (h) => h(App),  
  }).$mount("#micro-app");}

render方法中我们加上onGlobalStateChange,并且第二位参数置为true,这样微应用一启动的时候,我们马上就可以看到刚刚设置的globalToken:123456

好了已经改造完毕,我们刷新重新登录主应用然后点击微应用的菜单,可以看到微应用不需要再登录了,如下图:

好像还是有点问题喔,微应用的菜单怎么展示出来了???

别怕,最后一步,留给亲爱的你去解决吧,思路就是在微应用中利用window.__POWERED_BY_QIANKUN__去判断是否通过qiankun启动的,是的话我们写个变量使用v-if将微应用的菜单和头部隐藏,不就完事了?

以上就是qiankun框架实战的第一篇本地开发的全部内容,总体结构上跟我做的项目迁移很相似了,其它还有些小细节不影响,其实本章有一个巨坑,下一篇将带大家部署打包后的项目,并告诉大家这个巨坑在哪里。

   “分享、点赞、在看” 支持一波 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_34998786/article/details/118005639

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签