专栏首页EAWorld让前端走进微时代, 微微一弄很哇塞!

让前端走进微时代, 微微一弄很哇塞!

●●●

让前端走进微时代,

微微一弄很哇塞!

微前端这个词这两年很频繁的出现在大家的视野中,主要是把微服务的概念引入到了前端,让前端的多个模块或者应用解耦,做到让前端的子模块独立仓储,独立运行,独立部署。

下面是微前端的概念图。

为什么需要微前端?

1. 项目前端是单体应用即负责貌美如花还要赚钱养家,而后端使用微服务只需要负责分家家然后给前端送朵花花。前端单体承担了所有的接口。

2.系统模块增多,前端单体应用变得肥胖,开发效率低下,构建速度变慢。前端心想:哼,老娘就使劲的吃,浏览器也别想看见我,卡死你。

3.公司人员扩大,需要多个前端团队独立开发,独立部署,如果都在一个仓储中开发会带来一系列问题,例如:老子辛苦一天写的代码,第二天让别人弄没了。

4.解决遗留系统问题,新模块需要使用最新的框架和技术,旧系统还继续使用。

5.单体前端带来的测试问题。前端小姐姐分模块测试,这时正遇到构建发布,多人运动戛然而止。

6.编不下去了。。。。。。

微前端技术选型之路

目前关注度和成熟度最高的应该就是 single-spa,但是国内也有很多团队都有自己的微前端框架,像蚂蚁金服的qiankun,phodal 的 mooa,阿里飞冰的icestark,这些都是比较出名的微前端解决方案。但是这些框架都有一定的局限性,像mooa是针对Angular 打造的主从结构的微前端框架,icestark是最近才出的方案,而qiankun官网的开发文档就仅仅的几十行。本文将对qiankun进行几个方面讲解。

聊一聊qiankun

没错,qiankun正是光天化日,朗朗乾坤的乾坤。qiankun是一个开放式微前端架构,支持当前三大主流前端框架甚至jq等其他项目无缝接入。

qiankun是基于路由配置,适用于 route-based 场景,通过将微应用关联到一些 url 规则的方式,实现当浏览器 url 发生变化时,自动加载相应的微应用的功能。

qiankun在Vue项目中的使用

1、微前端构建

构建主应用

1、使用vue脚手架初始化项目或者原有Vue项目

2、npm install qiankun --save下载qiankun微前端方案依赖

3、改造主项目,很简单,在src创建一个qiankun文件夹,创建init.js文件,文件内容:

import {
  registerMicroApps, // 注册子应用方法
  setDefaultMountApp, // 设默认启用的子应用
  runAfterFirstMounted, // 有个子应用加载完毕回调
  start, // 启动qiankun
  addGlobalUncaughtErrorHandler, // 添加全局未捕获异常处理器
} from "qiankun";
function genActiveRule(routerPrefix) {//路由监听
return location => location.pathname.startsWith(routerPrefix);
}
export default function startQiankun() {
const apps = [//子应用配置
    {
name: 'admin',
entry: "//localhost:9528",
container:'#admin',//将子应用节点挂载到父应用定义id=admin的div上
      activeRule: genActiveRule('/admin'),//路由前缀
      props: {mag:”我是主应用”} //父应用给子应用传参
    }
  ]
//注册子应用
  registerMicroApps(apps, {
beforeLoad: [//子应用加载生命周期,下同
      app => {
console.log("before load", app);
      }
    ],
beforeMount: [
app => {
console.log("before mount", app);
      }
    ],
afterUnmount: [
app => {
console.log("after unload", app);
      }
    ]
  })
// 设置默认子应用
//   setDefaultMountApp('/admin');
// 第一个子应用加载完毕回调
  runAfterFirstMounted((app) => {
console.log(app)
  });
// 启动微服务
  start({
prefetch: 'all'//预加载子应用静态资源
  });
// 设置全局未捕获一场处理器
  addGlobalUncaughtErrorHandler(event => console.log(event));
}

(左右滑动查看全部代码)

4、在App.vue中加入<div id="admin"></div>即可

<template>
<div class="app-wrapper" :class="classObj">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"></div>
<sidebar  v-if="!isFullModel" class="sidebar-container"></sidebar>
<div class="main-container" :style="isFullModel?'margin-left:0px;':''">
<navbar></navbar>
<tags-view v-if="!isFullModel"></tags-view>
<div id="admin" :style="`height:calc(100vh - ${isFullModel ? 54: 84}px);`"></div>//子应用盒子
</div>
</div>
</template>

