项目架构
- 主应用:umi 搭建的框架
- 子应用:react
- 子应用:也是 umi 搭建的框架
- 子应用:vue3
要达到的效果:
主应用配置
umi 搭建主应用
安装 qiankun
yarn add qiankun
安装 @umijs/plugin-qiankun
yarn add @umijs/plugin-qiankun
注册子应用
在项目根目录的 .umirc.ts
中配置:
{
...
qiankun: {
master: {
// 注册子应用信息
apps: [
{
name: 'micro-react', // 唯一 id ,必须与路由中的 microApp 保持一致
entry: 'http://localhost:8001/', // html entry
},
{
name: 'micro-umi', // 唯一 id ,必须与路由中的 microApp 保持一致
entry: 'http://localhost:8002', // html entry
},
]
}
}
}
配置路由
我这里是在
config/routes.ts
进行路由的统一管理的,然后在.umirc.ts
引入:import { defineConfig } from 'umi'; import routes from './config/routes'; export default defineConfig({ routes, qiankun: { ... }, });
config/routes.ts
:
{
name: "react 子应用", // 侧边栏的菜单名
path: "/micro-react", // 跳转的路径
icon: 'smile', // umi 中默认使用 antd 的 icon 名
microApp: "micro-react", // 注册子应用的名称,与 .umirc.ts 中的 qiankun 里 的 name 保持一致
},
{
name: 'umi 子应用',
path: '/micro-umi',
icon: 'smile',
microApp: 'micro-umi'
},
子应用配置
react 子应用
搭建 react 项目
确保项目能正常启动:
npx create-react-app micro-react
cd micro-react
yarn start
安装 react-app-rewired
yarn add react-app-rewired -D
修改运行时的 publicPath
在 src/
下新建 public-path.js
,内容如下:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
/**
* 坑
* 直接放index.js import 下面:微应用中静态资源(图片)不显示
* 直接放index.js 最上方 运行报错
*/
引入 public-path.js 并导出生命周期
将入口文件 src/index.js
的所有代码替换为:
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
function render(props) {
const { container } = props;
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
export async function mount(props) {
console.log('[react16] props from main framework', props);
render(props);
}
export async function unmount(props) {
const { container } = props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
export async function update(props) {
console.log('update props', props);
}
修改 webpack 配置
我们已经安装了 react-app-rewired
,因此在根目录下新建 config-overrides.js
文件,添加下面的代码
如果安装的是
@rescripts/cli
,则在根目录新增.rescriptsrc.js
。
const { name } = require('./package');
module.exports = {
webpack: (config, env) => {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = 'umd';
config.output.globalObject = 'window';
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
return config;
},
devServer: (_) => {
const config = _;
config.headers = {
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback = true;
config.hot = false;
config.watchContentBase = false;
config.liveReload = false;
config.injectClient = false;
return config;
}
}
修改 package.json 的配置
将 package.json
下的 script
修改为:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
},
管理端口
在项目根目录下新建 .env
文件,便于管理端口号:
PORT=8001
启动子应用服务
yarn start
umi 子应用
安装 @umijs/plugin-qiankun
yarn add @umijs/plugin-qiankun -D
注册刚安装的插件
跟主应用一样,在项目根目录的 .umirc.ts
中配置:
qiankun: {
slave: {}
}
添加后如下所示:
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{ path: '/', component: '@/pages/index' },
],
fastRefresh: {},
qiankun: {
slave: {}
}
});
导出相应生命周期钩子
在 src/app.ts
中添加如下代码,如果 app.ts
不存在则新建一个即可:
export const qiankun = {
// 应用加载之前
async bootstrap(props) {
console.log('app1 bootstrap', props);
},
// 应用 render 之前触发
async mount(props) {
console.log('app1 mount', props);
},
// 应用卸载之后触发
async unmount(props) {
console.log('app1 unmount', props);
},
};
管理端口
在项目根目录下新建 .env
文件,便于管理端口号:
PORT=8002
启动子应用服务
yarn start
vue 子应用
搭建 vue 项目
确保项目能正常启动:
vue create micro-vue
cd micro-vue
yarn serve
修改运行时的 publicPath
在 src/
下新建 public-path.js
,写入如下代码:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
引入 public-path.js 并导出生命周期
import "./public-path"
import { createApp } from "vue"
import App from "./App.vue"
let app = createApp(App)
function render(props = {}) {
const { container } = props
app.mount(container ? container.querySelector("#vueApp3") : "#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)
render(props)
}
export async function unmount() {
app.unmount()
app = null
}
修改 webpack 配置
将根目录下的 vue.config.js
的代码全部替换为:
// 注意:这里的 name 要与主应用中注册的子应用名称保持一致
const { name } = require('./package');
module.exports = {
devServer: {
port: 8003, // 监听端口
// 关闭主机检查,使微应用可以被 fetch
// disableHostCheck: true,
headers: { // 配置跨域请求头,解决开发环境的跨域问题
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
// 子应用的名称
library: `${name}-[name]`,
// 将你的 library 暴露为所有的模块定义下都可运行的方式
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
// 按需加载相关,设置为 webpackJsonp_微应用名称 即可
chunkLoadingGlobal: `webpackJsonp_${name}`,
},
},
};
启动子应用服务
yarn serve
小 tip : 可以通过
yarn serve -- --port 端口号
的方式指定服务端口号
遇到的问题
- 主应用连接子应用时:
Unhandled Rejection (TypeError): Failed to fetch
![Failed to fetch](./微前端qiankun实践/Failed to fetch.png)
ValidationError: Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
![Invalid configuration object](./微前端qiankun实践/Invalid configuration object.png)
这里报错,是因为是 webpack5.x 不兼容 jsonpFunction
的写法,需要替换成 chunkLoadingGlobal
。
- 启动 vue 子应用服务时:
'__webpack_public_path__' is not defined
![‘__webpack_public_path__’ is not defined](./微前端qiankun实践/webpack_public_path is not defined.png)
这个报错是 eslint 的问题, __webpack_public_path__
不是全局变量所导致的。在子应用 package.json
文件中 eslintConfig
配置全局变量后 重起服务 解决:
"eslintConfig": {
...,
"globals": {
"__webpack_public_path__": true
}
}
动态注册子应用
删除子应用注册的配置
删除 .umirc.ts
中的下面代码中带 ‘-‘ 的行,其余保持不变:
qiankun: {
master: {
// 注册子应用信息
- apps: [
- {
- name: 'micro-react', // 唯一 id
- entry: 'http://localhost:8001/', // html entry
- },
- {
- name: 'micro-umi', // 唯一 id
- entry: 'http://localhost:8002', // html entry
- },
- {
- name: 'micro-vue', // 唯一 id
- entry: 'http://localhost:8003', // html entry
- },
- ],
},
},
配置 mock
在 mock
文件夹下新建 config.ts
,写入以下代码:
export default {
'/api/getMicro': {
apps: [
{
name: 'micro-react', // 唯一 id
entry: 'http://localhost:8001/', // html entry
},
{
name: 'micro-umi', // 唯一 id
entry: 'http://localhost:8002', // html entry
},
{
name: 'micro-vue', // 唯一 id
entry: 'http://localhost:8003', // html entry
},
]
}
}
运行时动态配置子应用
根据 官网 所说,该配置是在 src/app.ts
中开启的,因此:
在 src/
新建 app.ts
,写入以下代码:
// 从接口中获取子应用配置,export 出的 qiankun 变量是一个 promise
export const qiankun = fetch('/api/getMicro') // 这里的接口为上文中 mock/config.ts 配置的接口
.then(res => {
return res.json();
})
.then(({ apps }) => ({
// 注册子应用信息
apps,
// 完整生命周期钩子请看 https://qiankun.umijs.org/zh/api/#registermicroapps-apps-lifecycles
lifeCycles: {
afterMount: (props) => {
console.log(props);
},
},
// 支持更多的其他配置,详细看这里 https://qiankun.umijs.org/zh/api/#start-opts
}));
效果
可以看到,主应用依然能正确加载子应用。
主应用动态装载子应用路由
TODO
- 非 umi 搭建主应用
- 主应用动态装载子应用(路由)
- 父子应用通讯