当前位置: > > > Vue.js - 微前端框架 qiankun 的使用详解(附样例)

Vue.js - 微前端框架 qiankun 的使用详解(附样例)

一、微前端介绍

1,什么是微前端?

(1)微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

(2)微前端的诞生主要为了解决如下两个问题:
  • 复用(嵌入)别人的项目页面,但是别人的项目运行在他自己的环境之上。
  • 将原先的一个巨无霸应用拆分成一个个的小项目,这些小项目既可以独立开发部署,又可以自由组合。

2,使用微前端的好处

  • 技术栈无关,各个子项目可以自由选择框架,可以自己制定开发规范。
  • 快速打包,独立部署,互不影响,升级简单。
  • 可以很方便的复用已有的功能模块,避免重复开发。

3,目前微前端主要的解决方案

(1)iframe 方案
  • iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。
  • 但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享(主要是本地存储、全局变量和公共插件),两个项目不同源(跨域)情况下数据传输需要依赖 postMessage,随之带来的开发体验、产品体验的问题。
(1)具体来说,使用 iframe 有如下常见的问题:
  • url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  • UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
  • 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
  • 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
(2)其中有的问题比较好解决(问题 1),有的问题我们可以睁一只眼闭一只眼(问题 4 ),但有的问题我们则很难解决(问题 3)甚至无法解决(问题 2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题。

(2)single-spa 方案
  • Single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架(GitHub 主页)。简单来说就是将子项目的内容(包括容器、js)插入到主项目,从而呈现出子项目的内容。
  • 相对于 iframesingle-spa 让父子项目属于同一个 document,这样做既有好处,也有坏处。好处就是数据/文件都可以共享,公共插件共享,子项目加载就更快了,缺点是带来了 js/css 污染。
  • single-spa 上手并不简单,也不能开箱即用,开发部署更是需要修改大量的 webpack 配置,对子项目的改造也非常多。

二、qiankun 介绍

1,什么是 qiankun?

    qiankun 是一个基于 single-spa 的微前端实现库,孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台。它在 single-spa 的基础上,实现了开箱即用,除一些必要的修改外,子项目只需要做很少的改动,就能很容易的接入。

2,qiankun 主要特性

  • 基于 single-spa 封装,提供了更加开箱即用的 API
  • 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
  • HTML Entry 接入方式,让我们接入微应用像使用 iframe 一样简单。
  • 样式隔离,确保微应用之间样式互相不干扰。
  • JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
  • umi 插件,提供了 @umijs/plugin-qiankunumi 应用一键切换成微前端架构系统。

三、使用样例

1,主应用开发

(1)主应用也就是整个微前端的基座应用,我们会将各个微应用加载到主应用中展示。这里我们创建一个 Vue 项目作为主应用,创建后执行如下命令安装 qiankun
npm i qiankun -S

(2)主应用需要提供一个容器 DOM,用于插入加载的微应用。这里我们将 App.vue 修改成如下内容:
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/app1">App1</router-link> |
      <router-link to="/app2">App2</router-link>
    </div>
    <div id="container"/>
  </div>
</template>

(3)然后在 main.js 中注册微应用并 start 即可。
提示:当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

// 引入 qiankun 组件
import { registerMicroApps, start } from 'qiankun';

// 注册微应用信息
registerMicroApps([
  {
    name: 'hangge-app-1',
    entry: 'http://localhost:9091',
    container: '#container',
    activeRule: '/app1',
  },
  {
    name: 'hangge-app-2',
    entry: 'http://localhost:9092',
    container: '#container',
    activeRule: '/app2',
  }
]);
// 启动 qiankun
start();

  • 当然微应用也不是必须要跟路由关联,我也可以选择手动加载微应用的方式:
import { loadMicroApp } from 'qiankun';

loadMicroApp({
  name: 'hangge-app-1',
  entry: 'http://localhost:9091',
  container: '#container',
});

2,微应用开发

(1)首先在微应用的 src 目录下新增 public-path.js 文件,内容如下:
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

(2)接着修改 main.js 文件,在最顶部引入 public-path.js,导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用。
注意mount 时,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。
import './public-path';
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

let instance = null;

// 页面渲染方法
function render(props = {}) {
  const { container } = props;
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时就直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

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

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}

(3)修改 router/index.js,设置路由 base,值和该微应用在主应用那边的 activeRule 是一样的。
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

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

const router = new VueRouter({
  mode: 'history',
  base: window.__POWERED_BY_QIANKUN__ ? '/app1/' : process.env.BASE_URL,
  routes
})

export default router

(4)最后编辑项目根目录下的 vue.config.js 文件(如果没有则手动创建一个),修改 webpack 打包,允许开发环境跨域和 umd 打包。
const { name } = require('./package');
module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*', //允许开发环境跨域
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

(5)经过上面修改后访问可能会报“error  '__webpack_public_path__' is not defined”错误。这个是 eslint 的问题,webpack_public_path 不是全局变量所导致的。我们在 package.json 文件中 eslintConfig 配置全局变量后重起服务即可解决。
  },
  "eslintConfig": {
    "globals": {
      "__webpack_public_path__": true
    }
  }
}

3,运行效果 

(1)启动项目,如果我们直接访问微应用效果如下:

(2)访问主应用效果如下:

(3)点击主应用里的链接进行路由跳转,可以发现对应的微应用便成功加载进来了。

(4)微应用内的路由跳转也是完全正常的:
评论0