qiankun 主项目和子项目都是 vue2,部署在不同的服务器上,nginx 配置

1、主项目配置

1.1 micro.vue 组件

<template>
  <div id="container-sub-app"></div>
</template>

<script>
import { loadMicroApp } from 'qiankun';
import actions from '@/utils/actions.js';

export default {
  name: 'microApp',
  mixins: [actions],
  data() {
    return {
      microApp: null
    };
  },
  mounted() {
    const getMicroInfo = this.getMicroInfo();
    this.microApp = loadMicroApp(getMicroInfo, {
      singular: true
    });
  },
  beforeDestroy() {
    console.log('beforeDestroy...');
    this.microApp.unmount();
  },
  methods: {
    // 手动加载微应用
    getMicroInfo() {
      const appIdentifying = this.$route.path.split('/')[1];
      let data = {};
      const href = window.location.host;
      for (let i = 0; i < document.subApps.length; i++) {
        const element = document.subApps[i];
        if (element.activeRule.includes(appIdentifying)) {
          if (typeof element.entry !== 'string') {
            data = {
              ...element,
              entry: element.entry[href]
                ? element.entry[href]
                : Object.values(element.entry)[0]
            };
          } else {
            data = { ...element };
          }
          data.props = {
            token: {
              userInfo: {
                userName: '小明',
                userId: '123',
                date: new Date().toLocaleString()
              }
            }
          };
          data.activeRule = [appIdentifying];
          break;
        }
      }
      return data;
    }
  }
};
</script>

1.2 index.html 引入配置

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <script src="<%= BASE_URL %>register-apps.js"></script>
    <title><%= webpackConfig.name %></title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but <%= webpackConfig.name %> doesn't work properly without
        JavaScript enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

1.3 register-apps.js 配置

document.subApps = [
  {
    name: 'besFront',
    //entry: '//localhost:8086/bes-front/',// 本地调试
    entry: '/bes-front/',// 部署到服务器
    container: '#container-sub-app',
    activeRule: '/bes-front'
  }
];

1.4 路由配置

import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

import Layout from '@/layout';
import MicroApp from '@/components/microApp';

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '/dashboard',
    component: Layout,
    redirect: '/dashboard/index',
    children: [
      {
        path: 'index',
        name: 'Dashboard',
        component: () => import('@/views/dashboard/index'),
        meta: { title: 'Dashboard', icon: 'dashboard' }
      }
    ]
  },
  {
    path: '/',
    component: Layout,
    children: [
      {
        path: 'bes-front',
        component: MicroApp// 重点,用于加载子项目
      }
    ]
  }
  // 404 page must be placed at the end !!!
  // { path: '*', redirect: '/404', hidden: true }
];

const createRouter = () =>
  new Router({
    mode: 'history', // require service support
    base: '/parent/',// 部署在不同服务器上,主应用加前缀
    scrollBehavior: () => ({ y: 0 }),
    routes: constantRoutes
  });

const router = createRouter();

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter();
  router.matcher = newRouter.matcher; // reset router
}

export default router;

2、子项目配置

2.1 main.js 配置

// 动态设置 publicPath
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

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

let instance = null;
function render(props = {}) {
  console.log('子应用 render props::', props, 'instance====', instance);
  // sessionStorage.setItem('userInfo', JSON.stringify(props.token.userInfo));
  const { container } = props;

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

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

export async function bootstrap() {
  console.log('子应用 bootstrap ===========================');
}

let initialState = null;
export async function mount(props) {
  console.log('子应用 mount props ===============', props);
  sessionStorage.setItem('userInfo', JSON.stringify(props.token.userInfo));
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log('子应用获取共享数据 state::', state, 'prev::', prev);
    // 接收主应用中的共享数据 并将其设置为全局变量
    Vue.prototype.$initialState = state;
  });
  props.setGlobalState({
    initialState:
      '子应用中修改主应用中的全局变量,实现住应用子应用间数据的双向双向通信'
  });

  render(props);
}
export async function unmount() {
  console.log('子应用 unmount==========');
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
}

2.2 public-path 文件

