likes
comments
collection
share

微前端之qiankun上手实践指南

作者站长头像
站长
· 阅读数 53

前言

本文主要记录使用qiankun + Vue2搭建微前端的过程,主应用、子应用均采用Vue2,由于笔者工作项目中使用的路由模式都是hash模式,所以文中的主应用和子应用也都采用hash模式。

一、主应用

1、创建主应用

通过vue-cli创建一个Vue2项目,创建主应用路由和其对应的页面。

微前端之qiankun上手实践指南

// /src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login.vue')
  },
  {
    path: '/',
    redirect: '/home',
    component: () => import('@/views/layout.vue'),
    children: [
      {
        path: '/home',
        name: 'Home',
        component: () => import('@/views/home.vue')
      }
    ]
  }
]

const router = new VueRouter({
  mode: 'hash',
  routes
})

export default router

接着创建主应用layout布局,左侧菜单栏,右侧用于挂载微应用。

微前端之qiankun上手实践指南

<template>
  <div style="height: 100%;">
    <el-container style="height: 100%;">
      <el-aside style="height: 100%;">
        <el-menu
          router
          background-color="#545c64"
          text-color="#fff"
          active-text-color="#ffd04b"
          style="height: 100%;"
          :default-active="$route.path"
        >
        <el-menu-item index="/home">
          <i class="el-icon-menu"></i>
          <span slot="title">首页</span>
        </el-menu-item>
        <el-menu-item index="/micro-app-goods/list">
          <i class="el-icon-menu"></i>
          <span slot="title">商品列表</span>
        </el-menu-item>
        <el-menu-item index="/micro-app-order/list">
          <i class="el-icon-menu"></i>
          <span slot="title">订单列表</span>
        </el-menu-item>
        </el-menu>
      </el-aside>
      <!-- 主内容 -->
      <el-main>
        <!-- 主应用路由 -->
        <router-view />
        <!-- 挂载子应用的节点 -->
        <div id="micro-app"></div>
      </el-main>
    </el-container>
  </div>
</template>

菜单栏的商品列表和订单列表分别为【商品子应用】和【订单子应用】的页面。

2、主应用接入qiankun

安装qiankun:

yarn add qiankun

接入子应用:

// layout.vue
mounted() {
    const apps = [
      {
        name: 'micro-app-goods', // 微应用名称,确保唯一
        entry: '//localhost:3002', // 微应用入口
        container: '#micro-app', // 微应用挂载的节点
        activeRule: '#/micro-app-goods' // 微应用激活规则,由于是用的hash模式,所以前面记得加#
      },
      {
        name: 'micro-app-order',
        entry: '//localhost:3003',
        container: '#micro-app',
        activeRule: '#/micro-app-order'
      }
    ]
    registerMicroApps(apps) // 注册
    start() // 启动
}

为了确保qiankun启动时能获取到子应用挂载节点,所以在mounted进行上述操作。

此时点击菜单的商品列表或订单列表,页面还是空白的,因为是在主应用的layout路由里加载子应用,此时主应用并没有/micro-app-goods/list这个路由,所以会404。

微前端之qiankun上手实践指南

所以还需要在主应用的路由文件里增加一个路由,通过通配符去匹配子应用的路径并将其指向layout

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login.vue')
  },
  {
    path: '/',
    redirect: '/home',
    component: () => import('@/views/layout.vue'),
    children: [
      {
        path: '/home',
        name: 'Home',
        component: () => import('@/views/home.vue')
      }
    ]
  },
  // 匹配子应用
  {
    path: '/micro-app-*', // 以`/micro-app-`开头
    component: () => import('@/views/layout.vue')
  }
]

再次点击跳转子应用的菜单可以发现qiankun已经开始工作了,但此时我们还没有创建对应的子应用,所以控制台会报错。

微前端之qiankun上手实践指南

二、子应用

1、创建子应用

创建一个商品子应用micro-app-goods并创建路由。

微前端之qiankun上手实践指南

// src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/micro-app-goods/list',
    name: 'GoodsList',
    component: () => import('@/views/goods/list.vue')
  }
]

