一、前言

在日常开发中,我们经常会遇到多个项目,多个代码库,不同的基础框架,想要整合进一个系统的情况。面对这种情况,我们有多种处理方式,以前最常用的是使用iframe加载。但是iframe加载在视觉上,会感觉很割裂,特别是弹窗无法真正的全屏。这时候我们可能就会考虑用微前端实现,其中最常用的就是qiankun微前端框架。 接下来,我们一起看一下一个基于vue2 webpack作为基础框架的项目如何快速改造为微前端项目,目标是把项目改为同时适合作为微前端的主应用,也适合作为微前端的子应用

二、基础环境准备

安装qiankun

npm install qiankun --save-dev

三、代码改造

vue.config.js 改造

  1. 添加env变量VUE_APP_SUB_APPS,含子应用的项目名称name,子应用的入口页面entry。这组变量将在后续用于在主应用中注册子应用
VUE_APP_SUB_APPS=[{"name":"vueApp1","entry":"http://localhost:8085"}]
  1. 在添加env变量VUE_APP_PROJECT_NAME,名字保持跟提供给主应用的name一致
  2. 修改的vue.config 添加以下配置,主要是把子应用打包为UMD库格式
configureWebpack: {
    performance: {
      maxAssetSize: 2000000,
      maxEntrypointSize: 2000000
    },
    name: process.env.VUE_APP_PROJECT_NAME,
    output: {
      library: process.env.VUE_APP_PROJECT_NAME,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      chunkLoadingGlobal: `webpackJsonp_${process.env.VUE_APP_PROJECT_NAME}`
    },
},

在layout/index.vue中添加子应用的挂载节点

//html 页面内容容器中添加
<div id="subapp-container" ref="subappContainer"> </div>
// js 部分添加
mounted() {
  if (this.$refs.subappContainer) {
    this.$refs.subappContainer.appendChild(window.top.$microSubContaner);
  }
},

改造main.vue

  1. 添加public-path.js 用于在微应用中添加 publicPath,注释的eslint 是为了让eslint忽略未被声明__webpack_public_path__,否则eslint会报错
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. 在main.vue的最顶部引入public-path.js
import './qiankun/public-path';
  1. 改造main.vue vue实例化方法,以及导出几个微应用生命周期回调
let instance = null;
function render(props = {}) {
  const { container } = props;
  Vue.use(permission);
  Vue.use(VueClipboard);
  instance = new Vue({
    router,
    store,
    i18n,
    render: (h) => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app');
}
// 提前创建微应用挂载点,以免微应用挂载时节点不存在
const $div = document.createElement('div');
$div.classList.add('sub-container');
$div.style.width = '100%';
$div.style.height = '100%';

window.$microSubContaner = $div;

// 作为主应用或者独立应用时执行
if (!window.__POWERED_BY_QIANKUN__) {
  render();
  initMain();
  // 等待子应用注册完成后再启动
  document.addEventListener('DOMContentLoaded', initMainStart);
}

// 作为子应用使用时
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);
  // 接收主应用传递的参数,以及给主应用传递参数
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });
  props.setGlobalState({});
}
export async function unmount() {
  // 实例一定要注销
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
}

main.vue使用的initMain函数相关代码

import { registerMicroApps, start, setDefaultMountApp, initGlobalState} from 'qiankun';
const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);

const getSubApps = () => {
  let subApps = JSON.parse(process.env.VUE_APP_SUB_APPS || '[]');
  let subAppsList = [];
  for (let i = 0; i < subApps.length; i++) {
    subAppsList.push({
      name: subApps[i].name,
      entry: subApps[i].entry,
      container: window.top.$microSubContaner,
      activeRule: getActiveRule(`#/${subApps[i].name}`)
    });
  }
  return subAppsList;
};
export function initMain() {
  registerMicroApps(getSubApps());
  // 接收子应用传递的数据,以及给子应用传递数据
  const { onGlobalStateChange } = initGlobalState({ aa: 1 });
  onGlobalStateChange((state) => {
    console.log(state, 'state');
  });
}
export function initMainStart() {
  setDefaultMountApp('/');
  start();
}

router改造

  1. 修改router/index.js的路由实例化方法
// 当为子应用时添加路由前缀
let router = new VueRouter({
  base: window.__POWERED_BY_QIANKUN__
    ? `/${process.env.VUE_APP_PROJECT_NAME}`
    : '/',
  routes,
  // mode: 'history',
  scrollBehavior() {
    return { y: 0 };
  }
});

  1. 修改router/routes.js 中动态路由的生成 utils.js 公共方法
const subApps = process.env.VUE_APP_SUB_APPS;
export const isInQiankun = !!window.__POWERED_BY_QIANKUN__;
export const subAppsNames =subApps.map(item => item.name);

routes.js 修改根据菜单数据的动态路由的根路由

//SubLayoutIndex 和Layout的区别是SubLayoutIndex不含菜单和系统顶部公共部分,作为子应用时不应有这些
return {
    path: LAYOUT_PATH,
    component: isInQiankun ? SubLayoutIndex : Layout,
    redirect: HOME_PATH ?? homePath,
    children: routes
};

这样就完成了一个项目的微前端改造。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]