原理:

主应用某个div下面挂载的是子应用编译后生成的html文件。

子应用注册:

首先,在主应用中安装qiankun工具包。

npm i qiankun -S

然后,另起一个appRegister.js, 用于写子应用挂载。
子应用挂载主要用到了 qiankun 包中的 registerMicroApps 和 start 方法。下面是挂载微应用时的配置信息:

import { registerMicroApps, start, runAfterFirstMounted, initGlobalState } from "qiankun"
//检查路由
const checkMicroUrl = (location, urlList) => {
  let url = location.hash.indexOf("?") != -1 ? location.hash.split("?")[0] : location.hash // 增加路由带query查询参数判断
  const routerName = url.substring(2)
  return urlList.includes(routerName)
}

const config = [{
name: 'activity', 
entry: '//localhost:7100',
container: '#subapp-viewport',
activeRule: () => {
    return checkMicroUrl(location, ['myActivity', 'ourActivity'])
    },
  }
]
registerMicroApps(
  config,
  {
    beforeLoad: [
      app => {
        
      }
    ],
    beforeMount: [
      app => {
        
      }
    ],
    afterUnmount: [
      app => { 
        
      }
    ]
  }
)


/**
* Step3 启动应用
*/
start()

接着,在主应用的main.js文件中引入 appRegister.js

import './utils/appRegister.js'

子应用挂载:

找到挂载子应用的组件,加一个div,id是 subapp-viewport,用来挂载子应用

比如项目中,子应用挂载到home组件下:
<template>
  <div class="wrap-all">
    <div class="wrapper">
      <left-nav></left-nav>
      <router-view v-if="!$route.meta.isMicroApp"></router-view>
      <div v-else id="subapp-viewport"> </div>
    </div>
  </div>
</template>

主应用中路由:

{
        path: '/myActivity', //匹配微应用跳转
        name: 'activity',
        component: import('@/components/home.vue'),
        meta: {
          isMicroApp: true
        }
      },

这样就配置好了主应用,当访问的是https://test.jinyi.cn/#/myActivity时,就会命中微应用,#subapp-viewport的div里就会渲染子应用

子应用配置:

重新创建一个空的vue项目,在其main.js顶部引入qiankun官网需要引入的public-path.js文件

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

main.js中定义qiankun的mount钩子,渲染子应用

/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount() {
  render(props);
}

