Vue.js - 微前端框架 qiankun 的使用详解(附样例)
作者:hangge | 2021-11-16 08:10
一、微前端介绍
1,什么是微前端?
(1)微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

(2)微前端的诞生主要为了解决如下两个问题:
- 复用(嵌入)别人的项目页面,但是别人的项目运行在他自己的环境之上。
- 将原先的一个巨无霸应用拆分成一个个的小项目,这些小项目既可以独立开发部署,又可以自由组合。
2,使用微前端的好处
- 技术栈无关,各个子项目可以自由选择框架,可以自己制定开发规范。
- 快速打包,独立部署,互不影响,升级简单。
- 可以很方便的复用已有的功能模块,避免重复开发。
3,目前微前端主要的解决方案
(1)iframe 方案
- iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。
- 但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享(主要是本地存储、全局变量和公共插件),两个项目不同源(跨域)情况下数据传输需要依赖 postMessage,随之带来的开发体验、产品体验的问题。
(1)具体来说,使用 iframe 有如下常见的问题:
(2)其中有的问题比较好解决(问题 1),有的问题我们可以睁一只眼闭一只眼(问题 4 ),但有的问题我们则很难解决(问题 3)甚至无法解决(问题 2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题。
- url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
- 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
- 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
(2)single-spa 方案
- Single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架(GitHub 主页)。简单来说就是将子项目的内容(包括容器、js)插入到主项目,从而呈现出子项目的内容。
- 相对于 iframe,single-spa 让父子项目属于同一个 document,这样做既有好处,也有坏处。好处就是数据/文件都可以共享,公共插件共享,子项目加载就更快了,缺点是带来了 js/css 污染。
- single-spa 上手并不简单,也不能开箱即用,开发部署更是需要修改大量的 webpack 配置,对子项目的改造也非常多。
二、qiankun 介绍
1,什么是 qiankun?
qiankun 是一个基于 single-spa 的微前端实现库,孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台。它在 single-spa 的基础上,实现了开箱即用,除一些必要的修改外,子项目只需要做很少的改动,就能很容易的接入。
- 官方网站:https://qiankun.umijs.org/zh/
- Github 主页:https://github.com/umijs/qiankun
2,qiankun 主要特性
- 基于 single-spa 封装,提供了更加开箱即用的 API。
- 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- HTML Entry 接入方式,让我们接入微应用像使用 iframe 一样简单。
- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
三、使用样例
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,导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
注意: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)