微前端就是将不同的功能按照不同的未读拆分成多个子应用,通过主应用来加载这些子应用,微前端的核心在于拆,拆完后再合
我们可以将一个应用划分成若干哥子应用,将子应用打包成一个个的lib,当路径切换时加载不同的子应用,这样每个子应用都是独立的,技术栈也不用做限制了!从而解决了前端协同开发的问题
文档地址:https://qiankun.umijs.org/zh
2018 年 Single-SPA 诞生了, single-spa 是一个用于前端微服务化的 JavaScript 前端解决方案 ( 本身没有处理样式隔离, js 执行隔离 ) 实现了路由劫持和应用加载。
2019 年 qiankun 基于 Single-SPA, 提供了更加开箱即用的 API ( single-spa + sandbox + import-html-entry ) 做到了,技术栈无关、并且接入简单(像 i frame 一样简单)。
这里打算建立三个项目进行实操,都是用的vue技术栈(这里不限技术栈),qiankun-base充当主应用,qiankun-vue和qiankun-vue1充当子应用

vue create qiankun-base vue create qiankun-vue vue create qiankun-vue1 三个项目:基座:qiankun-base,子应用:qiankun-vue,qiankun-vue1
注意:这里如果时选的vue2.x创建的项目,之后用router和store的时候要注意版本号和写法的对应
1、基座qiankun-base配置
项目创建好后我们首先进行主应用qiankun-base的配置,进入man.js文件进行配置,在main.js中加入以下代码,要注意的是,entry这项配置是我们两个子项目的域名和端口,我们必须确保两个子项目运行在这两个端口上面,container就是我们的容器名,就是我们子应用挂载的节点,相当于Vue项目里面的app节点,activeRule就是我们的激活路径,根据路径来显示不同的子应用
yarn add qiankun 或者 npm i qiankun -S
- import Vue from 'vue'
- import App from './App.vue'
- import router from './router'
- import ElementUI from 'element-ui'
- import 'element-ui/lib/theme-chalk/index.css';
- import less from 'less';
- import Vuex from 'vuex';
-
- // 引入乾坤
- import { registerMicroApps, start, initGlobalState } from 'qiankun';
- import store from './store/store'
-
- Vue.use(ElementUI)
- Vue.use(less)
- Vue.use(Vuex)
- Vue.config.productionTip = false
-
- let propsData = {
- sex: '女',
- age: 18,
- userName: '小莫'
- }
- const actions = initGlobalState(propsData);
- // 主项目项目监听和修改(在项目中任何需要监听的地方进行监听)
- actions.onGlobalStateChange((state, prev) => {
- //state变更后的状态,prev变更前的状态
- console.log("改变前的值", prev);
- console.log("改变后的值", state);
- });
- //将actions对象绑到Vue原型上,为了项目中其他地方方便使用
- Vue.prototype.$actions = actions
-
- const apps = [
- {
- name: 'vueApp1',//应用的名字
- entry: '//localhost:8081',//默认会加载这个html解析里面的js动态的执行(子应用必须支持跨域)fetch
- container: '#vue1',//容器名
- activeRule: '/main/vue',//激活的路径
- props: propsData,
- },
- {
- name: 'vueApp2',//应用的名字
- entry: '//localhost:8082',//默认会加载这个html解析里面的js动态的执行(子应用必须支持跨域)fetch
- container: '#vue2',//容器名
- activeRule: '/main/vue1',//激活的路径
- props: propsData,
- }
- ]
-
- registerMicroApps(apps);//注册应用
- start({
- prefetch: false//取消预加载
- });//开启
- new Vue({
- router,
- store,
- render: h => h(App),
- }).$mount('#app')
- <!--
- * @Author: mowencong 2829621014@qq.com
- * @Date: 2022-08-26 16:51:38
- * @LastEditors: mowencong 2829621014@qq.com
- * @LastEditTime: 2022-08-29 10:11:09
- * @FilePath: \qiankun\qiankun-base\src\views\Main\index.vue
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- -->
- <template>
- <div class="main-content">
- <el-container>
- <!-- 顶部 -->
- <el-header>
- <router-link to="/home">
- <img src="../../assets/logo.png" class="logo" />
- </router-link>
- </el-header>
- <el-container>
- <!-- 左侧导航 -->
- <el-aside>
- <el-menu
- default-active="1"
- class="el-menu-vertical-demo"
- background-color="#545c64"
- text-color="#fff"
- :router="true"
- >
- <!--基座中可以放自己的路由-->
- <el-menu-item index="/home">
- <i class="el-icon-menu"></i>
- <span slot="title">Home</span>
- </el-menu-item>
- <el-menu-item index="/about">
- <i class="el-icon-menu"></i>
- <span slot="title">About</span>
- </el-menu-item>
- <!--引用其他子应用-->
- <el-menu-item index="/main/vue">
- <i class="el-icon-document"></i>
- <span slot="title">qiankun-vue1</span>
- </el-menu-item>
- <el-menu-item index="/main/vue1">
- <i class="el-icon-setting"></i>
- <span slot="title">qiankun-vue2</span>
- </el-menu-item>
- </el-menu>
- </el-aside>
- <el-container>
- <!-- 内容 -->
- <el-main>
- <router-view></router-view>
- <!--这里就是子应用挂载的节点,跟main.js里面的配置一一对应-->
- <div id="vue1"></div>
- <div id="vue2"></div>
- </el-main>
- </el-container>
- </el-container>
- </el-container>
- </div>
- </template>
-
- <script>
- import { start } from "qiankun";
-
- export default {
- name: "Main",
- mounted() {
- if (!window.qiankunStarted) {
- window.qiankunStarted = true;
- start();
- }
- },
- };
- </script>
-
- <style lang='less' scoped>
- .main-content {
- height: 100%;
- & > .el-container {
- height: 100%;
- .el-header {
- background-color: #3f4044;
- display: flex;
- align-items: center;
- .logo {
- height: 40px;
- width: auto;
- cursor: pointer;
- }
- }
- & > .el-container {
- height: 100%;
- & > .el-aside {
- background-color: #d3dce6;
- width: 200px !important;
- .el-menu-vertical-demo {
- height: 100%;
- }
- }
- .el-container {
- .el-main {
- // background-color: #e9eef3;
- // color: #333;
- }
- }
- }
- }
- }
- </style>
2、子应用qiankun-vue配置
main.js配置
- /*
- * @Author: mowencong 2829621014@qq.com
- * @Date: 2022-08-26 15:57:22
- * @LastEditors: mowencong 2829621014@qq.com
- * @LastEditTime: 2022-08-29 10:50:43
- * @FilePath: \qiankun\qiankun-vue\src\main.js
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
- import Vue from 'vue'
- import App from './App.vue'
- import router from './router'
- import ElementUI from 'element-ui'
- import 'element-ui/lib/theme-chalk/index.css';
- Vue.use(ElementUI)
- Vue.config.productionTip = false
-
- let instance = null
- function render(props){
- instance = new Vue({
- router,
- render:h => h(App)
- }).$mount('#qkApp1');//这里是挂载到自己的html中,基座会拿到这个挂载后的html,将其插入进去
- //注意:这里#qkApp1在index.html和app.vue的挂载点的id名要同步修改
- }
- if(window._POWERED_BY_QIANKUN_){//动态添加publicPath
- _webpack_public_path_ = window._INJECTED_PUBLIC_PATH_BY_QIANKUN_;
- }
- if(!window._POWERED_BY_QIANKUN_){//默认独立运行
- render();
- }
- //父应用加载子应用,子应用必须暴露三个接口:bootstrap,mount,unmount
- //子组件的协议就ok了
- export async function bootstrap(props){};
- export async function mount(props){
- //加了true之后,会自动调取前面这个回调方法,这样可以拿到主应用(基座)修改的值
- props.onGlobalStateChange((state,prev) => {
- console.log(state,prev);
- },true);
- Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange;
- Vue.prototype.$setGlobalState = props.setGlobalState;
- console.log('是啥类型',Object.prototype.toString.call(props.onGlobalStateChange))
- render(props);
- }
- export async function unmount(props){
- instance.$destroy();
- }
router.js的配置
- /*
- * @Author: mowencong 2829621014@qq.com
- * @Date: 2022-08-26 17:05:52
- * @LastEditors: mowencong 2829621014@qq.com
- * @LastEditTime: 2022-08-29 09:37:57
- * @FilePath: \qiankun\qiankun-vue\src\router\index.js
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
- import Vue from 'vue'
- import VueRouter from 'vue-router'
- import Home from '../views/Home.vue'
- Vue.use(VueRouter);
- const routes = [
- {
- path: '/',
- redirect: '/main/vue/'
- },
- {
- path: '/main/vue/',
- name: 'Home',
- component: Home
- },
- {
- path: '/main/vue/about',
- name: 'About',
- component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
- }
- ]
- const router = new VueRouter({
- mode: 'history',
- base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",//基础路径
- routes
- })
-
- export default router
-
- //注意:路由前缀必须设置,这个跟基座main.js的激活路径相关
vue.config.js的配置
- /*
- * @Author: mowencong 2829621014@qq.com
- * @Date: 2022-08-26 15:57:22
- * @LastEditors: mowencong 2829621014@qq.com
- * @LastEditTime: 2022-08-29 10:08:14
- * @FilePath: \qiankun\qiankun-vue\vue.config.js
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
- module.exports = {
- lintOnSave:false,//关闭eslint检测
- devServer:{
- port:8081,//这里的端口必须和父应用配置的子应用端口一致
- headers:{
- //因为qiankun内部请求都是fetch请求资源,所以子应用必须允许跨域
- 'Access-Control-Allow-Origin':'*',
- }
- },
- configureWebpack:{
- output:{
- //资源打包路径
- library:'vueApp1',
- libraryTarget:'umd'
- }
- }
- }
3、注意点
1)如何在主应用的某个路由页面加载微应用
react + react-router技术栈的主应用:只需要让子应用的activeRule包含主应用的这个路由即可
vue + vue-router技术栈的主应用
例如:主应用需要在login页面登录,登录成功后跳转到main后台管理页面,在main管理页面下可以显示子应用
修改主应用router.js
- //如果这个路由有其他子路由,需要另外注册一个路由,仍然使用这个子组件即可
- //本案例就是有子路由,所以需要才后面重新定义main页面的路由
- const routes = [
- {
- path:'/',
- name:'Login',
- component:Login
- },
- {
- path:'/main',
- name:'Main',
- component:Main,
- children:[
- {
- path:'/home',
- name:'Home',
- component:Home
- },
- {
- path: '/about',
- name: 'About',
- // route level code-splitting
- // this generates a separate chunk (about.[hash].js) for this route
- // which is lazy-loaded when the route is visited.
- component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
- }
- ]
- },
- {
- path: '/main/*',
- name: 'Main',
- component: Main,
- }
- ]
修改主应用main.js 的文件
- // 子应用的 activeRule 需要包含主应用的这个路由 path
- const apps = [
- {
- name: 'vueApp', // 应用的名字
- entry: '//localhost:8081', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
- container: '#vue', // 容器名
- activeRule: '/main/vue', // 激活的路径
- props: { a: 1 }
- },
- {
- name: 'reactApp',
- entry: '//localhost:20000', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
- container: '#react',
- activeRule: '/main/react',
- }
- ]
- registerMicroApps(apps); // 注册应用