(左右滑动查看全部代码)

5、最后将init.js在main.js引入

import startQiankun from './qiankun/init/index'
let app = null
async function render({ appContent, loading } = {}) {
  if (!app) {
    await ReadConfig(Vue)//加载配置文件
    app = new Vue({
      router,
      store,
      i18n,
      render: h => h(App),
    }).$mount('#app')
  } else {
    // app.content = appContent;
    // app.loading = loading;
    console.log(loading, appContent)
  }
}
render()
startQiankun()

(左右滑动查看全部代码)

构建子应用

1、使用vue脚手架初始化项目

2、在main.js中加入:

let router = null;
let instance = null;
if (window.__POWERED_BY_QIANKUN__) { 
  //处理资源
   __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 
  }
//下面的 /admin  与主应用  registerMicroApps 中的  activeRule 字段对应
function render(props = {}) {
  const {container} = props
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/admin' : '/',
    mode: 'history',
    routes,
  });
  const create = async () => {
    // await ReadConfig(Vue)
    instance = new Vue({
      router,
      render: h => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app');
  }
  create()
}
if (!window.__POWERED_BY_QIANKUN__) {//判断是否在qiankun环境下
  render();
}
// 导出子应用生命周期 挂载前
export async function bootstrap(props) {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {// 导出子应用生命周期 挂载前 挂载后
  render(props);
}// 导出子应用生命周期 挂载前 卸载后
export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

(左右滑动查看全部代码)

3、创建vue.config.js文件,需要做一些打包配置。

const path = require('path');
const packageName = require('./package').name;
function resolve(dir) {
  return path.join(__dirname, dir);
}
const dev = process.env.NODE_ENV === 'development'
const port = 9528; // dev port
module.exports = {
  publicPath: dev ? `//localhost:${port}` : '/',
  outputDir: 'dist',
  assetsDir: 'static',
  filenameHashing: true,
  devServer: {
    hot: true,
    historyApiFallback: true,
    port,
    overlay: {
      warnings: false,
      errors: true,
    },
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  // 自定义webpack配置
  configureWebpack: {
    resolve: {
      alias: {
        '@': resolve('src'),
      },
    },
    output: {// 把子应用打包成 umd 库格式
      library: `${packageName}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${packageName}`,
      globalObject: 'this',
    },
  },
};

(左右滑动查看全部代码)

2、微前端解决的问题

自此,一个父子前端微应用搭建成功,当然这只是简单的vue实现,真正复杂的是一个项目中每个模块使用不同的前端框架。效果如下:

我们可以看到A模块、B模块和C模块三个微应用分别运行在Vue、React和Angular的环境中,而主应用主要提供了NavBar和SideBar的界面。中心是微应用中组件显示的界面。

我们从图中可以看出将前端微服务化后解决了什么问题:

1、当A模块需要维护时,只需要动A模块的代码,维护完成之后对A项目进行构建发布,由于体积很小,构建发布就很快。

2、当A项目构建发布时,其他应用运行正常,避免了之前单体应用构建发布导致整个前端项目无法访问。

3、主应用可以捕捉到子应用的运行状态,如果子应用出现异常或者挂掉,可以对用户进行友好提示。例如当A模块挂掉后,用户点击其功能一将提示“功能正在维护中”。

4、由于子应用运行的环境不同,项目可以加入各种各样的技术栈,解决了前端人员因为技术栈不同无法聚合的问题。

5、每个子项目拥有独自的仓储,代码易维护,并且可以用到别的项目中,这样一来,每个微前端又可以作为一个微应用提供服务。

3、微前端开发中需要解决的问题

父子应用通信

父向子通信(Shared 通信方案)

Shared 通信方案的原理就是,主应用基于 Vuex维护一个状态池,通过 shared 实例暴露一些方法给子应用使用。同时,子应用需要单独维护一份 shared 实例,在独立运行时使用自身的 shared 实例,在嵌入主应用时使用主应用的 shared 实例,这样就可以保证在使用和表现上的一致性。

1、在主应用中创建shared.js,可以将需要给子应用传的数据通过getter和setter的方式设置。

import store from "../../store";
class Shared {
//获取 Token
  getToken() {
const state = store.state;
return state.user.token || "";
  }
  setToken(token) {
// 将 token 的值记录在 store 中
    store.commit('SET_TOKEN',token);
  }
}
const shared = new Shared();
export default shared;

(左右滑动查看全部代码)

2、在init.js注册的子应用中,将shared传递过去

const apps = [
  {
    name: 'admin',
    entry: "//localhost:9528",
    container:'#admin',
    activeRule: genActiveRule('/admin'),
    props: {
      shared,//将数据以类的方式传递
    }
  }
]

(左右滑动查看全部代码)

3、当主应用登录成功之后可以通过调用shared.setToken(token)设置token,然后主应用通过自身路由跳转到主页this.$router.push({ path: '/' })。

4、现在,我们来处理子应用需要做的工作。我们希望子应用有独立运行的能力,所以子应用也应该实现 shared,以便在独立运行时可以拥有兼容处理能力。代码实现如下:

class Shared {
 //获取 Token
 getToken() {
    // 子应用独立运行时,在 localStorage 中获取 token
    return sessionStorage.getItem("token") || "";
  }
 setToken(token) {
    // 子应用独立运行时,在 localStorage 中设置 token
    sessionStorage.setItem("token", token);
  }
 }
 class SharedModule {//通过SharedModule 来维护shared
  static shared = new Shared();
  static overloadShared(shared) {
    SharedModule.shared = shared;
  }
  static getShared() {
    return SharedModule.shared;
  }
}
 export default SharedModule;

(左右滑动查看全部代码)

5、接下来我们只需要在子应用的入口文件接受父应用传来的shared,然后设置到SharedModule 中。

export async function bootstrap(props) {
  const {shared = SharedModule.getShared() } = props;
  SharedModule.overloadShared(shared);
}

(左右滑动查看全部代码)

6、然后子应用就可以在组件中通过SharedModule获取shared然后得到token,最后发起网络请求。

mounted () {
const shared = SharedModule.getShared();
const token = shared.getToken();
let resp =await this.dispatch(AppController.findById, {id: this.currentApp.appId})//封装之后的网络请求
...
}

(左右滑动查看全部代码)

子向父通信(emit通信)

emit通信的原理是子应用通过触发父应用传递的函数来改变父应用vuex中维护的状态,进而达到子应用想父应用的通信。这里以子应用向父应用发送token失效,让父应用跳转至登录页的场景。

1、父应用定制提供子应用触发token注销的函数

import store from "../../store";
import { removeToken } from '@/utils/auth'
function logout(childThis) {
  removeToken()
  childThis
    .$confirm('登录信息已过期!', '提示', {
      confirmButtonText: '重新登录',
      type: 'warning',
      center: true,
      showClose: false,
      showCancelButton: false,
      closeOnClickModal: false,
    })
    .then(() => {
      store.dispatch('FedLogOut')
    })
}
export {
  logout
}

(左右滑动查看全部代码)

2、将父应用定制的函数传递至子应用

const apps = [
  {
    name: 'admin',
    entry: "//localhost:9528",
    container:'#admin',
    activeRule: genActiveRule('/admin'),
    props: {
      shared,
      emitFnc: childEmit,//将上述的函数传递子应用
    }
  }
]

(左右滑动查看全部代码)

3、子应用获取并注册到子应用的全局变量中

export async function bootstrap(props) {
  const {emitFnc , shared = SharedModule.getShared() } = props;
  SharedModule.overloadShared(shared);
  Object.keys(emitFnc || {}).forEach(i => {
    Vue.prototype[`$${i}`] = emitFnc[i]
  });
}

(左右滑动查看全部代码)

4、子应用通过在组件中调用this.$logout(this)即可完成通信。

公共资源共享

项目中存在大量的公共资源,例如公共方法,公共组件,公共UI。在开发的时候不可能每个项目都复制一遍,这样既降低了开发效率,同时项目的体积增大,构建速度变慢。所以对于微前端项目,我们需要定制一套合适的方案将公共资源抽离出来,子应用可以在运行期动态获取到资源并加以使用,就像maven的顶级pom,将公共的jar抽离了出来。

1、这里以公共组件为例,将定义好的公共组件放置主应用的文件夹中并导出,创建一个js文件专门作为公共组件。

import InputEditor from './input-editor/src' //自定义公共组件
import ClipButton from './clip-button/src' //自定义公共组件
const components = [InputEditor,ClipButton];
const install = function (Vue) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};
if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue);
}
export default {
  install,
};

