官网地址:qinkun
📄前言
总结:我们最终目的为了干什么
- 产品在迅速增长,模块内容越来越多,项目构建和启动速度越来越慢。
- 在开发过程中,由于缺少业务场景磨炼,组件跌代比较频繁,导致部分出现需要重构,改造需要大量时间。
- 为了公司产品化道路,做一个技术预研。
- 最近公司开拓市场,大多是在老项目上面进行维护。例如:将A项目中某个模块,B中
# 基础阶段
介绍: 业务场景需要,一种解决方案。
第一阶段: 根据官网,实现运行简单MVP项目
第二阶段: 主应用和子应用如何通讯,以及目前通讯解决方案有哪几种?
第三阶段: 如何部署实践
# 进阶阶段
第四阶段: 如何改造项目侧边栏、导航栏、登录页?
第五阶段: 微前端项目如何鉴权,如何将子应用路由,主应用的菜单之间实现共用
第六阶段: 微前端公共资源加载,如何后端管理系统的系统
主应用
承担那些功能
- 导航的渲染和登录态的下发
- 子应用提供一个挂载的容器div
- 基座应该保持简洁,不应该做涉及业务的操作
子应用
切换加载数据?
进阶
全局状态管理
一个发布-订阅的设计模式
全局状态管理
qiankun通过initGlobalState, onGlobalStateChange, setGlobalState实现主应用的全局状态管理,然后默认会通过props将通信方法传递给子应用。先看下官方的示例用法:
1. 主应用的状态封装
2. vue子应用的状态封装
子应用切换Loading处理
🎉阶段计划
1️⃣第一阶段:如何运行,做一个简单MVP项目
1、初始化仓库
xianzhi-cli 进行添加基础框架一个模板
npm init -y
xianzhi init vue-mini main-app
xianzhi init vue-mini xianzhi-client # 客户端
2、修改配置
# 整体代码库中package.json
# npm-run-all,并行或者连续,运行多个npm脚本的CLI工具
"scripts": {
"dev-all": "npm-run-all --parallel dev:*",
"install-all": "npm-run-all --serial install:*",
"build-all": "npm-run-all --parallel build:*",
"install:micro": "npm install",
"install:main": "cd micro-app-main && npm install",
"dev:main": "cd micro-app-main && npm run dev",
"install:client": "cd xianzhi-client && npm install",
"dev:client": "cd xianzhi-client && npm run dev",
"test": "echo "Error: no test specified" && exit 1"
}
备注:主应用端口好不要设置9000 90001 100001 10001这种情况的,不能正常运行
目前测试9527 9528 可行,坑!
3、主应用修改
public文件夹index.html
修改vue项目构建好内容插入点,只是改一下名称,避免子应用挂载重复
<body>
<div id="main-app"></div>
<!-- 避免子应用挂载重复 -->
<!--<div id="app"></div>-->
<!-- built files will be auto injected -->
</body>
main.js(原来内容可不修改)
将router实例迁移到main.js。这一步可以不做,无伤大雅
子应用确实需要迁移,后续会讲述为什么放在main.js
const router = new Router({
mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
micro代码逻辑
前面都是做的铺垫,有请qiankun登场
npm install -D qiankun # v1.x 和v2.0 部分api改动,注意留一下官网文档
registerMicroApps, // 注册子应用方法
setDefaultMountApp, // 设默认启用的子应用
runAfterFirstMounted, // 有个子应用加载完毕回调
start, // 启动qiankun
addGlobalUncaughtErrorHandler, // 添加全局未捕获异常处理器
initGlobalState, // 官方应用间通信
# src/micro/index.js
import {
registerMicroApps,
addGlobalUncaughtErrorHandler,
start
} from 'qiankun'
// 子应用注册信息
const apps = [
/**
* name: 微应用名称 - 具有唯一性
* entry: 微应用入口 - 通过该地址加载微应用,这里我们使用 config 配置
* container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
* activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
*/
{
name: 'ClientMicroApp',
entry: '//localhost:9527',
container: '#frame',
activeRule: '/client'
}
]
/**
* 注册子应用
* 第一个参数 - 子应用的注册信息
* 第二个参数 - 全局生命周期钩子
*/
registerMicroApps(apps, {
// qiankun 生命周期钩子 - 加载前
beforeLoad: app => {
// 加载子应用前,加载进度条
console.log('before load', app.name)
return Promise.resolve()
},
// qiankun 生命周期钩子 - 挂载后
afterMount: app => {
// 加载子应用前,进度条加载完成
console.log('after mount', app.name)
return Promise.resolve()
}
})
/**
* 添加全局的未捕获异常处理器
*/
addGlobalUncaughtErrorHandler(event => {
console.error(event)
const { message: msg } = event
// 加载失败时提示
if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
console.log('子应用加载失败,请检查应用是否可运行')
}
})
// 导出 qiankun 的启动函数
export default start
# main.js
import startQiankun from './micro/index.js'
startQiankun()
#vue.config.js 部分代码
module.exports = {
devServer: {
port: 9999,
open: true,
disableHostCheck: true
}
}
App.vue
保留改造系统之前路由方式,减少对子应用的改造,当个子应用也能独立运行
# App.vue
<div id="main-app">
<layout />
<!--<router-view />-->
</div>
菜单 layout/siderbar/index.vue
主应用显示的菜单,当前主应用菜单 + 子应用菜单进行拼接
#\src\layout\components\Sidebar\index.vue
permission_routes: constantRoutes.concat(appsMains)
4、子应用修改
vue.config.js
devServer: {
// 监听端口
port: 10200,
// 关闭主机检查,使微应用可以被 fetch
disableHostCheck: true,
// 配置跨域请求头,解决开发环境的跨域问题
headers: {
'Access-Control-Allow-Origin': '*'
}
},
添加public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// 动态设置 webpack publicPath,防止资源加载出错
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
main.js
function render(props) {
router = new Router({
base: window.__POWERED_BY_QIANKUN__ ? '/client' : '/',
mode: 'history', // require service support
routes: constantRoutes
})
instance = new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
}
// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('VueMicroApp bootstraped')
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log('VueMicroApp mount', props)
render(props)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log('VueMicroApp unmount')
instance.$destroy()
instance = null
router = null
}
看到这里是不是感觉很简单,几个简单API调用一下实现了😊
2️⃣第二阶段: 主应用和微应用如何通讯,通讯方式
解读当前微前端通讯方式,对三种通讯方式优缺点?
官方initGlobalState
主应用修改
# shared/action.js
import { initGlobalState } from 'qiankun'
const initialState = {}
const actions = initGlobalState(initialState)
export default actions
// 注册一个观察者函数
actions.onGlobalStateChange((state, prevState) => {
// state: 变更后的状态; prevState: 变更前的状态
console.log('主应用观察者:token 改变前的值为 ', prevState.token)
console.log('主应用观察者:登录状态发生改变,改变后的 token 的值为 ', state.token)
})
# 进行设置传递
actions.setGlobalState({ token })
子应用修改
# mian.js中进行获取
function render(props) {
if (props) {
// 注入 actions 实例
actions.setActions(props)
}
....
}
rxjs
基于 qiankun 的微前端最佳实践(图文并茂) - 应用间通信篇
redux、mobx
3️⃣第三阶段: 如何部署实施
nginx部署
# 配置文件
vim /etc/nginx/nginx.conf
systemctl reload nginx # 重载
# nginx配置文件
server {
listen 9001;
location / {
add_header 'Access-Control-Allow-Origin' "*" always;
add_header 'Access-Control-Allow-Headers' "Content-Type" always;
add_header 'Access-Control-Allow-Credentials' "true" always;
root /root/micro-app/micro-app-main/;
index index.html index.htm;
}
location / {
add_header 'Access-Control-Allow-Origin' "*" always;
add_header 'Access-Control-Allow-Headers' "Content-Type" always;
add_header 'Access-Control-Allow-Credentials' "true" always;
root /root/micro-app/micro-app-main/;
index index.html index.htm;
}
}
server {
listen 9002;
location / {
add_header 'Access-Control-Allow-Origin' "*" always;
add_header 'Access-Control-Allow-Headers' "Content-Type" always;
add_header 'Access-Control-Allow-Credentials' "true" always;
root /root/micro-app/micro-app-react/;
index index.html index.htm;
}
}
server {
listen 9003;
location / {
add_header 'Access-Control-Allow-Origin' "*" always;
add_header 'Access-Control-Allow-Headers' "Content-Type" always;
add_header 'Access-Control-Allow-Credentials' "true" always;
root /root/micro-app/micro-app-vue/;
index index.html index.htm;
}
QA
1、页面如何跳转?
登录问题--子应用独立运行
1.主应用
import { registerMicroApps, start,setDefaultMountApp, initGlobalState } from 'qiankun';
let apps = [
// 一句话总结 当匹配到路由是activeRule时, 会请求entry的资源,渲染到container上去
{
name: 'MUYE', // app name registered
entry: '//10.100.172.52:9527', //子应用的入口 ,单独启动子应用的那个链接
container: '#qiankun', //要挂在的节点
activeRule: '/workbench/muye', //路由的匹配规则
},
]
// 通讯
const actions = initGlobalState({
mt: 'stateValue' // 初始化state值
})
actions.onGlobalStateChange((state,prev)=>{
console.log('main state change',state);
})
// 将action对象绑到Vue原型上,为了项目中其他地方使用方便
Vue.prototype.$actions = actions
2.修改state的方式
this.$actions.setGlobalState({
test: value,
});
3.子应用
//生命周期函数
async mount(props) {
// 注册应用间通信 props是基座传输过来的值,可进行token等存储使用
// appStore(props)// 存储例子1 可作参考
Vue.prototype.$MicroMount = props
// 设置应用间通讯(以下两行代码)
Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange
Vue.prototype.$setGlobalState = props.setGlobalState
// Cookies.set(props,'$MicroMount')
// 注册微应用实例化函数
render(props)
},
动态加载路由思想
blog.csdn.net/BLUE_JU/art…
动态清除路由
import Vue from 'vue'
import Router from 'vue-router'
import { constantRoutes } from './constantRoutes' //导入初始化router
// 传入当前router
export function resetRouter (router) {
const createRouter = () =>
new Router({
mode: 'history'
routes: constantRoutes
})
// 用初始化的matcher替换当前router的matcher
router.matcher = createRouter.matcher
}