微前端:qiankun-基础篇

290 阅读6分钟

官网地址: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、主应用修改

qiankun官网

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微前端实战看这篇就够了 - Vue项目篇

基于 qiankun 的微前端最佳实践(图文并茂) - 应用间通信篇

redux、mobx

redux

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 
}

🥇相关资料