(左右滑动查看全部代码)

2、同样的,将此文件作为参数传递至子应用。

const apps = [
  {
    name: 'admin',
    entry: "//localhost:9528",
    container:'#admin',
    activeRule: genActiveRule('/admin'),
    props: {
      shared,
      emitFnc: childEmit,
      components: Components//公共组件
    }
  }
]

(左右滑动查看全部代码)

3、子应用接收到参数后,将组件注册到自身的项目中

export async function bootstrap(props) {
  const {emitFnc ,components, shared = SharedModule.getShared() } = props;
  SharedModule.overloadShared(shared);
  Vue.use(components)  //注册组件
  Object.keys(emitFnc || {}).forEach(i => {
    Vue.prototype[`$${i}`] = emitFnc[i]
  });
}

(左右滑动查看全部代码)

4、子应用可以在任何组件中使用父应用传递的组件。需要注意的是,使用公共组件的名称要和注册组件的名称保持一致,其他公共js类似。

通过父子间通信,我们大致可以了解到项目中如何实现鉴权,因为前端鉴权是一个难点,在微前端中鉴权方案有很多种,有兴趣的小伙伴可以尝试着去实现自身项目中的鉴权方案。

总结

qiankun,意为统一。通过 qiankun 这种技术手段,让你能很方便的将一个巨石应用改造成一个基于微前端架构的系统,并且不再需要去关注各种过程中的技术细节,做到真正的开箱即用和生产可用。

