Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端架构具备以下几个核心价值:
主框架不限制接入应用的技术栈,微应用具备完全自主权
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
每个微应用之间状态隔离,运行时状态不共享
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。
目前 qiankun 已在蚂蚁内部服务了超过 2000+ 线上应用,在易用性及完备性上,绝对是值得信赖的。
简单
由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
解耦/技术栈无关
微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
基于 single-spa 封装,提供了更加开箱即用的 API。
技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
样式隔离,确保微应用之间样式互相不干扰。
JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
App.vue中建立一个element-ui的菜单用来跳转:
Home
vue应用
安装qiankun
npm i qiankun -S
main.js中初始化
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI);
// 引入qiankun 的注册应用方法 / 启动应用方法
import { registerMicroApps, start } from 'qiankun'
// 定义一个列表 写入要注册的app
const apps = [{
name: 'vueApp', //微应用名称,在微应用的打包配置文件中library的名称,微应用之间必须确保唯一
entry: 'http://localhost:8081', //微应用地址,子应用必须支持跨域 fetch
container: '#vue', //微应用挂载的容器节点
activeRule: '/vue', //微应用的激活规则:访问到/vue的时候跳转子应用
props:{token:'gaiery-token-xxxx'} //主应用需要传递给微应用的数据
}]
// 注册app
registerMicroApps(apps)
// 开启
start()
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
router/index.js:
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',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
因为在基座的main.js中配置了访问到/vue的时候就挂载子应用,所以就要修改子应用路由的根路径 router/index.js:
const router = new VueRouter({
mode: 'history',
base: '/vue',
routes
})
然后我们要把子应用打包成一个类库,在main.js中配置:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
//定义一个变量接收vue的实例
let instance = null
function render() {
// 这里是挂载到自己的html上
// 基座会拿到挂载后的html 插入到#vue这个容器中
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
}
//如果是基座访问子应用 (qiankun访问子应用)
if (window.__POWERED_BY_QIANKUN__) {
//就动态配置子应用的自身的根路径 ( 避免使用到基座的根路径 )
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
//如果子应用是独立运行的话 就直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 子组件的协议(固定) 导出三个promise函数
export async function bootstrap(props) {
//应用初始化的时候调用
}
export async function mount(props) {
//每次进入子应用的时候调用
//子应用渲染的时候挂载 也可以把props传入子应用进行通讯
render(props)
}
export async function unmount(props) {
//切出 / 卸载的时候会调用
// 子应用卸载的时候用destory把实例卸载了
// instance.$destory()
}
配置完成后需要打包成类库,所以还得配置一下打包vue.config.js,因为子应用嵌套在父应用下,所以还需要配置跨域,还要配置打包后的结果:
module.exports = {
devServer: {
port: 8081, // 配置端口号
headers: {
// 配置跨域 表示所有的人都可以访问我
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
library: "vueApp",
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
}
}
基本的接入已经完成
注意:基座和子应用的router mode相同,都为“history”
const router = new VueRouter({
mode: 'history',
routes
})
使用props传递数据
在主应用的main.js的registerMicroApps(apps)中 props:{token:'gaiery-token-xxxx'} //主应用需要传递给微应用的数据
在微应用的main.js的mount方法中
export async function mount(props){
console.log(props) //props中携带主应用传递过来的token:gaiery-token-xxxx
render(props) //渲染
}