1 乾坤框架的css沙箱只能保证子应用之间的样式不相互影响,所以开发过程中主应用应该给样式添加限制
2 无法在子应用全局变量中保存props
微应用接入指南
主应用
Main.js代码
import { createApp } from 'vue'
import App from './App.vue'
import Router from './router'
import { registerMicroApps, start, initGlobalState, setDefaultMountApp } from 'qiankun'
let apps = [
{
name: 'app1',
entry:
process.env.NODE_ENV === "development" ? '//localhost:8082/app1/' : '/app1',
container: '#container',
activeRule: '#/app1',
beforeLoad: () => { },
beforeMount: () => { },
afterMount: () => { },
beforeUnmount: () => { },
afterUnmount: () => { }
}, {
name: 'app2',
entry:
process.env.NODE_ENV === "development" ? '//localhost:8081/app2/' : '/app2',
container: '#container',
activeRule: '#/app2',
beforeLoad: () => { },
beforeMount: () => { },
afterMount: () => { },
beforeUnmount: () => { },
afterUnmount: () => { }
},
];
let initQiankun = (vue) => {
window.__POWERED_BY_QIANKUN_PARENT__ = true
//注册微应用
registerMicroApps(apps);
setDefaultMountApp('#/app1')
start();
//初始化传给微应用的数据
let state = {
userId: '1232q345123',
name: 'LiLin',
pageName: '',
isLogin: true
};
const actions = initGlobalState(state);
// 子应用修改了state会调用此方法 state: 变更后的状态; prev 变更前的状态
actions.onGlobalStateChange((state, prev) => {
console.log('子应用修改了state', state, prev);
if (!state.isLogin) {
//TODO 重新登录
}
});
actions.setGlobalState(state);
actions.offGlobalStateChange();
vue.config.globalProperties.$actions = actions;
}
Router.beforeEach((to, from, next) => {
next()
})
let app = createApp(App).use(Router);
app.mount('#qiankun');
initQiankun(app);
//处理浏览器前进后退
(() => {
window.addEventListener("popstate", function () {
let hashHistory = sessionStorage.getItem('hashHistory');
if (!hashHistory) hashHistory = [];
else
hashHistory = JSON.parse(hashHistory)
let current = window.location.href.split('#')[1];
if (current == 'undefined') {
Router.replace('/')
return;
}
//如果不是微应用的路由地址,不做操作
let length = apps.filter(item => {
return current.indexOf(item.name) == 1;
}).length;
if (length == 0) return;
//如果还没有缓存,放入数组中缓存起来
if (hashHistory.indexOf(current) == -1)
hashHistory.push(current);
sessionStorage.setItem('hashHistory', JSON.stringify(hashHistory));
/// console.log(11111, hashHistory)
}, false)
window.addEventListener("hashchange", function (e) {
// console.log('hashchange')
let hashHistory = sessionStorage.getItem('hashHistory');
if (!hashHistory) return
hashHistory = JSON.parse(hashHistory)
let current = e.newURL.split('#')[1] || '';
//如果是缓存记录中的路由地址,不做操作
let arr = hashHistory.filter(item => {
return item == current;
})
if (arr.length > 0) return;
//查找出包含此路由地址的缓存路由,最后替换路由地址
let temp = hashHistory.filter((item) => {
return item.indexOf(current) != -1;
})
Router.replace(temp[0])
}, false);
})()
Vue2脚手架项目接入
1在src目录下创建文件public-path.js,内容如下
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2修改main.js文件
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import router from './router/index';
import store from './store';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import request from '@/utils/request/request'
import Security from '@/utils/request/EncryptionTools/index'
import settings from '@/utils/request/settings'
Vue.use(Security.Security)
Vue.use(ElementUI);
Vue.prototype.request = request;
let instance = null;
function render (props = {}) {
const { container } = props;
instance = new Vue({
router,
store,
render: (h) => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap () {
console.log('[vue] vue app bootstraped');
}
export async function mount (props) {
console.log('[vue] props from main framework', props);
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev, props.setGlobalState);
});
render(props);
}
export async function unmount () {
instance.$destroy();
instance.$el.innerHTML = '';
}
主要修改:
- 引入public-path.js
- 声明并导出方法bootstrap、mount、unmount 三个方法
- 根据加载方式不同将生成的html节点绑定到不同的根节点上
3.修改vue.config.js配置文件
const { name } = require('./package');
const publicPath = process.env.NODE_ENV === 'production' ?
'http://localhost:82/app2' : ``;
module.exports = {
publicPath: '/app2/',
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
chainWebpack: (config) => {
const fontRule = config.module.rule('fonts');
fontRule.uses.clear();
fontRule
.use('file-loader')
.loader('file-loader')
.options({
name: 'fonts/[name].[hash:8].[ext]',
publicPath,
})
.end();
const imgRule = config.module.rule('images');
imgRule.uses.clear();
imgRule
.use('file-loader')
.loader('file-loader')
.options({
name: 'img/[name].[hash:8].[ext]',
publicPath,
})
.end();
},
};
说明:
- 地址http://localhost:82/app2为该应用部署后的访问地址,主要是为了将资源文件中的相对地址替换成绝对地址
- app2这个名称必须是唯一的,为了解决加载微应用时的跨域问题
- 设置开发模式下允许跨域访问,方便开发环境调试
4.修改router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '',
name: 'Home',
component: Home
},
{
path: '/',
name: 'Home',
component: Home
},
];
if (window.__POWERED_BY_QIANKUN__) {
let prefix = '/app2' // 为主应用的activeRule
routes.map(item => {
item.path = prefix + item.path;
});
}
const router = new VueRouter({
mode: 'hash',
routes
});
if (window.__POWERED_BY_QIANKUN__) {
router.beforeEach((to, from, next) => {
if (!to.path.includes('/app2')) { // /workbench为主应用的activeRule
next({ path: prefix + to.path })
} else { next() }
})
}
export default router;
Vue3脚手架项目接入
1在src目录下创建文件public-path.js,内容如下
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2修改main.js文件/* eslint-disable */
/* eslint-disable */
// @ts-nocheck
import './public-path.ts'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
let app;
function render (props = {}) {
const { container } = props;
app = createApp(App);
app.config.globalProperties.$props = props;
app.use(router).mount(container ? container.querySelector('#app') : '#app')
//app.config.globalProperties.$props
props.setGlobalState && props.setGlobalState({ pageName: '123' })
}
// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap () {
console.log("VueMicroApp bootstraped");
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount (props) {
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev, props.setGlobalState);
});
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount () {
console.log("VueMicroApp unmount");
}
主要修改:
- 引入public-path.js
- 声明并导出方法bootstrap、mount、unmount 三个方法
- 根据加载方式不同将生成的html节点绑定到不同的根节点上
3.修改vue.config.js配置文件
const { ReplacePath } = require('./lib/replace-path.js')
const { name } = require('./package')
const path = require('path')
const publicPath = process.env.NODE_ENV === 'production' ? 'http://localhost:82/app1/' : `/app1/`;
module.exports = {
publicPath,
devServer: {
headers: {
//允许跨域
'Access-Control-Allow-Origin': '*',
}
},
configureWebpack: {
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${name}`,
},
plugins: [
new ReplacePath({})
]
},
}
ReplacPath插件代码
/** 自定义的插件: 替换一个全局变量 */
const fs = require('fs')
const path = require('path')
class ReplacePath {
apply (compiler) {
if (process.env.NODE_ENV === 'development')
return;
compiler.hooks.afterEmit.tap("ReplacePath", (compilation) => {
compilation.chunks.forEach(chunk => {
let pathHeader = path.resolve(__dirname.replace('lib', ''), 'dist');
chunk.files.map(fileName => {
if (!/.js$/.test(fileName)) return;
let source = fs.readFileSync(path.resolve(pathHeader, fileName), { encoding: 'utf-8' });
source = source.replace(/window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__/, `"${compilation.options.output.publicPath}"`);
fs.writeFileSync(path.resolve(pathHeader, fileName), source, { encoding: 'utf-8' });
})
});
});
}
}
module.exports = { ReplacePath };
- app1这个名称必须是唯一的,为了解决加载微应用时的跨域问题
- 设置开发模式下允许跨域访问,方便开发环境调试
4.修改router.js
import { createRouter, createWebHashHistory } from 'vue-router'
import TestText from '../views/test/test-text.vue'
import TestText2 from '../views/test2/test-text.vue'
import TestText3 from '../views/test3/test-text.vue'
let routes = [
{
path: '/',
component: TestText
},
{
path: '/page2',
component: TestText2
},
{
path: '/page3',
component: TestText3
},
];
const router = createRouter({ routes, history: createWebHashHistory(window.__POWERED_BY_QIANKUN__ ? '#/app1' : '') });
export default router;
Nginx部署
Nginx配置如下
worker_processes 2;
events {
worker_connections 1024;
}
http {
proxy_cache_path /tmp/nginxcache levels=1:2 keys_zone=app:10m max_size=10g inactive=1m use_temp_path=off;
include mime.types;
default_type application/octet-stream;
keepalive_timeout 65;
server{
listen 81;
server_name localhost;
# gzip on;
# gzip_min_length 1k;
# gzip_buffers 4 16k;
location / {
root html/main/; #你的项目的根目录
index index.html index.htm;
autoindex on;
}
location /app1/ {
proxy_pass http://localhost:82/app1/;
proxy_set_header Host $host:$server_port;
proxy_cache app; #开启缓存 off关闭缓存
proxy_cache_valid 200 304 12h; #表示对200 304头信息12小时过期
proxy_cache_valid any 10m; #表示除200 304头信息10钟过期
add_header Nginx-Cache "$upstream_cache_status";
}
location /app2/ {
proxy_pass http://localhost:82/app2/;
proxy_set_header Host $host:$server_port;
proxy_cache app; #开启缓存 off关闭缓存
proxy_cache_valid 200 304 12h; #表示对200 304头信息12小时过期
proxy_cache_valid any 10m; #表示除200 304头信息10钟过期
add_header Nginx-Cache "$upstream_cache_status";
}
access_log logs/k8s.log;
error_log logs/zhaoxin_error.log;
charset utf-8; # 编码
error_page 500 502 503 504 /50x.html;
}
server{
listen 82;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#允许跨域请求的域,*代表所有
add_header 'Access-Control-Allow-Origin' *;
#允许带上cookie请求
add_header 'Access-Control-Allow-Credentials' 'true';
#允许请求的方法,比如 GET/POST/PUT/DELETE
add_header 'Access-Control-Allow-Methods' *;
#允许请求的header
add_header 'Access-Control-Allow-Headers' *;
location / {
root html/; #你的项目的根目录
index index.html index.htm;
}
}
}
- 基座部署的时候需要设置代理,并开启缓存(否则加载会很慢)
- 微应用部署的时候需要设置允许跨域访问,可以限制下资源类型,主要是当加载微应用的时候一些图片字体等资源需要跨域访问