阿里qiankun+vue搭建微前端

410 阅读2分钟

准备工作

使用vuecli新建两个应用,main_vue,app1,将main_vue作为主应用,app1为子应用。  
main_vue安装阿里qiankun使用以下安装命令

npm i qiankun -S 或者 yarn add qiankun #

配置子应用

与src同级新增vue.config.js

//vue.config.js

const { name } = require('./package');
module.exports = {
  devServer: {
    port:9095,//启动端口
    headers: {
      'Access-Control-Allow-Origin': '*',//配置跨域
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

于main.js同级新建public-path.js

//public-path.js

if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

子应用router.js配置

//router.js
//不能在此处直接配置路由需每次加载子应用时配置路由

import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]
export default router

子应用main.js配置

//main.js

import './public-path'; //导入public-path.js主要在于处理静态资源路径
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';

Vue.config.productionTip = false;

let router = null;
let instance = null;
function render(props = {}) {
  const { container } = props;
  //加载路由,之所以不在一个js页面配置是因为应用每次加载需要重新加载路由,否则当子应用卸载时再次加载会出现问题显示不出来
  router = new VueRouter({
  //主应用注册时子应用时activeRule需与此一致,此处为单独运行时默认路由前缀为/,在主应用运行时为/app-vue/
    base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/', 
    mode: 'history',
    routes,
  });

  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

//以下为qiangun规定的需要暴露出来的3个生命周期
export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}

配置主应用

主应用main.js配置

//main.js

import Vue from 'vue'
import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start } from 'qiankun';
import App from './App.vue';

Vue.config.productionTip = false

let app = null;

//类似单例模式,初次加载时渲染主应用框架,之后每次注册子应用时会再次触发该函数
function render({ appContent, loading }) {
  if (!app) {
    app = new Vue({
      el: '#my-app', //public文件下的index.html中根节点id需要相应修改
      data() {
        return {
          content: appContent,
          loading,
        };
      },
      render(h) {
        return h(App, {
          props: {
            content: this.content,
            loading: this.loading,
          },
        });
      },
    });
  } else {
    app.content = appContent;
    app.loading = loading;
  }
}

//路由处理
function genActiveRule(routerPrefix) {
  return location => location.pathname.startsWith(routerPrefix);
}

//首次渲染主应用
render({ loading: true });

const request = url =>
  fetch(url, {
    referrerPolicy: 'origin-when-cross-origin',
});

// 传入子应用的数据
let msg = {
  data: {
    auth: false
  },
  fns: [
    {
      name: "_LOGIN",
      _LOGIN() {
      }
    }
  ]
};
//注册子应用,name:子应用名字作为唯一标识,entry子应用路径,render触发渲染,  
注意activeRule需要与子应用路由的base一致
registerMicroApps(
  [
    { name: 'app-vue/', entry: '//localhost:8081', render, activeRule: genActiveRule('/app-vue/') },
  ],
  {
    beforeLoad: [
      //子应用加载前
      app => {
        window.console.log('before load', app);
      },
    ],
    beforeMount: [
      //进入子应用时触发
      app => {
        window.console.log('before mount', app);
      },
    ],
    afterUnmount: [
      //离开子应用时触发
      app => {
        window.console.log('after unload', app);
      },
    ],
  },
  {
    fetch: request,
  },
);

setDefaultMountApp('/vuecli');//进入主应用时加载的默认路由
runAfterFirstMounted(() => window.console.info('first app mounted')); //首个子应用加载完

start({ prefetch: true, fetch: request }); //启动

主应用app.js配置

//app.js

<template>
  <section>
    <header class="header">
      <nav>
        <ol>
          <li><a @click="goto('app1', '/app-vue/')">app1</a></li> 
        </ol>
      </nav>
    </header>
    <div v-if="loading">loading</div>
    <div class="appContainer" v-html="content">content</div>
  </section>
</template>

<script>
	export default {
		name: 'framework',
		props: {
			loading: Boolean,
			content: String,
		},
		methods: {
			goto(title, href) {
				window.history.pushState({}, title, href);
			},
		},
	};
</script>

<style scoped>
  .header {
  }

  .appContainer {
    margin-top: 50px;
  }
</style>

github Demo地址