子应用的是用vue-cli构建的,修改vue.config.jsconfigureWebpack

  // 自定义webpack配置
  configureWebpack: {
    output: {
      // 把子应用打包成 umd 库格式
###       // const { name } = require('./package'); name是package.json中定义的
      library: `${name}`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },

主应用向子应用单向传参

  • 主应用如何向子应用传值?

和Vue组件传值类似,都用到了props属性。官方文档中,registerMicroApps方法的第一个参数apps,数组apps中每个元素包含 activeRule, container, entry, name, props,前面四个都是必填,props是非必填,官网中对props的说明是:
qiankun官网props说明
比如当需要把主应用的NODE_ENV给子应用时,可以通过props传过去。

props: {
    data: {
      store,
      nodeEnv: process.env.NODE_ENV
    }
}
  • 子组件中如何接收?

子应用的mount钩子函数接收一个参数props,props对象里存有从主应用传过来的变量。
image
如上图,data属性,就是传给从主应用传过来的变量,可以在渲染子应用时,将props中的data取出来,存到store中,供子应用使用。

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

props对象的另外两个变量 onGlobalStateChange和setGlobalState,也是非常重要的方法,后面会用到。

主应用和子应用共享变量

设置共享变量的步骤:

  1. 用initGlobalState方法,在主应用中初始化需要用的变量
  2. 用onGlobalStateChange监听变量的变化
  3. 在子应用中使用setGlobalState,修改主应用传过来的变量的值

第1,2步,主应用中全局变量printData的注册

const {onGlobalStateChange, setGlobalState} = initGlobalState({
  printData: [], // 需要打印的数据
})

第3步,子应用中setGlobalState,修改全局变量printData。子应用中监听全局变量变化的也是onGlobalStateChange。这两个变量,上文中提到过,都是从props对象中取。

props传参和initGlobalState的区别是啥?

都是传参。props着重主应用向子应用单向传递参数。onGlobalStateChange着重共享变量。一系列变量,在主应用中初始化,在主和子应用中同时对它进行维护。

如何把qiankun发布到测试和生产环境

我的Vue项目路由是hash模式,发布到测试环境或者生产环境,只需要将项目发布的地址配到 entry属性里就好了。

测试环境:
entry: ‘https://test.jinyi.cn/maData/#/'

生产环境:
entry:’https://pro.jingyi.cn/maData/#/'

路径entry的配置比较好理解,重点是 activeRule 的配置。官网上activeRule可以是字符串、字符串数组、或者返回值是Boolean的函数。这里,强烈推荐第三种 返回值是Boolean的函数
activeRule是函数的时候,会接收一个location变量。location打印出来是这样的:
image
根据location.hash来匹配微应用路由就可以了。(具体查看前文中的
checkMicroUrl方法)

为什么推荐用activeRule的函数形式?

原因是第一次部署微应用到测试环境时,踩了坑。
首先交代下我的项目的背景,主应用的代码部署在三个测试环境。

https://test.jinyi.cn/jsapp/cc/a/#/
https://test.jinyi.cn/jsapp/cc/b/#/
https://test.jinyi.cn/jsapp/cc/c/#/

微应用部署在
https://test.jinyi.cn/maData/#/
如果主应用中 /maNew 路由使用的是微应用,activeRule像下面这样配置,在本地localhost中,可以正常访问。

‘activeRule’: '/#/maNew'

但是,在测试环境中就不行,访问 https://test.jinyi.cn/jsapp/cc/a/#/ ,页面会一片空白,子应用的html代码没有挂载在container配置的div下面。

什么原因导致子应用不渲染呢?

大家可以对比下主应用和微应用部署的地址区别,前面一段test.jinyi.cn是相同的,后面就不同了,上面配置的activeRule字符串,是告诉qiankun,在访问/maNew时,项目A,B,C分别访问以下地址:

https://test.jinyi.cn/jsapp/cc/a/#/maNew
https://test.jinyi.cn/jsapp/cc/b/#/maNew
https://test.jinyi.cn/jsapp/cc/c/#/maNew

很明显,访问这三个链接并没有资源,a、b、c三个项目,在访问/maNew路由时,都应该访问:
https://test.jinyi.cn/maData/#/maNew
要想正常命中maNew,需要改写activeRule:

'activeRule': [
'/#/maNew',
'/jsapp/cc/a/#/maNew’,
'/jsapp/cc/b/#/maNew’,
'/jsapp/cc/c/#/maNew’,
]

可以发现,activeRule非常复杂,如果有5个测试地址,2个路由走微应用,那么activeRule长度就是10。。是很容易出错的。从理解和实现上,都不推荐以上配置activeRule的方法。

生产环境的发布和测试环境相同。

发布到测试和生产时踩的坑

坑一就是上文提到的,本地可以正常访问子应用的路由,但是在测试环境中访问的确是空白页。
坑二是发到测试环境后浏览器报错,提示element的css字体找不到,错误是这样的:
element字体找不到

搜了一下官网,还真有这个问题
微应用打包之后 css 中的字体文件和图片加载 404

因为子应用要被几个主应用使用,所以,方法 2,3,4都不适用。
方法1,element是通过npm安装的,也不适用。
只有方法5可以用。项目的css代码较少时,可以用,部署后也不会报错。
还有一种方法,既把css单独打包了,也可以解决element-icons.535877f5.woff 引用相对路径报错的问题。虽然比较挫,但是也解决了这个问题...
写一个js脚本,读取到有问题的css文件,根据NODE_ENV判断地址怎么换,需要改两个地方:
Step1,新增一个changeCssPath.js文件

const { readFileSync, readdirSync, writeFileSync } = require('fs');

const { name } = require('./package');

const { join } = require('path')

let folderName = ''

let mode = ''

switch (process.env.NODE_ENV) {

case "development":

folderName = "cc"

mode = "development"

break;
case "production":

folderName = "dist"

mode = "production"

break;
default:
break;
}


/**
* 变更css路径
*/
async function changeCssPath() {

let dirs = await readdirSync(join(__dirname, `${folderName}/static/css`))

let appfilename = dirs.filter(dir => dir.startsWith('app'))[0]

let appValue = await readFileSync(join(__dirname, `${folderName}/static/css/${appfilename}`), 'utf-8')

appValue = appValue.replace('../../static/fonts', mode === 'production' ? `./${name}/static/fonts`: `../${name}/static/fonts`)

appValue = appValue.replace('../../static/fonts', mode === 'production' ? `./${name}/static/fonts`: `../${name}/static/fonts`)

await writeFileSync(join(__dirname, `${folderName}/static/css/${appfilename}`), appValue)

console.log('ok!')
}

changeCssPath()

step2, 在package.json中,打包的时候,运行一下changeCssPath( )脚本

  "scripts": {
    "serve": "cross-env NODE_ENV=development vue-cli-service serve && cross-env NODE_ENV=development node changeCssPath.js",
    "build:dev": "cross-env NODE_ENV=development vue-cli-service build && cross-env NODE_ENV=development node changeCssPath.js",
    "build": "cross-env NODE_ENV=production vue-cli-service build && cross-env NODE_ENV=production node changeCssPath.js"
  },

以上就是给大家分享的微应用的实践。


joychenke
47 声望3 粉丝

业精于勤荒于嬉。加油ヾ(◍°∇°◍)ノ゙