qiankun官网
1.什么是微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。简单来说就是就是可以把多个不同框架的项目部署在不同环境,但又可以在一个项目中访问。
2.他是做什么的
他可以将不同框架的多个项目组成统一项目,而组成项目的子项目又可以单独部署单独访问。可以说他们的关系是互相关联而又互不关联。
说明:由于项目还并没有进行上线测试,此代码还不能保证上线没有问题,如有问题请与我交流沟通。
主项目代码实现
1.首先我们需要建立.env.development,.env.production,.ent.qa。3个文件,这3个文件的作用是为了配置前端打包项目路径配置,方便上线,测试,开发环境的区分(注意:文件内配置必须为VUE_APP_开头,否则后面用到的时候不能进行解析)
env.development
NODE_ENV = 'development'
// 开发主项目
VUE_APP_BASE_URL = 'http://localhost:8080'
// 开发子项目
VUE_APP_APP_URL = 'http://localhost:8081'
VUE_APP_SERVICEMESH_URL = 'http://localhost:8082'
VUE_APP_RESOURCE_URL = 'http://localhost:8083'
VUE_APP_MONITOR_URL = 'http://localhost:8084'
VUE_APP_SYSTEM_URL = 'http://localhost:8085'
env.production
NODE_ENV = 'production'
// 生产主项目
VUE_APP_BASE_URL = 'http://localhost:8080'
// 生产子项目
VUE_APP_APP_URL = 'http://localhost:8081'
VUE_APP_SERVICEMESH_URL = 'http://localhost:8082'
VUE_APP_RESOURCE_URL = 'http://localhost:8083'
VUE_APP_MONITOR_URL = 'http://localhost:8084'
VUE_APP_SYSTEM_URL = 'http://localhost:8085'
env.qa
NODE_ENV = 'qa'
// 测试主项目
VUE_APP_BASE_URL = 'http://localhost:8080'
// 测试子项目
VUE_APP_APP_URL = 'http://localhost:8081'
VUE_APP_SERVICEMESH_URL = 'http://localhost:8082'
VUE_APP_RESOURCE_URL = 'http://localhost:8083'
VUE_APP_MONITOR_URL = 'http://localhost:8084'
VUE_APP_SYSTEM_URL = 'http://localhost:8085'
package.json增加
"scripts": {
"serve": "vue-cli-service serve ",
"build": "vue-cli-service build ",
"lint": "vue-cli-service lint",
"serve-qa": "vue-cli-service serve --mode qa",
"serve-product": "vue-cli-service serve --mode production",
"build-qa": "vue-cli-service build --mode qa",
"build-product": "vue-cli-service build --mode production"
},
主项目router.js
import {
createRouter,
createWebHashHistory
} from 'vue-router';
import store from '../store/store';
// 采用路由懒加载方式
const login = () => import('../components/static/Login.vue');
const Home = () => import('../components/static/Home.vue');
const router = createRouter({
history: createWebHashHistory(),
routes: [{
path: '/',
redirect: login,
name: '登录'
}, {
path: '/login',
component: login,
name: '登录'
}, {
path: '/home',
name: 'Home',
component: Home
}]
});
export default router;
主项目src下创建micro文件夹,
micro/apps.js 用来配置子项目路由
// /src/micfo/apps.js
//process.env为上边配置的路径第值
console.log(process.env);
const apps = [{
name: 'kem-app-name',
entry: process.env.VUE_APP_APP_URL,
container: '#KEMAPP',//承载子项目的div的id值必须与此对应
activeRule: '#/kem-app',//激活子项目的路由
sandbox: {
strictStyleIsolation: true // 开启样式隔离
}
}];
export default apps;
mico/index.js
// src/mico/index.js
import {
registerMicroApps,
addGlobalUncaughtErrorHandler,
start,
initGlobalState
} from 'qiankun';
import apps from './apps';
registerMicroApps(apps, {
beforeLoad: [
app => {
console.log("before load", app.name);
return Promise.resolve();
},
],
beforeMount: [
app => {
console.log("before mount", app.name);
return Promise.resolve();
},
],
afterUnmount: [
app => {
console.log("after mount", app.name);
return Promise.resolve();
},
],
}, );
const state = {};
//主项目与子项目交互用的参数,子项目与主项目都可以修改此参数
const actions = initGlobalState(state);
actions.setGlobalState({
globalToken: ''
})
addGlobalUncaughtErrorHandler((event) => {
console.log(event);
const {
msg
} = event;
if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
console.log('微应用加载失败,请检查应用是否可运行');
}
});
export default start;
export {
actions
}
主项目的App.vue
<template>
<div>
<a-layout v-if="token" style="min-height: 100vh">
<a-layout-header>
<div class="titleBox">
123
</div>
</a-layout-header>
<a-layout>
<a-layout-sider v-model:collapsed="collapsed" collapsible>
<a-menu
v-model:selectedKeys="selectedKeys"
theme="dark"
mode="inline"
:open-keys="openKeys"
@openChange="onOpenChange"
>
<a-menu-item key="dashboard">
<HeartOutlined />
<span>dashboard</span>
</a-menu-item>
<a-sub-menu v-for="item in menu" :key="item.key">
<template #title>
<span>
<ReconciliationOutlined
v-else-if="item.title == 'dashboard'"
/>
<HeartOutlined v-else />
<span>{{ item.title }}</span>
</span>
</template>
<a-menu-item
v-for="itemchildren in item.children"
:key="itemchildren.title"
>
<router-link :to="itemchildren.url">{{
itemchildren.title
}}</router-link>
</a-menu-item>
</a-sub-menu>
</a-menu>
</a-layout-sider>
<a-layout-content>
<router-view />
// 此项的值与micfo/apps.js中apps中container的值对应
<div id="KEMAPP"></div>
</a-layout-content>
</a-layout>
</a-layout>
<router-view />
</div>
</template>
<script>
import { useStore } from 'vuex';
import {
AppstoreOutlined,
DatabaseOutlined,
FileSearchOutlined,
ToolOutlined,
SettingOutlined,
ReconciliationOutlined,
HeartOutlined
} from '@ant-design/icons-vue';
import {
defineComponent,
ref,
computed,
// onMounted,
toRefs,
reactive
} from 'vue';
// import { useRouter } from "vue-router";
import { logout } from '@/commons/common';
export default defineComponent({
name: 'App',
components: {
AppstoreOutlined,
DatabaseOutlined,
FileSearchOutlined,
ToolOutlined,
SettingOutlined,
ReconciliationOutlined,
HeartOutlined
},
setup() {
const store = useStore();
// 在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('store', JSON.stringify(store.state));
});
// const router = useRouter();
const state = reactive({
openKeys: ['']
});
const menu = ref([
{
title: '系统',
key: 'app',
children: [
{ title: '应用', url: '/kem-app/about' }
]
}
]);
const onOpenChange = openKeys => {
console.log(openKeys);
const latestOpenKey = openKeys.find(
key => state.openKeys.indexOf(key) === -1
);
if (openKeys.length == 0) {
state.openKeys = openKeys;
} else {
state.openKeys = latestOpenKey ? [latestOpenKey] : [];
}
};
return {
collapsed: ref(true),
selectedKeys: ref([]),
menu,
token: computed(() => store.state.token),
logout,
...toRefs(state),
onOpenChange
};
}
});
</script>
子项目 vue.config.js
const path = require('path');
const packageName = require('./package.json').name;
console.log(packageName);
function resolve(dir) {
return path.join(__dirname, dir);
}
const port = 8081; // dev port
const dev = process.env.NODE_ENV === 'development'
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: dev ? `//localhost:${port}` : '/',
outputDir: 'dist',
chainWebpack: config => {
// 删除 浏览器在⻚⾯加载完成后,利⽤空闲时间提前获取⽤户未来可能会访问的内容。
config.plugins.delete('prefetch');
// 压缩代码
config.optimization.minimize(true);
config.module.rule('images')
.use('url-loader')
.loader('url-loader')
.options({
limit: 4096, // ⼩于4kb将会被打包成 base64
fallback: {
loader: 'file-loader',
options: {
name: './img/[name].[hash:8].[ext]',
publicPath: dev ? `//localhost:${port}` : '/'
}
}
})
config.module
.rule('md')
.test(/\.md/)
.use('text-loader')
.loader('text-loader')
.end()
},
assetsDir: 'static',
filenameHashing: true,
// tweak internal webpack configuration.
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
devServer: {
// host: '0.0.0.0',
hot: true,
disableHostCheck: true,
port,
overlay: {
warnings: false,
errors: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
// 自定义webpack配置 packageName的值为package.json中name值
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
},
},
output: {
// 该值需要与主项目中micfo/apps.js apps中的name值相对应
library: `${packageName}-name`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
},
};
子项目src路径下创建2个文件夹shares和micro
shares/action.js
// 主应用与子应用字段交互方法
function emptyAction() {
// 警告:提示当前使用的是空 Action
console.warn("Current execute action is empty!");
}
class Actions {
// 默认值为空 Action
actions = {
onGlobalStateChange: emptyAction,
setGlobalState: emptyAction
};
/**
* 设置 actions
*/
setActions(actions) {
this.actions = actions;
}
/**
* 映射
*/
onGlobalStateChange(...args) {
return this.actions.onGlobalStateChange(...args);
}
/**
* 映射
*/
setGlobalState(...args) {
return this.actions.setGlobalState(...args);
}
}
const actions = new Actions();
export default actions;
micro/public-path.js
// /src/micro/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
完毕