qiankun—微前端初体验
一、qiankun介绍1、官网https://qiankun.umijs.org/zh/guideqiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。2、qiankun特性基于 single-spa 封装,提供了更加开箱即用的 API。技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/
一、微前端、qiankun介绍
1、微前端
(1)概念理解:
微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,解决中后台的“巨石项目”,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用可以独立运行、独立开发、独立部署。
(2)应用场景:
- 兼容遗留系统
经常会有团队需要在兼容已有系统的前提下,使用新框架去开发新功能。遗留系统功能已经完善,并且稳定运行,团队没有必要,也没有精力去将遗留系统重构一遍。此时团队如果需要使用新框架,新技术去开发新的应用,使用微前端是很好的解决方案。 - 应用聚合
大型互联网公司都会为用户提供很多应用和服务,如何为用户呈现具有统一用户体验的应用聚合成为必须解决的问题。而在大型商业公司内部,往往部署有大量的软件服务。如何为员工提供服务聚合,提供员工工作效率,成为企业内部IT建设的重中之重。前端聚合已成为一个技术趋势,目前比较理想的解决方案就是微前端。 - 团队间共享
不用应用之间往往存在很多可以共享和功能和服务,如果在团队之间进行高质量的共享成为提高研发效率的一条重要途径。微前端可以采用组件或者服务的方式进行团队间的技术共享。其低内聚高耦合的共享,使得高质量的共享成为可能。 - 局部/增量升级
一个大的产品由很多应用和服务组成,很多时候只需要对部分应用和服务进行升级。如果是单体应用,升级耗时长,风险高,影响可服务性。而前端可以只对需要的应用和服务进行升级,不会影响其他应用和服务。升级效率高,风险低,不影响其他应用和服务的可服务性。
(3)核心价值: - 技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权 - 独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新 - 增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略 - 独立运行时
每个微应用之间状态隔离,运行时状态不共享
2、qiankun
qiankun 是蚂蚁金服开源的一款框架,是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。已在蚂蚁内部服务了超过 200+ 线上应用,在易用性及完备性上,绝对是值得信赖的。
官网 https://qiankun.umijs.org/zh/guide
3、qiankun特性
- 基于 single-spa 封装,提供了更加开箱即用的 API。
- 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
4、qiankun使用
- 主应用安装qiankun,注册微应用,设置微应用挂载的dom节点
- 微应用导出生命周期钩子,配置打包工具
以上步骤即可完成一个微前端搭建。
5、qiankun常用API介绍
registerMicroApps(apps, lifeCycles?)
(1)apps
- Array - 必选,微应用的一些注册信息
RegistrableApp:name、entry 、container 、activeRule
(2)lifeCycles
- LifeCycles - 可选,全局的微应用生命周期钩子
LifeCycles:beforeLoad 、beforeMount 、afterMount 、beforeUnmount 、afterUnmountstart(opts?)
start(
{
prefetch:false, //取消预加载
sandbox:{
experimentalStyleIsolation :true //强制性的样式沙箱隔离
}
}
) //开启
setDefaultMountApp
设置主应用启动后默认进入的微应用。
import { setDefaultMountApp } from 'qiankun';
setDefaultMountApp('/vue');
initGlobalState(state)
定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。
示例:
主应用中的使用方法
import { initGlobalState, MicroAppStateActions } from 'qiankun';
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => { //在当前应用监听全局状态,有变更触发 callback
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
actions.setGlobalState(state); //按一级属性设置全局状态,微应用中只能修改已存在的一级属性
actions.offGlobalStateChange(); //移除当前应用的状态监听,微应用 umount 时会默认调用
微应用中的使用方法
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
props.setGlobalState(state);
}
二、主应用(基座)
1、安装qiankun
npm i qiankun -S
2、main.js注册微应用
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'vueApp', // 微应用名称,在微应用的打包配置文件中library的名称,微应用之间必须确保唯一
entry: '//localhost:10000',//微应用地址
container: '#vueDom',//微应用挂载的容器节点
activeRule: '/vue',//微应用的激活规则
props:{token:'gaiery-token-xxxx'} //主应用需要传递给微应用的数据
},
{
name: 'reactApp',
entry: '//localhost:20000',
container: '#reactDom',
activeRule: '/react',
},
]
registerMicroApps(apps);
start();
new Vue({
router,
render: h => h(App)
}).$mount('#app')
3、主应用中设置微应用挂载的容器节点。例如,挂载在App.vue中
<template>
<div>
<el-menu :router="true" mode="horizontal">
<!-- 基座中可以放自己的路由 -->
<el-menu-item index="/">home</el-menu-item>
<!-- 引用其他子应用 -->
<el-menu-item index="/vue">vue应用</el-menu-item>
<el-menu-item index="/react">react应用</el-menu-item>
</el-menu>
<router-view />
<!-- 两个微应用的容器节点 -->
<div id="vueDom"></div>
<div id="reactDom"></div>
</div>
</template>
三、微应用
微应用不需要额外安装任何其他依赖,只需要下面两步即可接入qiankun主应用:
1、导出相应的生命周期钩子
微应用需要在自己的入口js导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
vue微应用为例:main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
let instance = null
function render(props){
instance = new Vue({
router,
render: h => h(App)
}).$mount('#app') //这里是挂载到自己的html中,基座会拿到这个挂载后的html,将其插入进去
}
// qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,我们需要做的是在微应用的 main.js 的顶部添加如下代码:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
} // or此处我们也可以在 src 目录新增 public-path.js,在main.js中引入该文件(import './public-path';),内部代码内容和上述一样。
//如何独立运行微应用
if (!window.__POWERED_BY_QIANKUN__) { //不是在qiankun环境下的话,独立运行
render();
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('vue app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) { //props含有主应用传递进来的数据
render(props)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
instance.$destroy()
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
2. 配置微应用的打包工具
除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
vue.config.js
const packageName = require('./package.json').name;
module.exports = {
devServer:{
port:10000, //设置微应用端口号
headers:{
'Access-Control-Allow-Origin':'*'//允许跨域
}
},
configureWebpack:{
output:{
// library:'vueApp',
// libraryTarget:'umd'
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
}
}
}
3、微应用内部路由跳转
微应用建议使用 history 模式的路由。
微应用在内部进行路由跳转的时候,应该设置路由的base,也就是在router的index.js文件中,修改如下:
以上步骤即完成了主应用和子应用的配置,各自启动项目,浏览器访问效果如下:
访问主应用home时:
点击【vue应用】菜单,加载微应用vue项目:
点击微应用vue项目中的about菜单时:
四、主微应用之间简易传递数据方法
使用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) //渲染
}
五、主微应用都使用hash路由的情况
以上四个步骤都是history路由模式下的代码,如果主子应用都是hash路由的话,也可以,具体代码如下:
主应用
主应用activeRule注意加上#
;
主应用router里面注意base: process.env.BASE_URL
;
主应用router注意加上path: '/child/*'
的路由配置,以保证在子应用切换路由时能正常跳转。
1、router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/child/*',
name: 'About',
component: About,
},
]
const router = new VueRouter({
mode: 'hash', // 使用hash路由
base: process.env.BASE_URL, //设置base
routes
})
export default router
2、mian.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import {registerMicroApps,start} from 'qiankun'
Vue.config.productionTip = false
Vue.use(ElementUI);
const apps = [
{
name:'vueApp',
entry:'//localhost:10001',
container:'#vue',
activeRule:'#/child/vue',
props:{token:'zxp-token'}
},
{
name:'reactApp',
entry:'//localhost:20000',
container:'#react',
activeRule:'/react'
}
]
registerMicroApps(apps)//注册子应用
new Vue({
router,
render: h => h(App)
}).$mount('#app')
3、App.vue
<template>
<div>
<el-menu :router="true" mode="horizontal">
<!-- 基座中可以放自己的路由 -->
<el-menu-item index="/">home</el-menu-item>
<el-menu-item index="/child/vue">各项子应用</el-menu-item>
</el-menu>
<router-view />
</div>
</template>
4、About.vue,这次把挂载节点放在about页面了,用以测试子应用路由跳转是否好用
<template>
<div class="about">
<el-card class="box-card">
<h1>主应用about页面</h1>
<el-menu :router="true" mode="horizontal">
<!-- 引用其他子应用 -->
<el-menu-item index="/child/vue">vue应用</el-menu-item>
<el-menu-item index="/child/react">react应用</el-menu-item>
</el-menu>
<router-view />
<div id="vue"></div>
<div id="react"></div>
</el-card>
</div>
</template>
<script>
import {start} from 'qiankun'
export default {
mounted(){
if (!window.qiankunStarted) {
window.qiankunStarted = true
start()
}
}
}
</script>
子应用
子应用注意router里面的path
要加上主应用的前缀/child/vue
,且设置base: process.env.BASE_URL,
1、router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/child/vue',
name: 'Home',
component: Home
},
{
path: '/child/vue/aboutchild',
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: 'hash',
base: process.env.BASE_URL,
routes
})
export default router
2、App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/child/vue/">Home</router-link> |
<router-link to="/child/vue/aboutchild">About</router-link>
</div>
<router-view/>
</div>
</template>
展示子应用home页,浏览器显示效果如下:
跳转子应用about页,浏览器显示效果如下:
更多推荐
所有评论(0)