qiankun-微前端
主应用
1、安装qiankun
$ yarn add qiankun # 或者 npm i qiankun -S
2、在主应用中注册微应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app', // app已经注册的名字
entry: '//localhost:7100', // 进入的主机端口号
container: '#yourContainer', // 类似于一个容器,起到占位的作用
activeRule: '/yourActiveRule', // 找到微应用的路径
},
{
name: 'vue app',
entry: '//localhost:3012',
container: '#yourContainer1',
activeRule: '/yourActiveRule1',
props:{} // 用于主应用与微应用之间的通信
},
]);
// 启动乾坤
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
微应用
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
- 导入相应的生命周期钩子
微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('react app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
);
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
qiankun 基于 single-spa,所以你可以在这里找到更多关于微应用生命周期相关的文档说明。
- 配置微应用的打包工具
除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};
相关配置介绍可以查看 webpack 相关文档。
举例
react为主应用,vue3和react为子应用
react主应用配置
1、首先在主应用下面安装
$ yarn add qiankun # 或者 npm i qiankun -S
2、其次在这子应用的根目录下面建一个.env
文件,里面分别配置上如下代码
PORT=端口号 // 这3个应用下的.env文件都配置上,但是`端口号`要写3个不一样的
3、在react主应用中src
下面的index.js
文件当中注册微应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'qiankun_3',
entry: '//localhost:3012',
container: '#qiankun3',
activeRule: '/qiankun3',
props:{} // 数据的传递
},
{
name: 'vue3_app',
entry: '//127.0.0.1:3013',
container: '#vue3_app',
activeRule: '/vue3_app',
},
]);
// 启动乾坤
start();
react子应用配置
1、子应用的src
目录下创建public-path.js
文件,里面做如下配置:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// __webpack_public_path__ :在入口文件中导入public-path.js文件以后,
// webpack5需要在package.json中改为全局变量,要不这块会显示未找到的错误。
// 修改为package.json文件如下即可。
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"globals": {
"__webpack_public_path__": true
}
},
2、入口文件 index.js 修改,为了避免根 id #root 与其他的 DOM 冲突,需要限制查找范围。
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'));
}
这里需要特别注意的是,通过 ReactDOM.render 挂载子应用时,需要保证每次子应用加载都应使用一个新的路由实例。
3、下载react-app-rewired插件,更改子应用启动项
$ npm i react-app-rewired-S
package.json
文件中改启动项
"scripts": {
"start": "react-app-rewired start", // 更改启动区为react-app-rewired
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
4、重写webpack
中的方法,根目录下创建config-overrides.js
文件
const { name } = require('./package');
module.exports = {
webpack: (config) => {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = 'umd'; // 注入方式
// config.output.jsonpFunction = `webpackJsonp_${name}`;
// 在2020-10-10发布的webpack 5中已将 output.jsonpFunction 更名为
// output.chunkLoadingGlobal
config.output.globalObject = 'window';
// 中自动推断出一个唯一的构建名称,并将其作为 output.uniqueName 的默认值。
// 这个值用于使所有潜在的冲突的全局变量成为唯一。
config.output.globalObject = 'window';
return config;
},
devServer: (_) => {
const config = _;
config.headers = {
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback = true;
config.hot = false;
config.watchContentBase = false;
config.liveReload = false;
return config;
},
};
vue3子应用配置
如果想配置vue2的请看这里
1、子应用的src
目录下创建public-path.js
文件,里面做如下配置:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2、配置入口文件 main.js
import './public-path'; // 必须开头导入要不解析不了
import { createApp } from 'vue'
import App from './App.vue'
import routes from './router'
import { createRouter, createWebHistory } from 'vue-router';
// import store from './store'
import './assets/main.css'
let router = null;
let instance = null;
let history = null;
function render(props = {}) {
const { container } = props;
let arr = routes.options.routes;
console.log(arr)
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/app-vue3' : '/');
router = createRouter({
history,
routes: routes.options.routes,
});
instance = createApp(App);
instance.use(router);
// instance.use(store);
instance.mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('%c%s', 'color: green;', 'vue3.0 app bootstraped');
}
function storeTest(props) {
props.onGlobalStateChange &&
props.onGlobalStateChange(
(value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true,
);
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name,
},
});
}
export async function mount(props) {
storeTest(props);
render(props);
instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange;
instance.config.globalProperties.$setGlobalState = props.setGlobalState;
}
export async function unmount() {
instance.unmount();
instance._container.innerHTML = '';
instance = null;
router = null;
history.destroy();
}
3、在vue.config.ts
文件中配置监听端口号,配置vue允许夸端口 设置vue.config,并且设置挂载点
const { name } = require('./package');
module.exports = {
devServer: {
port:3012,//设置端口号
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
这样一个过程完成下来后,您就会对微前端恍然大悟。