关于作者:卜壮,普元前端开发工程师,负责Mobile 8.0项目管理平台前端部分。熟悉ReactNative,目前正在学习Vue,大前端技术探求者。

本文分享自微信公众号 - EAWorld(eaworld),作者:卜壮

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 全网首发:逐一解读云原生应用开发“12-Factors”

    作者自序: 12原则的提出已有五年之久,可惜业界一直缺乏一篇对其进行简明解读的指导性文章,所以我决定写这样一篇文章。在微服务模式的大背景下,力求对12原则的来龙...

    yuanyi928
  • DevOps驱动的人保微服务平台建设之路

    2018年,我们在人保寿险进行了微服务平台建设。针对保险行业,微服务建设有哪些需求,我们又是如何应用DevOps理念的,本文我就和大家分享一下我们在人保寿险的微...

    yuanyi928
  • 微服务模式系列之一:整体式架构

    译者自序: 熟悉我的朋友都知道,我很不喜欢翻译东西,因为在两种语言的思维方式之间做频繁切换对我来说是件很痛苦的事情。但是这次不一样,公司和同事的大力支持降低了我...

    yuanyi928
  • 10款你可能不知道的Android开发辅助工具

    1、XAppDbg XAppDbg是一个可以在运行中改变代码中参数的一个应用开发工具。这个工具可以为你省下大量的时间,因为你不用为应用的每次小改变而重新编译运...

    非著名程序员
  • DevOps驱动的人保微服务平台建设之路

    2018年,我们在人保寿险进行了微服务平台建设。针对保险行业,微服务建设有哪些需求,我们又是如何应用DevOps理念的,本文我就和大家分享一下我们在人保寿险的微...

    yuanyi928
  • 你可能不知道的10款Android开发辅助工具

    1XAppDbg XAppDbg是一个可以在运行中改变代码中参数的一个应用开发工具。这个工具可以为你省下大量的时间,因为你不用为应用的每次小改变而重新编译运行你...

    非著名程序员
  • app开发学习需要经历哪些流程

      app开发学习需要经历哪些流程?如何零基础入门app开发?以下是知乎热心开发者的经验总结,对学习app开发有很好的参考意义 1.如果没有编程基础的,学习基础...

    ytkah
  • 英伟达官宣:CUDA 将不再支持 macOS

    内容提要:或许,今后我们再也看不到搭载英伟达显卡的新款苹果电脑了。英伟达在最近的一份说明文档中宣布,将停止 CUDA 对 macOS 的驱动支持。这意味着,苹果...

    HyperAI超神经
  • Cloudsight推出图像识别API,免费开放给教育机构

    如果自己研发做图像识别的成本比较高,尤其是在没有一个很好的硬件设施(GPU)的情况下,还是通过API比较合适。 ? 计算机科学学位的技术往往要落后于现实。许多学...

    BestSDK
  • 支付宝支付-常用支付API详解(查询、退款、提现等)

    Maven项目引用JAR包可以参考 支付宝Wap支付你了解多少? 里面有详细的介绍

    Javen

扫码关注云+社区

领取腾讯云代金券