if (window.__POWERED_BY_QIANKUN__) {
  /* eslint-disable @typescript-eslint/camelcase */
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

2.3 vue.config.js 配置

'use strict';
const path = require('path');
const defaultSettings = require('./src/settings.js');
const proxyTable = require('./proxyTable');

function resolve(dir) {
  return path.join(__dirname, dir);
}

const name = defaultSettings.title || 'vue Admin Template'; // page title

// If your port is set to 80,
    // use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 9528 npm run dev OR npm run dev --port = 9528
const port = process.env.port || process.env.npm_config_port || 9528; // dev port

// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
  /**
   * You will need to set publicPath if you plan to deploy your site under a sub path,
   * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
   * then publicPath should be set to "/bar/".
   * In most cases please use '/' !!!
   * Detail: https://cli.vuejs.org/config/#publicpath
   */
  publicPath: '/bes-front/',
  outputDir: 'dist',
  assetsDir: 'static',
  lintOnSave: process.env.NODE_ENV === 'development',
  productionSourceMap: false,
  devServer: {
    headers:{
        "Access-Control-Allow-Origin": "*",
    },
    port: port,
    open: true,
    proxy: proxyTable,
    overlay: {
      warnings: false,
      errors: true
    }
  },
  configureWebpack: {
    // provide the app's title in webpack's name field, so that
    // it can be accessed in index.html to inject the correct title.
    name: name,
    output:{
        library: `besFront`,// 主应用
        libraryTarget: "umd",// 把微应用打包成 umd 库格式
        jsonpFunction: `webpackJsonp_besFront`,// webpack5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
    },
    resolve: {
      alias: {
        '@': resolve('src')
      }
    }
  },
  chainWebpack(config) {
    // it can improve the speed of the first screen, it is recommended to turn on preload
    config.plugin('preload').tap(() => [
      {
        rel: 'preload',
        // to ignore runtime.js
        // https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/config/app.js#L171
        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
        include: 'initial'
      }
    ]);

    // when there are many pages, it will cause too many meaningless requests
    config.plugins.delete('prefetch');

    // set svg-sprite-loader
    config.module.rule('svg').exclude.add(resolve('src/icons')).end();
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
      .end();

    config.when(process.env.NODE_ENV !== 'development', (config) => {
      config
        .plugin('ScriptExtHtmlWebpackPlugin')
        .after('html')
        .use('script-ext-html-webpack-plugin', [
          {
            // `runtime` must same as runtimeChunk name. default is `runtime`
            inline: /runtime\..*\.js$/
          }
        ])
        .end();
      config.optimization.splitChunks({
        chunks: 'all',
        cacheGroups: {
          libs: {
            name: 'chunk-libs',
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            chunks: 'initial' // only package third parties that are initially dependent
          },
          elementUI: {
            name: 'chunk-elementUI', // split elementUI into a single package
            priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
            test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
          },
          commons: {
            name: 'chunk-commons',
            test: resolve('src/components'), // can customize your rules
            minChunks: 3, //  minimum common number
            priority: 5,
            reuseExistingChunk: true
          }
        }
      });
      // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
      config.optimization.runtimeChunk('single');
    });
  }
};

2.3 接口需加前缀区分

const uri = '/child1/api';

// 部门新增
export async function departmentAddApi(params) {
  return $ajax.post(`${uri}/department/add`, params);
}

3、主项目和子项目部署在不同的服务器上,其中主应用路由使用history模式,子应用路由使用hash模式,子应用所有的静态资源及接口统一从主应用进行转发,nginx 配置如下:

主应用 nginx 配置:

locatoin /parent/ {
    root /opt/parent;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
}


location /bes-front/ {
    proxy_pass http://子应用ip/bes-front/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header REMOTE_HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}


location /bes-front/static/ {
    proxy_pass http://子应用ip/bes-front/static/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header REMOTE_HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}


location /child1/ {
    proxy_pass http://子应用ip/child1/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header REMOTE_HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

注:使用的下面这种部署方式,主应用代码放在主应用服务器的/opt/parent目录下,子应用代码放在子应用服务器的/opt/bes-front目录下面,重点是主应用的路由要加前缀进行区分,其次,子应用的所有接口转发都统一从主应用转发到子应用,因此子应用的接口要增加前缀进行区分

官网参考链接:https://qiankun.umijs.org/zh/cookbook#%E5%9C%BA%E6%99%AF-2%E4%B8%BB%E5%BA%94%E7%94%A8%E5%92%8C%E5%BE%AE%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BD%BF%E7%94%A8-nginx-%E4%BB%A3%E7%90%86%E8%AE%BF%E9%97%AE

4、总结

在部署主应用和子应用时,遇到两个核心问题

1、主应用路由未加前缀,导致主应用转发到子应用时,脱离了qiankun框架直接访问子应用的静态资源,致使的结果是,无法再返回到主应用。

2、如果要求子应用接口都统一从主应用转发,那么子应用接口需要前缀进行区分。

3、最后,静态资源的问题,多半是子应用的 publicPath 配置有关。

相关推荐
吃饭只吃七分饱12 分钟前
字符设备驱动模块 dev和misc
linux·运维·服务器
knighthood200144 分钟前
解决:gpg: 从公钥服务器接收失败:服务器故障
运维·服务器
偏执网友1 小时前
Hyper-V管理器连接到服务器出错。请检查虚拟机管理服务是否正在运行以及是否授权你连接到此服务器。
运维·服务器
nice666601 小时前
Linux 常用命令详细总结
java·linux·运维·服务器·数据结构·mysql
wys23381 小时前
自营机房服务器产品优势
服务器
壹只菜鸟1 小时前
nginx 资料整理(二)- HTTP
运维·nginx·http
_李白_1 小时前
linux:使用sar诊断问题
linux·运维·服务器
咸鱼咖啡厅1 小时前
10.10 QT服务器与客户端
服务器·开发语言·qt
小安运维日记1 小时前
Linux云计算 |【第四阶段】RDBMS2-DAY2
linux·运维·服务器·sql·mysql·云计算
小安运维日记1 小时前
Linux云计算 |【第四阶段】RDBMS2-DAY4
linux·运维·数据库·云计算·改行学it