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目录下面,重点是主应用的路由要加前缀进行区分,其次,子应用的所有接口转发都统一从主应用转发到子应用,因此子应用的接口要增加前缀进行区分
4、总结
在部署主应用和子应用时,遇到两个核心问题
1、主应用路由未加前缀,导致主应用转发到子应用时,脱离了qiankun框架直接访问子应用的静态资源,致使的结果是,无法再返回到主应用。
2、如果要求子应用接口都统一从主应用转发,那么子应用接口需要前缀进行区分。
3、最后,静态资源的问题,多半是子应用的 publicPath 配置有关。