const router = new VueRouter({
  mode: 'hash',
  routes
})

export default router
<!-- src/views/goods/list.vue -->
<template>
  <div>
    <p>商品列表页</p>
  </div>
</template>

2、增加public-path.js

// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line camelcase, no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

3、改造main.js

import './public-path' // 引入前面增加的publi-path文件
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import store from './store/index'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false
Vue.use(ElementUI)

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()
}

export async function bootstrap() {}

export async function mount(props) {
  render(props)
}

export async function unmount() {
  instance.$destroy()
  instance.$el.innerHTML = ''
  instance = null
}

子应用不需要安装qiankun,只需在main.js暴露出qiankun所需要的生命周期函数即可。

4、改造vue.config.js

  • 子应用需要打包成umd格式。
  • 子应用需要支持跨域。
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package')

module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: './',
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`
      // chunkLoadingGlobal: `webpackJsonp_${name}` // webpack5使用chunkLoadingGlobal
    }
  },
  devServer: {
    port: 3002,
    headers: {
      // 支持跨域
      'Access-Control-Allow-Origin': '*'
    }
  }
})

到这里子应用的改造便完成了,重新启动子应用后便可以在主应用里加载子应用了。

微前端之qiankun上手实践指南

三、应用间跳转

因为主应用、子应用的路由模式都是hash模式,所以比较省事,平时怎么跳的就怎么跳。

// 【商品子应用】跳转【订单子应用】
this.$router.push({
  path: '/micro-app-order/list'
})

微前端之qiankun上手实践指南

四、样式隔离

qiankun提供了两种样式隔离方式:strictStyleIsolationexperimentalStyleIsolation

start({
  sandbox: {
    strictStyleIsolation: true,
    // or
    // experimentalStyleIsolation: true
  }
})

使用strictStyleIsolation开启严格的隔离模式后,qiankun会为每个微应用包裹上一层shadow dom来达到隔离的效果。

微前端之qiankun上手实践指南

experimentalStyleIsolation则是为微应用的样式增加【应用名前缀】。

微前端之qiankun上手实践指南

qiankun3.0版本计划移除strictStyleIsolation,推荐使用experimentalStyleIsolation。

五、应用通信

1、props

主应用通过props传递:

const token = 'xxx'
const apps = [
  {
    name: 'micro-app-goods',
    entry: '//localhost:3002',
    container: '#micro-app',
    activeRule: '#/micro-app-goods',
    props: {
      token
    }
  },
  {
    name: 'micro-app-order',
    entry: '//localhost:3003',
    container: '#micro-app',
    activeRule: '#/micro-app-order',
    props: {
      token
    }
  }
]
registerMicroApps(apps)
start({
  sandbox: {
    experimentalStyleIsolation: true
  }
})

子应用接收:

// src/main.js
function render(props = {}) {
  const { container, token } = props
  console.log('token', token)
  
  instance = new Vue({
    router,
    store,
    render: (h) => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app')
}

当然也可以传递一些方法:

// 主应用
const actions = {
  getName: () => {
    return '张三'
  }
}
const apps = [
  {
    name: 'micro-app-goods',
    entry: '//localhost:3002',
    container: '#micro-app',
    activeRule: '#/micro-app-goods',
    props: {
      actions
    }
  }
]
// 微应用
function render(props = {}) {
  const { container, actions } = props
  Vue.prototype.$actions = actions // 挂到全局
  
  instance = new Vue({
    router,
    store,
    render: (h) => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app')
}

// 在其他地方调用
mounted() {
  const name = this.$actions.getName()
}

2、initGlobalState

主应用:

// src/qiankun/actions.js
import { initGlobalState } from 'qiankun'

const actions = initGlobalState({})

export default actions

主应用使用:

import actions from '@/qiankun/actions'
// 改变状态
actions.setGlobalState({
  name: '李四'
})
// 监听状态改变
actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log('主应用onGlobalStateChange', state, prev)
})

微应用的props参数里默认就有setGlobalStateonGlobalStateChange

微前端之qiankun上手实践指南

微应用可以通过onGlobalStateChange监听状态改变。

// 微应用
export async function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    console.log(state, prev)
  }, true) // onGlobalStateChange 第二个参数为 true,表示立即执行一次观察者函数
  render(props)
}

initGlobalState使用起来挺鸡肋的,qiankun3.0版本也计划移除掉移除掉它了,不推荐使用。

除此之外,也可以使用状态管理(如vuex)或发布/订阅模式(EventBus)等方式,通过主应用下发,实现应用间通信。

六、路由缓存keep-alive

使用registerMicroApps时,微应用在切出后会卸载掉,切入后再重新加载,所以在这种方式下,即使我们的Vue应用使用了keep-alive也无法实现缓存。

1、方案一:loadMicroApp + 多应用节点

qiankun提供了loadMicroAppAPI用于手动加载微应用,使用loadMicroApp,切换微应用不会被卸载,除非自己手动进行卸载。

同时创建多个微应用容器节点,使每个微应用都单独挂载在一个DOM节点上,用v-show控制对应的微应用容器显示或隐藏,隐藏只是display:none而没有真正销毁DOM,从而达到keep-alive的作用。

2、方案二:手动缓存实例

还是通过registerMicroApps注册微应用,只挂载一个微应用节点,使用类似于keep-alive的实现方式,微应用自己缓存本身实例的vnode,下次重新进入时微应用直接使用上一次缓存的vnode直接渲染为真实DOM。

3、方案一实现过程

  • 方案一的优点是:实现起来较为简单,应用间切换更流畅一些。缺点是如果应用过多,DOM节点也更多,内存负担会更大。
  • 方案二的优点是:DOM节点少,内存也会降低些。缺点是需要自己实现,实现方式较为复杂,而且多了虚拟DOM转换真实DOM的过程,渲染时间更多一些。

笔者的项目中采用的是方案一,对方案二感兴趣的同学可自行查阅其他文章了解。下面是改造过程:

  • 定义多个微应用节点,使用v-show控制显示隐藏。
<!-- <div id="micro-app"></div> -->
<div id="micro-app-goods" v-show="$route.path.startsWith('/micro-app-goods')"></div>
<div id="micro-app-order" v-show="$route.path.startsWith('/micro-app-order')"></div>
  • loadMicroApp
mounted() {
    const apps = [
      {
        name: 'micro-app-goods',
        entry: '//localhost:3002',
        // container: '#micro-app',
        container: '#micro-app-goods', // 修改container
        activeRule: '#/micro-app-goods'
      },
      {
        name: 'micro-app-order',
        entry: '//localhost:3003',
        // container: '#micro-app',
        container: '#micro-app-order', // 修改container
        activeRule: '#/micro-app-order'
      }
    ]
    this.appInstances = apps.map(app => {
      const appInstance = loadMicroApp(app, {
        sandbox: {
          experimentalStyleIsolation: true
        }
      })
      return appInstance
    })
    
    // 销毁时调用loadMicroApp返回值里的unmount进行卸载
    this.$once('hook:beforeDestroy', () => {
      this.appInstances.forEach((app) => {
        app.unmount()
      })
    })
}

这里只是做一个示例,实际项目中也可以通过路由判断去加载对应子应用,而不是一次性加载全部子应用。

loadMicroApp返回一个对象,可以通过它里面的方法来进行卸载操作,例如在多标签页的后台管理系统中,关闭后如果不存在子应用的页签了可以手动进行卸载。

微前端之qiankun上手实践指南

  • 子应用里增加keep-alive
<template>
  <div id="app">
    <keep-alive>
      <router-view />
    </keep-alive>
  </div>
</template>

关于keep-alive改造就完成了,接下来在商品列表页和订单列表页的created输出内容。

created() {
  console.log('商品列表===')
}

微前端之qiankun上手实践指南

可以看到,重新切回商品列表和订单列表,控制台不会触发createdkeep-alive缓存已经生效。

最后

感谢您的阅读,如果本文对你有什么帮助的话,别忘了动动手指点个赞❤❤❤!

转载自:https://juejin.cn/post/7269740596335591458
评论
请登录