微前端

505 阅读3分钟

iframe

image.png

基座(vue)

基座可以放自己路由,以及音乐其他子应用

image.png

注册应用(main.js)

  • 安装npm i -S qiankun
  • 引入qiankun,import {registerMicroApps,start} from 'qiankun'
  • 注册应用
const apps = [
    {
        name: 'vueApp',//应用自定义名称
        entry: '//localhost:10000',//默认会加载这个html解析里面的js动态资源(子应用必须解决跨域),使用fetch来请求
        container:'#vue',//容器名
        activeRule: '/vue'//激活的路径
    },
    {
        name: 'vueApp',//自定义名称
        entry: '//localhost:20000',
        container:'#react',
        activeRule: '/react'
    }

]

registerMicroApps(apps);//注册应用
start();//启动应用

加载的为微组件

例如一个路由的弹窗框

import { loadMicroApp } from 'qiankun'

loadMicroApp({
    name: 'app',
    entry: '//localhost:7100',
    container: '#yourContaniner'

})

子项目更改(vue)

路由的base路径

new VueRouter({
    mode:'history',
    base: '/vue',//更改为对应的子路由路径
    //base: window.__POWERED_BY_QIANKUN__?'/vue':'/',//完整写法
    routes
})

导出要求的三个方法

导出的方法规定要promise,所以可以使用async直接返回promise

//main.js
...

let instance = null//方便卸装
function render(){
   instance = new Vue({
        router,
        render: h=>h(App)
    }).$mount('#app');//挂载到自己的html中,基座会拿到这个挂载后的html再插入到基座中

}


//给webpack的publicpath动态注入基座的路径,主要解决微应用动态载入的脚本、样式、图片等 地址不正确的问题
//添加这个,不然基座触发跳转到子项目路由会有问题
if(window.__POWERED_BY_QIANKUN__){
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}


//保证单独项目也能够独立运行,有window.__POWERED_BY_QIANKUN__说明是基座调用
if(!window.__POWERED_BY_QIANKUN__){
    render()
}

//方法必须有
//类似于created方法,只在初始化的时候触发
export async function bootstrap(props){}

//
export async function mount(props){
    render(props)
}
export async function unmount(props){
    instance.$destory()
}
//vue.config.js
module.exports = {
    //publicPath: '/',//这里打包地址要基于主应用中注册的entry值
    devServer: {
        port: 20000,
        headers: {
            'Access-Control-Allow-Origin': '*'
        }
    },
    configureWebpack: {
        output: {
            library: 'vueApp',//库名,与主应用注册的微应用的name一致
            libraryTarget: 'umd',//用window.vueApp可以拿到main.js暴露的所有东西
        }
    }

}

启动该项目

应该是该项目单独也能正常访问,基座调用切换到对应的/vue的路由也可以正常显示

注意问题(正式环境下部署)

主应用注册时候的配置

主应用注册微应用时候,entry可以为相对路径,activeRule不可以和entry一样(否则主应用页面刷新就变成微应用)

const apps = [{
    name: 'VueApp',
    entry: '/system/',//http://localhost/system/ 这里会通过nginx代理指向对应的子应用地址
    container: '#frame',
    activeRule: '/manage'
}]

vue路由的base

如果是主应用调用的那么路由的base为/manage/

router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__?'/manage':'/',
    mode: 'history',
    routes
})

webpack打包配置

对于webpack构建的微应用,微应用的webpack打包的publicPath需要配置成/system/,否则微应用的inde.html能正常请求,但是微应用index.html里面的js/css路径不会带上/system/.

module.exports = {
    publicPath: '/system/''
}

nginx配置

主应用:

/system/代理代理到子应用中,即访问到/system/会代理到子应用的地址才可以访问到子应用 image.png

子应用的配置

跨域等配置

image.png

dockerfile配置

自动化配置(gitlab-cli/cd配置)

部署逻辑

nginx代理,但不适合自动化部署,最理想的方法为下面: image.png

image.png

参考

完整的项目

vue-base(基座代码)

//main.js
import Vue from 'vue'
import App from './App.vue'
import vueRouter from 'vue-router'
import {registerMicroApps,start} from 'qiankun'
import VueRouter from 'vue-router'


