• 「微前端实践」使用Vue+qiankun微前端方案重构老项目的本地验证


      10月份换了新的工作,参与完一个月的需求迭代后,接到了项目重构的任务。简单来说,需要在短时间内提出方案设想,同时进行本地验证,最终需要拿出一套技术替换方案来。于是,埋头苦干了一个月,总算干了点成绩出来,今天把当时的思考和实践做个简单总结,就当是个复盘吧。

      一、老项目现状

      最初接触到的老项目,使用到的前端技术栈主要是(jQuery1.8 + layui-v2.5.4 + ExtJS4.2.1.883),当时拿到这个项目的时候,上手书写起来竟然有点手生,毕竟这几年长时间使用的都是vue和react,jquery的书写方式有点陌生了,同时发现使用的ExtJS4.2.1.883(2011年发布)版本陈旧、 layui-v2.5.4于2021年7月也已停止更新。这两种框架,一种学习曲线高,一种停止更新后可能后续碰到复杂问题没有地方可查,所以还是想着整体上去替换。

      二、提出设想

      由于当前项目,子模块众多,且每个模块涉及功能也很多,老项目中将所有模块都独立成单独文件夹,在菜单点击时,获取模块url,然后基于iframe方式接入。这种方式存在的问题是:

    • 每个模块都单独使用文件,公共文件不能共享,模块越多,文件越多。
    • iframe方式引入,每次加载都需要重建浏览器上下文、资源重新加载,导致缓慢。
    • iframe方式引入的模块,与父窗口数据完全隔离,但该项目又并非是微前端项目,隔离后的数据传递步骤繁琐。

      考虑到老项目中引入iframe思想,已经有了微前端的影子,于是,我在设想中引入了qiankun微前端方案。

      2.1 微前端技术简介

      那到底什么是微前端呢?首先微的概念,就是小的意思,小是相对于大来说的。也就是说,在项目模块增加,功能增多,业务日趋复杂的情况下,一个普通应用会逐渐成为巨石应用,巨石应用在维护成本与难度上也逐渐会出现问题。如果把庞大的应用,拆分成小型应用,然后根据小型应用去开发,在人员管理、技术把控上也会更加容易。微前端的优点大家可在qiankun官网上自行查看,这里不再赘述。

      除了考虑引入微前端技术之外,我在设计时还创建了一个公共库,因为最终的微应用都会合在主应用中,作为一个整体呈现到用户眼前,这里必须要保证公用的样式,除此之外,在每个应用中会存在一些工具方法,如对数据格式的处理、时间处理等,因此还构建了一个公共库。

      2.2 微应用拆分

      已经确认使用微前端的思想,那么最重要的一个任务就是对微应用进行拆分。拆分原则上,可以根据共性业务功能去抽取,也可以根据目前已有的菜单管理去抽取,由于是在实验阶段,重在验证微前端实现上,因此我直接按照已有的菜单去抽取了对应微应用。

      2.3 技术架构

      在系统架构方面,主应用能动态注册微应用,同时通过路由管理接入微应用。

      微应用,需要满足单独开发、单独部署、单独测试、同时又能接入到主应用中与主应用同时运行的原则。单独开发时候,需要具有登录、工作台、系统管理等的功能,接入主应用,能够直接接入到内容展示区域。

      主应用、微应用根据需求安装公共组件库,同时能够独立部署。架构图涉密,这里就不放出来了。

      三、具体实现

      主应用与微应用使用到的技术栈(qiankun-2.6.3 + vueCli4.5.0 + vue-2.6.11 + element-ui-2.15.6 ),微应用也可以使用其他技术进行开发,这里技术栈不做限制。后续可以试着接入其他技术栈。

      3.1 主应用

      主应用利用vue-cli创建项目,vue-cli如何快速创建vue项目,请在Vue CLI官网查阅,默认加入vue-router、vuex生态。注,所有在3.1章节中展示的代码均在主应用项目中,当前主应用项目为micro-front-main

      3.1.1 qiankun引入

      在主应用中,执行yarn add qiankun,安装qiankun框架。

      3.1.2 路由配置

      在主应用的src目录下,创建micro文件夹,同时创建app和index.js文件,app.js文件中,配置接入的子应用,index中启动微服务。

      在微应用的路由配置上,需要遵循路由配置原则。路由配置需要根据主子路由的模式(history和hash)、主路由是否在某个路由页面加载微应用两个方面进行设计。

    • 第一种情况,主应用路由是history模式,子应用也是history模式,不在主应用的某个路由页面加载微应用,即直接在App.vue中加载微应用。   

      

    • 第二种情况,主应用路由是(history/hash)模式,微应用是hash模式,不在主应用的某个路由页面加载微应用。这种与主子路由都是hash模式的方式配置相同,需要注意‘#’。
    • 第三种情况,主应用路由是history模式,子应用路由是history模式,在主应用的某个路由页面加载微应用,如Home.vue页面加载。

    • 第四种情况,主应用路由是(history/hash)模式,子应用路由是hash模式,在主应用的某个路由页面加载微应用,如Home.vue页面加载。

     

     

      3.1.3 路由具体实现

      本文实践的主应用与微应用都是基于hash模式的,同时定义在主页面home.vue中接入微应用,因此在配置路由时,选择上面描述的第四种方式。

    1. 主应用中设置微应用dom节点

      在home页面中接入,home页面开放主、微应用dom节点。这里的micro-app在后续微应用引入配置中,需要保持一致。

    复制代码
    1 <div class="content-wrapper">
    2     <!--主应用渲染,读取的是当前主应用的路由-->
    3     <router-view v-show="$route.name"></router-view>
    4     <!--子应用渲染,读取子应用中的对应的路由-->
    5     <div v-show="!$route.name" id="micro-app"></div>
    6 </div>
    复制代码
    1. 配置路由

      在src下新建routers.js文件,在该文件中,配置主、微应用的路由。这里使用name区分主、微应用。与上面home.vue页面中规定的v-show相呼应。

    复制代码
     1 import Home from './pages/Home'
     2 import Dashboard from './pages/Dashboard'
     3 import Login from './pages/Login'
     4 const routers = [
     5     {
     6         path: '/home',
     7         name: 'Home',
     8         component: Home,
     9         children: [
    10             {
    11                 path: '',
    12                 name: 'Dashboard',
    13                 component: Dashboard
    14             }
    15         ]
    16     },
    17     /**
    18      * 微服务应用url相关配置
    19      */
    20     {
    21         path: '/home/*',
    22         component: Home
    23     }
    24 ]
    25 export default routers
    复制代码
    1. 微应用注册

      这里container属性与页面中定义的挂载dom id属性一致。

    复制代码
     1 const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
     2 
     3 const MICRO_FRONTED_DATA_MANAGE = process.env.VUE_APP_MICRO_DATA_MANAGE
     4 const MICRO_FRONTED_DATA_QUERY = process.env.VUE_APP_MICRO_DATA_QUERY
     5 // micro/app.js
     6 const apps = [
     7     {
     8         name: 'data-manage-micro',
     9         entry: MICRO_FRONTED_DATA_MANAGE,
    10         container: '#micro-app',
    11         activeRule: getActiveRule('#/home/micro-data-manage'),
    12         // 这里也可以直接写 activeRule: '#/data',但是如果主应用是 history 模式或者主应用部署在非根目录,这样写不会生效。
    13     },
    14     {
    15         name: 'micro-fronted-query',
    16         entry: MICRO_FRONTED_DATA_QUERY,
    17         container: '#micro-app',
    18         activeRule: getActiveRule('#/home/micro-fronted-query'),
    19     }
    20 ]
    21 
    22 export default apps
    复制代码
    1. 导出启动函数
    复制代码
     1 // 与路由直接关联
     2 import {
     3     registerMicroApps,
     4     start,
     5     addGlobalUncaughtErrorHandler
     6 } from 'qiankun'
     7 import apps from './app'
     8 console.log(apps)
     9 
    10 /**
    11  *  name: 微应用名称
    12  *  entry: 微应用入口,一般是微应用启动后的地址
    13  *  container: 微应用挂载节点,类似iframe的id
    14  *  activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
    15  *
    16  */
    17 registerMicroApps(apps)
    18 
    19 addGlobalUncaughtErrorHandler((event) => {
    20     console.error(event);
    21     const { message: msg } = event
    22     if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
    23         console.error("微应用加载失败,请检查应用是否可运行");
    24     }
    25 });
    26 
    27 // 启动主应用
    28 export default start
    复制代码
    1. 菜单配置

      通常菜单都会从接口中直接获取,菜单路径则是在后台管理系统中直接配置,这里配置的菜单,相当于在home.vue页面菜单栏中点击后操作的事件,因此菜单配置的路径也要和上面定义的微应用路由一致。如菜单1对应路径path为"/home/micro-fronted-query",这里的路径与微应用注册时定义的路径一致

      
      3.1.4 页面中启动qiankun

      在主应用中,如果不在根APP.vue中接入微应用,需要去对应接入微应用的页面中调用启动函数。如在App.vue页面中,则start()方法在main.js中进行调用。如在home.vue中接入,则start()方法在Home.vue页面中调用。

      
    复制代码
     1 import qiankunStart from '../micro'
     2 mounted() {
     3        this.getMenu()
     4        if( this.$router.currentRoute.path !== '/home' ) {
     5            this.$router.push({path: '/home'})
     6         }
     7         if (!window.qiankunStarted) {
     8              window.qiankunStarted = true;
     9              qiankunStart();
    10          }
    11 },
    复制代码

      主应用接入的主要功能已完成,下面开始微应用接入工作。

      3.2 微应用

      微应用同样利用vue-cli框架进行创建,主要做一下几个工作。微应用项目为micro-fronted-query。

      3.2.1 登录逻辑

      由于微应用需要确保独立运行、测试、部署的能力,因此在具体开发中需要根据项目需要,梳理登录逻辑。在本次测试中,首先利用全局导航守卫判断token是否存在,如果存在的话直接能够读取路由,如果不存在,则进入微应用的登录页面。同时为了防止cookies过期,会在接口请求的拦截器中,判断token是否有效,如果token失效了,也会直接进入微应用的登录页面。

       

      3.2.2 创建微服务配置文件

      在子应用的src目录下,创建微服务的配置文件public-path.js

    复制代码
    1 if (window.__POWERED_BY_QIANKUN__) {
    2     // 动态设置 webpack publicPath,防止资源加载出错
    3     // eslint-disable-next-line no-undef
    4     __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    5 }
    复制代码
      3.2.3 是否微服务处理

      在main.js中根据是否启用微服务,处理渲染逻辑,为了避免根 id #app与其他的 DOM 冲突,需要限制查找范围,同时还需要暴露bootstrap/mount/unmount三个生命周期函数,否则会出现报错或加载不上的问题。

       

    复制代码
     1 src/main.js
     2 // 微应用的渲染函数
     3 function render(props = {}) {
     4   const { container } = props;
     5   router = new VueRouter({
     6     // 设置微服务与独立运行时的路径
     7     routes : sysRouters.concat(routerConfigs)
     8   })
     9   vueInstance = new Vue({
    10     render: h => h(App),
    11     router
    12   }).$mount(container ? container.querySelector('#app') : '#app')
    13 }
    14 // 独立运行时,直接读取当前配置
    15 if (!isQiankun) {
    16   render()
    17 }
    18 /**
    19  * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
    20  * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
    21  */
    22 export async function bootstrap() {
    23   console.log("VueMicroApp bootstraped");
    24 }
    25 /**
    26  * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
    27  */
    28 export async function mount(props) {
    29   console.log("VueMicroApp mount", props);
    30   render(props);
    31 }
    32 /**
    33  * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
    34  */
    35 export async function unmount() {
    36   console.log("VueMicroApp unmount");
    37   vueInstance.$destroy();
    38   vueInstance = null;
    39   router = null;
    40 }
    复制代码
      3.2.4 打包配置文件修改
      在微应用的根目录下创建vue.config.js文件,在该文件中配置qiankun需要使用到的配置属性。注意configureWebpack属性下的library,需要与微应用注册时的名称相同,这里为了便于出错,可以直接获取package.json中的name值。如果出现了跨域问题,还需要在devServer中配置header跨域信息,这块大家可以查下vue.config.js配置详情
      
    复制代码
     1 const packageName = require('./package.json').name;
     2 
     3 module.exports = {
     4     // 基本路径
     5     publicPath: "/query/",
     6     // 输出文件目录
     7     outputDir: process.env.outputDir,
     8     devServer: {
     9         // 监听端口
    10         port: 8083,
    11         // 关闭主机检查,使微应用可以被 fetch
    12         disableHostCheck: true,
    13         // 配置跨域请求头,解决开发环境的跨域问题
    14         headers: {
    15             "Access-Control-Allow-Origin": "*",
    16         },
    17     },
    18     configureWebpack: {
    19         output: {
    20             library: `${packageName}`,
    21             libraryTarget: 'umd', // 把微应用打包成 umd 库格式
    22             jsonpFunction: `webpackJsonp_${packageName}`,
    23         },
    24     },
    25 };
    复制代码
      3.2.5 路由配置

      在微应用的路由配置文件中,需要增加主应用中配置时对应的路由根路径,同时区分是否为微服务,微服务情况下添加路由前缀,非微服务不需要添加。

    复制代码
     1 src/router.js
     2 const prefix = window.__POWERED_BY_QIANKUN__ ? '/home/micro-fronted-query' : ''
     3 const appRouters = [
     4     {
     5         path: `${prefix}/`,
     6         redirect: `${prefix}/login`
     7     },
     8     {
     9         path: `${prefix}/login`,
    10         name: 'login',
    11         meta: {
    12             title: 'Login - 登录'
    13         },
    14         component: () => import('./views/login.vue')
    15     },
    16     {
    17         path: `${prefix}/home`,
    18         component: () => import('./views/home.vue')
    19     },
    20     {
    21         path: `${prefix}/list`,
    22         component: () => import('./views/list.vue')
    23     }
    24 ]
    25 
    26 export default appRouters
    复制代码

      3.3 公共库

      除了主、微应用自身逻辑的处理之外,在这个方案当中,又创建了一个公共库,公共库主要能够保证主微应用样式兼容,同时能够对公用组件、方法等进行复用。该公共库同样基于vue进行开发,当然这里可以选择任意技术开发公共库。公共库应用名称为system-common。公共库,主要用于抽取各个应用之间公用的内容,包括系统公共路由配置、业务相关页面如登录、权限、用户协议组件,判断登录方式的api接口,cookie设置与获取的工具方法等。技术上,我们采用VUE框架,开发公共库,同时提供插件暴露的方式,供其他应用使用,vue如何开发插件会在后续文章中更新。公共库可以使用npm发私有包的形式使用,也可以使用yarn add file:/ 的模式,直接安装在本地项目中。

        
      3.3.1 公共库插件开发

      在src/index.js中,主要暴露插件方法,如,我们在components文件夹下创建了一个共用组件,通过在components/index.js中,使用Vue.component()方法,注册组件。同时将该注册过的组件,在index.js中进行安装。这样我们在其他应用中,只要安装了公共库,就可以使用Vue.use()方法进行使用组件了。

    复制代码
     1 // src/index.js文件
     2 const install = function(Vue) {
     3     if (install.installed) return;
     4     install.installed = true;
     5     // 遍历注册所有组件
     6     components.map((component) => Vue.use(component));
     7 }
     8 
     9 if (typeof window !== "undefined" && window.Vue) {
    10     install(window.Vue);
    11 }
    12 
    13 export default {
    14     install,  
    15     ...components, // 共用组件
    16     cookies,   // 公共cookies
    17     sysRouters,  // 通用路由
    18     judgeApi // 通用api方法
    19 };
    复制代码

      3.4 项目部署

      3.4.1 应用打包

      微应用是基于vue进行开发,严格按照vue提供的方式进行打包。

      首先在应用中创建.env.*文件,如.env.development.js文件代表的是开发环境下的打包配置,在该配置中,可以配置接口的访问地址,打包路径、node执行环境等。同时修改package.json中script执行脚本的命令,在命令中增加--mode模式。这样就可以根据不同配置进行不同环境的打包。除了通用打包方式外,还增加了动态微应用配置。这里在.env文件中,增加主应用接入的微应用地址配配置,然后在注册微应用的方法中动态获取,这样在注册微应用时,可以只修改配置文件中的内容就可以完成注册。

    复制代码
    1 // .env.development
    2 NODE_ENV = 'development'
    3 VUE_APP_MODE = 'development'
    4 outputDir = 'dev'
    5 VUE_APP_MICRO_DATA_MANAGE = '//localhost:9999/data/'
    6 VUE_APP_MICRO_DATA_QUERY = '//localhost:8083/query/'
    复制代码

         

      3.4.2 nginx部署

      项目部署,在本地测试时,主要采用了nginx进行静态资源重定向。将各个应用,打包之后,配置在nginx相同域名下,就可以实现cookie的数据共享。具体nginx部署,可查询nginx中文官网

      

      四、效果图

       

     




     

     




      

       






     

     

     

  • 相关阅读:
    vue里el-form+el-table实现验证规则的写法
    【动态规划】583. 两个字符串的删除操作、72. 编辑距离
    前端控制小数点精度及数字千位分割
    TwinCAT3库文件制作
    《微信小程序开发从入门到实战》学习二十四
    Rocky Linux Download
    WebRTC 如何指定 H265解码器
    Java项目:ssm图书商城系统
    Leetcode 2269. 找到一个数字的 K 美丽值(滑动窗口)
    要体验 AI 编程助手吗?
  • 原文地址:https://www.cnblogs.com/yinmochunCoder/p/15908686.html