qiankun 是目前较成熟可以开箱即用的实现微前端的框架。它主要特点有:

  1. 简单 - 任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。
  2. 完备 - 几乎包含所有构建微前端系统时所需要的基本能力,如 样式隔离、js 沙箱、预加载等。
  3. 生产可用 - 已在蚂蚁内外经受过足够大量的线上系统的考验及打磨,健壮性值得信赖。

qiankun2.0 提供了两种接入微应用的方式:基于路由配置、手动加载微应用。

基于路由配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React from 'react';
import ReactDOM, { render } from 'react-dom';
import { start, registerMicroApps, setDefaultMountApp } from 'qiankun';
import App from './App';

render(<App></App>, document.getElementById('mainApp'));

// registerMicroApps 注册 微应用
registerMicroApps([
{
name: 'react-app', // 微应用名称
entry: '//localhost:8888', // 微应用接入入口
container: '#reactApp', // 微应用挂载的 容器DOM节点
activeRule: '/react-app', // 触发的路由
},
{
name: 'vue-app',
entry: '//localhost:8080',
container: '#vueApp',
activeRule: '/vue-app',
},
{
name: 'ng-app',
entry: '//localhost:4200',
container: '#ngApp',
activeRule: '/ng-app',
},
]);

// 设置的默认路由
setDefaultMountApp('/ng-app');

// 启动应用
start();

手动加载微应用

适用于需要手动 加载/卸载 一个微应用的场景。

通常这种场景下微应用是一个不带路由的可独立运行的业务组件。 微应用不宜拆分过细,建议按照业务域来做拆分。业务关联紧密的功能单元应该做成一个微应用,反之关联不紧密的可以考虑拆分成多个微应用。 一个判断业务关联是否紧密的标准:看这个微应用与其他微应用是否有频繁的通信需求。如果有可能说明这两个微应用本身就是服务于同一个业务场景,合并成一个微应用可能会更合适。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React from 'react';
import ReactDOM, { render } from 'react-dom';
import { loadMicroApp, initGlobalState, MicroAppStateActions } from 'qiankun';
import App from './App';

render(<App></App>, document.getElementById('mainApp'));

const actions: MicroAppStateActions = initGlobalState({
'vue-app': [],
'react-app': [],
'ng-app': [],
});

// 手动记载 微应用
loadMicroApp({
name: 'vue-app',
entry: '//localhost:8080',
container: '#vueApp',
});
loadMicroApp({
name: 'react-app',
entry: '//localhost:8888',
container: '#reactApp',
});
loadMicroApp({
name: 'ng-app',
entry: '//localhost:4200',
container: '#ngApp',
});

actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
// console.log(state, prev);
});

微应用配置

qiankun 的接入使用并不复杂,但它对需要接入的微应用打包配置提了一些要求,并且会一些坑。下面列的都是基于 webpack5 的配置方式。

public-path.js

在每个微应用工程下都需要建一个 public-path.js 文件,内容如下:

1
2
3
4
5
//@ts-ignore
if (window.__POWERED_BY_QIANKUN__) {
//@ts-ignore
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

public-path.js 应该被引入工程启动文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 工程启动 文件引入 public-path
import './public-path';
import { createApp } from 'vue';
import { store } from './store';
import App from './App.vue';

let instance: any = null;
function render(props: any) {
const { container } = props;
instance = createApp(App);
instance.use(store);
instance.config.globalProperties.$onGlobalStateChange =
props.onGlobalStateChange;
instance.config.globalProperties.$setGlobalState = props.setGlobalState;
instance.mount(container ? container.querySelector('#vueApp') : '#vueApp');
}

//@ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}

export async function bootstrap() {
console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
}

export async function mount(props: any) {
render(props);
}

export async function unmount() {
instance.unmount();
instance._container.innerHTML = '';
instance = null;
}

webpack 配置

对于用 webpack 构建的工程有几个地方需要注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
output: {
path: path.resolve(__dirname, './dist'),
library: `${name}`, // 这个name 和 在quankun里面配置的 name 保持一致
libraryTarget: 'umd', // 必须
chunkLoadingGlobal: `webpackJsonp_${name}`, // 必须
globalObject: 'window', // 必须
}

devServer: {
hot: true,
historyApiFallback: true,
compress: true,
open: true,
port: 8080,
injectClient: false, // webpack5 必须
headers: {
'Access-Control-Allow-Origin': '*', // 必须
},
}

注意 ⚠️:如果是 webpack5"webpack-dev-server": "^4.0.0-beta.0" 需要用这个版本。

微应用数据通信

qiankun 提供了在主应用和微应用之间数据同步的方案: initGlobalState(state)

初始化

在主应用初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';
import ReactDOM, { render } from 'react-dom';
import { loadMicroApp, initGlobalState, MicroAppStateActions } from 'qiankun';
import App from './App';

render(<App></App>, document.getElementById('mainApp'));

// 初始化 GlobalState
const actions: MicroAppStateActions = initGlobalState({
'vue-app': [],
'react-app': [],
'ng-app': [],
});

loadMicroApp({
name: 'vue-app',
entry: '//localhost:8080',
container: '#vueApp',
});
loadMicroApp({
name: 'react-app',
entry: '//localhost:8888',
container: '#reactApp',
});
loadMicroApp({
name: 'ng-app',
entry: '//localhost:4200',
container: '#ngApp',
});

// 监听 GlobalState数据有变化时 触发
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
// console.log(state, prev);
});

微应用监听

在主应用初始化 GlobalState之后,我们可以在微应用获取到获取三个方法用来处理 State:

  • onGlobalStateChange: (callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void, 在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback
  • setGlobalState: (state: Record<string, any>) => boolean, 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
  • offGlobalStateChange: () => boolean,移除当前应用的状态监听,微应用 umount 时会默认调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import './public-path';
import { createApp } from 'vue';
import { store } from './store';
import App from './App.vue';

let instance: any = null;
function render(props: any) {
const { container } = props;
instance = createApp(App);
instance.use(store);
instance.config.globalProperties.$onGlobalStateChange =
props.onGlobalStateChange;
instance.config.globalProperties.$setGlobalState = props.setGlobalState;
instance.mount(container ? container.querySelector('#vueApp') : '#vueApp');
}

//@ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}

export async function bootstrap() {
console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
}

// mount props对象中会有 onGlobalStateChange、setGlobalState、offGlobalStateChange三个方法
export async function mount(props: any) {
render(props);
}

export async function unmount() {
instance.unmount();
instance._container.innerHTML = '';
instance = null;
}

在微应用拿到onGlobalStateChangesetGlobalStateoffGlobalStateChange方法后就可以根据自己的业务需要愉快的和其它应用交流了。但是如果你发现有两个微应用数据交流过多,则你需要考虑将这个两个微应用合并了。

代码

Refer To

https://qiankun.umijs.org/zh/guide

https://juejin.cn/post/6844904151231496200

https://juejin.cn/post/6846687602439897101