Vue.use(VueRouter)
var router = new vueRouter({
  mode: 'history',
  base: '/',
  routes: [
    {
      path: '/hello',
      component: ()=>import('./components/HelloWorld.vue')
    }
  ]
})

Vue.config.productionTip = false



const apps = [
  {
      name: 'vueDemo',//应用自定义名称
      entry: '//localhost:10001',//默认会加载这个html解析里面的js动态资源(子应用必须解决跨域),使用fetch来请求
      container:'#vue',//容器名
      activeRule: '/vue'//激活的路径
  },
  {
      name: 'vueDemo1',//自定义名称
      entry: '//localhost:20000',
      container:'#vue1',
      activeRule: '/vue1'
  }

]

registerMicroApps(apps);//注册应用
start();//启动应用


new Vue({
  router: router,
  render: h => h(App),
}).$mount('#app')
//App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">

    <router-link to="/hello">hello</router-link>
    <router-link to="/vue">vueDemo</router-link>
    <router-link to="/vue1">vueDemo1</router-link>


    <router-view></router-view>
    <div id="vue"></div>
    <div id="vue1"></div>

  </div>
</template>

<script>

export default {
  name: 'App',
}
</script>

子项目vue_demo

//main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false


var instance = null
function render(props){
  let { container } = props;
  instance = new Vue({
    render: h => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app')
}

//加这个防止父路由跳子路由出现问题
if (window.__POWERED_BY_QIANKUN__) {
  // 动态设置 webpack publicPath,防止资源加载出错
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

if(!window.__POWERED_BY_QIANKUN__){
  render({})
}



//方法必须有
//类似于created方法,只在初始化的时候触发
export async function bootstrap(){}

//
export async function mount(props){
    render(props)
}
export async function unmount(){
    instance.$destroy()
}
//vue.config.js
module.exports = {

    devServer: {
        port: 10000,
        headers: {
            'Access-Control-Allow-Origin': '*'
        }
    },
    configureWebpack: {
        output: {
            library: 'vueDemo',//库名,与主应用注册的微应用的name一致
            libraryTarget: 'umd',//用window.vueApp可以拿到main.js暴露的所有东西
        }
    }

}

vue_demo1配置参考vue_demo

实现效果

  • 启动vue_demo和vue_demo1子项目
  • 启动主项目,图示: 实现效果为,每个路由都能正确显示每个页面 image.png

可能遇到的问题

  • 会报_webpack_public_path__没有定义,原因webpack_public_path 不是全局变量所导致的,子应用 package.json 文件中 eslintConfig 配置全局变量后 重起服务,package.json添加这个全局变量,代码如下:
 "eslintConfig": {
    ...
    "globals": {
      "__webpack_public_path__ ": true
    }
  },

如果还是不行,直接去掉eslint:

//vue.config.js
module.exports = {
    lintOnSave: false,
    ....
}

参数传递

qiankun内部使用initGlobalState(state)定义全局状态,该方法执行后返回一个MicroAppStateActions实例,实例中包含三个方法,分别是onGlobalStateChangesetGlobalStateoffGlobalStateChange

  • 主应用:
//main.js
import {initGlobalState  } from 'qiankun'

const actions = initGlobalState({});
Vue.prototype.$actions = actions
//模拟全局token
<button @click="setToken">设置token</button>

methods: {
    setToken(){
      let token = Math.random()*10000//模拟token
      console.log('actions的',this.$actions);
      this.$actions.setGlobalState({ token })
    }
  }
  • 子应用获取,子应用在props传递的时候就已经自带了onGlobalStateChangesetGlobalState这两个方法,只要主应用创建一个MicroAppStateActions,保证所有子应用使用的都是该对象就可以实现数据共享。
//main.js
export async function mount(props){
    if (props) {
      Vue.prototype.$actions = props
    }
    render(props)
}
mounted () {
    //第二个参数为是否立即触发
    this.$actions.onGlobalStateChange((state) => {
        this.token = state.token;
    }, true);
}
  • 上面就是最简单的演戏数据传递,需要规范的封装actions等请参考或者参考1