完整代码地址在microfrontend-learning
1. 创建项目
主应用是使用vue开发,两个子应用分别是vue、react, 创建命令如下:
# 创建主应用
vue create app-main
# 创建一个app-vue的子应用
vue create app-vue
# 创建一个app-react的子应用
npm install -g create-react-app
npx create-react-app app-react
2. 主应用改造
安装qiankun、ant-design-vue、vue-router
yarn add qiankun ant-design-vue vue-router # 或者 npm i qiankun ant-design-vue vue-router -S
可编写配置文件.env.dev 用来方便配置子应用启动地址
# .env.dev 用于本地构建测试
VUE_APP_REACT_MICRO_APP=http://localhost:3000
VUE_APP_VUE_MICRO_APP=http://localhost:9000
新建micro.js
import {
registerMicroApps,
start,
} from "qiankun"
// 子应用注册信息
/**
* name: 微应用名称 - 具有唯一性
* entry: 微应用入口 - 通过该地址加载微应用
* container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
* activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
*/
const apps = [{
name: "ReactMicroApp",
entry: process.env.VUE_APP_REACT_MICRO_APP,
container: "#frame",
activeRule: "/react"
},
{
name: "VueMicroApp",
entry: process.env.VUE_APP_VUE_MICRO_APP,
container: "#frame",
activeRule: "/vue"
}]
/**
* 注册子应用
* 第一个参数 - 子应用的注册信息
* 第二个参数 - 全局生命周期钩子
*/
registerMicroApps(apps, {
// qiankun 生命周期钩子 - 加载前
beforeLoad: (app) => {
console.log("before load", app.name);
return Promise.resolve();
},
// qiankun 生命周期钩子 - 挂载后
afterMount: (app) => {
console.log("after mount", app.name);
return Promise.resolve();
},
});
// 导出 qiankun 的启动函数
export default start;
在 app.vue 中用 v-if / v-show去控制是路由显示还是加载的子项目显示
<template>
<div id="main-app">
<section class="content-wrapper">
<!-- 主应用渲染区,用于挂载主应用路由触发的组件 -->
<router-view v-show="$route.name" />
<!-- 子应用渲染区,用于挂载子应用节点 -->
<section v-show="!$route.name" id="frame"></section>
</section>
</div>
</template>
// ...
入口文件 main.js
修改,为了避免根 id #app
与其他的 DOM 冲突,将id改为main-app
配置vue.config.js
const path = require("path");
module.exports = {
configureWebpack: {
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
},
devServer: {
port: 8888,
open: true,
disableHostCheck: true,
},
};
3. Vue子应用改造
在src下中新建public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
修改main.js
import Vue from 'vue'
import VueRouter from "vue-router"
import Antd from "ant-design-vue"
import "ant-design-vue/dist/antd.css"
import "./public-path"
import App from './App.vue'
import routes from './router'
Vue.use(VueRouter)
Vue.use(Antd)
Vue.config.productionTip = false
let instance = null
let router = null
/**
* 渲染函数
* 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
*/
function render() {
// 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染
router = new VueRouter({
// 运行在主应用中时,添加路由命名空间 /vue
base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",
mode: "history",
routes,
});
// 挂载应用
instance = new Vue({
router,
render: (h) => h(App),
}).$mount("#app")
}
// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log("VueMicroApp bootstraped")
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log("VueMicroApp mount", props)
render(props)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
console.log("VueMicroApp unmount");
instance.$destroy()
instance = null
router = null
}
打包配置修改(vue.config.js
)
const path = require('path');
// const { name } = require('./package');
module.exports = {
devServer: {
// 监听端口
port: 9000,
// 关闭主机检查,使微应用可以被 fetch
disableHostCheck: true,
// 配置跨域请求头,解决开发环境的跨域问题
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
}
},
output: {
// 微应用的包名,这里与主应用中注册的微应用名称一致
library: 'VueMicroApp',
// 将你的 library 暴露为所有的模块定义下都可运行的方式
// 把微应用打包成 umd 库格式
libraryTarget: 'umd',
// 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
jsonpFunction: `webpackJsonp_VueMicroApp`,
}
}
}
4. React子应用改造
在 src
目录新增 public-path.js
:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
注意: 根据 qiankun 的文档进行配置后,启动 子应用vue 时报 __ webpack_public_path__ 未定义,是 eslint 的问题, webpack_public_path 不是全局变量所导致的, 需要在子应用 package.json 文件中 eslintConfig 配置全局变量后 重起服务 解决
"eslintConfig": {
...,
"globals": {
"__webpack_public_path__": true
}
},
设置 history
模式路由的 base
:
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/react' : '/'}>
入口文件 index.js
修改
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "antd/dist/antd.css";
import "./public-path";
import App from "./App";
/**
* 渲染函数
* 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
*/
function render(props) {
// const { container } = props;
ReactDOM.render(<App />, document.querySelector("#root"));
}
// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log("ReactMicroApp bootstraped");
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log("ReactMicroApp props from main framework", props);
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
const { container } = props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector("#root") : document.querySelector("#root"));
}
修改 webpack
配置
安装插件react-app-rewired
npm i -D react-app-rewired
根目录增加config-overrides.js
const path = require("path");
const name = "ReactMicroApp";
module.exports = {
webpack: (config) => {
// 微应用的包名,这里与主应用中注册的微应用名称一致
config.output.library = `${name}`;
// 将你的 library 暴露为所有的模块定义下都可运行的方式
config.output.libraryTarget = "umd";
// 按需加载相关
config.output.jsonpFunction = `webpackJsonp_${name}`;
config.output.globalObject = "window";
config.resolve.alias = {
...config.resolve.alias,
"@": path.resolve(__dirname, "src"),
};
return config;
},
devServer: function (configFunction) {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
// 关闭主机检查,使微应用可以被 fetch
config.disableHostCheck = true;
// 配置跨域请求头,解决开发环境的跨域问题
config.headers = {
"Access-Control-Allow-Origin": "*",
};
// 配置 history 模式
// config.historyApiFallback = true;
return config;
};
}
};
修改package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
最后启动效果如下:
参考资料: