目录
qiankun搭建主应用与子应用
微前端方案
iframe
子应用通过iframe嵌套到父应用之中
缺点:
1:url不同步,刷新页面后,iframe中的路由会丢失
2:全局上下文完全隔离,内存变量不共享
3:ui不同步(eg: iframe的只能在当前嵌套内容区展示)
4:加载慢
qiankun
基座与子应用概念
基座(主应用):主要负责集成所有的子应用,提供一个入口能够访问你所需要的子应用的展示,一般不写复杂逻辑
子应用:根据不同的业务划分的模块,每个子应用都打包成umd模块的形式供基座来加载
基座实现
- 概念:只是一个容器,用于提供子应用的存放与共享内存变量等
- 创建项目
npx create-react-app base
步骤:
1:安装qiankun : cnpm i qiankun
2:修改入口文件 index.js || index.ts
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { registerMicroApps, start } from 'qiankun'; // 0:引入qiankun
import { BrowserRouter, HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<HashRouter>
<App />
</HashRouter>
);
// 1:要加载的子应用列表
const apps = [
{
name:'sub-react', // 子应用名称
entry: '//localhost: 3001', // 默认会加载这个路径的htlm,解析里面的js
activeRule: '/sub-react', // 匹配的路由
container: '#sub-app' // 加载的容器 (渲染路由那边 App.jsx)
}
]
// 2:注册子应用 + qiankun的生命周期
registerMicroApps(apps, {
beforeLoad: [async app => {
console.log('beforeLoad', app.name);
}],
beforeMount: [async app => {
console.log('beforeMount', app.name);
}],
afterMount: [async app => {
console.log('afterMount', app.name);
}]
});
// 3:启动服务
start();
3:定义挂载子应用区域 App.jsx
import React, { Component,useEffect } from 'react';
import routers from './router'; // 路由文件
import { Routes,Route,Link } from 'react-router-dom'; // 路由插件
export default class App extends Component {
render(){
return (
<div>
{/* 基座内容区 */}
<Routes>
{
routers.map((item, index) => (
<Route path={item.path} key={index} element={<item.components />}></Route>
))
}
</Routes>
{/* 加载子应用内容区域 */}
<div id="sub-app"></div>
</div>
)
}
}
子应用1实现 sub-react项目
index.tsx
1:根据props下传递的container,判断是否为qiankun环境下运行
2:需要根据基座中配置加载子应用 匹配路由规则 写入对应的 basename值
3:判断是否在qiankun环境下,非qiankun环境下独立运行
4:qiankun生命周期函数使用 => 基座点击菜单进入子应用时候触发 mount函数
5: 使用加载引入 public-path.js => 用于qiankun防止加载资源报错
6:改造webpack配置( cnpm i react-app-rewired ) config-overrides.js + 修改package.json文件指令
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom'
import './public-path.js'
let root: any;
function render(props: any) {
const { container } = props // 1:container有这个代表运行再qiankun环境下的
const dom = container ? container.querySelector('#root') : document.getElementById('root')
root = createRoot(dom)
root.render(
// 2:basename 这边对应着 基座入口文件index.tsx 下的 apps加载子应用的匹配规则
<BrowserRouter basename='/sub-react'>
<App/>
</BrowserRouter>
)
}
// 3:判断是否在qiankun环境下,非qiankun环境下独立运行
if (!(window as any).__POWERED_BY_QIANKUN__) {
render({});
}
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
export async function bootstrap() {
console.log('react app bootstraped');
}
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props: any) {
console.log('sub-react', props)
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
// 将这个state存储到我们子应用store
});
props.setGlobalState({ count: 2 });
render(props);
}
// 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
export async function unmount(props: any) {
root.unmount();
}
public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// 动态设置 webpack publicPath,防止资源加载出错
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
package.json
- 原本的
"start": "react-scripts start",
替换为这个格式如下"start": "react-app-rewired start",
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
config-overrides.js ( webpack打包配置 作用:把项目打包为umd模块,用于qiankun读取当前项目暴露的生命周期函数 )
cnpm i react-app-rewired
与 修改package.json指令
// 在根目录下新增config-overrides.js文件并新增如下配置
const { name } = require("./package");
module.exports = {
webpack: (config) => {
config.output.library = `${name}-[name]`;
// 项目打包为umd模块,方便qiankun读取项目暴露出来的生命周期函数等 => index.tsx之中的 export async function mount等
config.output.libraryTarget = "umd";
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
return config;
}
};
注意点
当前子项目运行在 3001 时 => 需要修改基座之中 index.tsx之中的 apps变量的entry
eg:
const apps = [
{
name:'sub-react', // 子应用名称
entry: '//localhost: 3001', // 默认会加载这个路径的htlm,解析里面的js
activeRule: '/sub-react', // 匹配的路由
container: '#sub-app' // 加载的容器 (渲染路由那边 App.jsx)
}
]
子应用2实现 vue-app项目 ( 使用vite打包的 )
cnpm i vite-plugin-qiankun
=> vue使用qiankun的插件(用于暴露)
入口文件 main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import router from './router'
let app: any;
// 判断是否为qiankun 环境下
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
createApp(App).use(router).mount('#app');
} else { // qiankun环境下的
renderWithQiankun({
mount(props) {
app = createApp(App);
app.use(router).mount(props.container.querySelector('#app'));
},
bootstrap() {
console.log('vue app bootstrap');
},
update() {
console.log('vue app update');
},
unmount() {
console.log('vue app unmount');
app?.unmount();
}
});
}
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun';
export default defineConfig({
base: '/sub-vue', // 和基座中配置的activeRule一致
server: {
port: 3002,
cors: true,
origin: 'http://localhost:3002'
},
plugins: [
vue(),
qiankun('sub-vue', { // 配置qiankun插件
useDevMode: true
})
]
})
子应用3实现 umi-app项目 (umi4)
cnpm i @umijs/plugins
配置 .umirc.ts
export default {
base: '/sub-umi', // 匹配基座的 加载子应用路由 index.tsx => apps中的activeRule
npmClient: 'npm',
plugins: ['@umijs/plugins/dist/qiankun'],
qiankun: {
slave: {},
},
headScripts: [
{ src: 'https://unpkg.com/axios@1.1.2/dist/axios.min.js', ignore: true },
],
};
umi下展示qiankun的生命周期 src / app.ts
export const qiankun = {
async mount(props: any) {
console.log(props)
},
async bootstrap() {
console.log('umi app bootstraped');
},
async afterMount(props: any) {
console.log('umi app afterMount', props);
},
};