微前端-落地与实践的探索

相关背景

你是否有遇到当我们想处理某一个工作时需要打开多个不同的系统(网站)来进行工作,这种各个系统之间入口不一样但是其业务是有一定的关联性的,你可能觉得那我直接在开发的时候把所有的功能都写在一个系统中就行了,当然是可以的,但同时也引入了其他的问题,如系统会很庞大,管理复杂度增多,协作难度提高,伴随而来的风险也会提高,那有没有一种有效的方案来解决这个问题呢 ?

我们先来想一想我们想要的效果是怎么样的:

  • 带有关联性业务的操作都可以在同一个平台上进行操作(同一个网站),减少多个平台直接的跳转。
  • 各个项目直接的独立的,相互隔离的,互不影响各自的开发。
  • 维护方便,无需维护多个系统,降低维护成本。

总体设计

技术栈:vite+vue+ts

  • main 应用:主要负责公共的操作,如登录,权限申请等。
  • pipeline 子应用:执行流水线平台
  • efficacy 子应用:效能平台
  • cmdb 子应用

简单案例

main主应用

1
npm i qiankun -S  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
onMounted(() => {
// @ts-ignore
if (!window.qiankunStarted) {
// @ts-ignore
window.qiankunStarted = true;
registerApps();
start({
sandbox: {
experimentalStyleIsolation: true, // 样式隔离
},
});
}
});

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
import { registerMicroApps, addGlobalUncaughtErrorHandler } from 'qiankun';
import config from '@/config';

const { subApps } = config;

export function registerApps() {
try {
registerMicroApps(subApps, {
beforeLoad: [
(app) => {
console.log('before load', app);
},
],
beforeMount: [
(app) => {
console.log('before mount', app);
},
],
afterUnmount: [
(app) => {
console.log('before unmount', app);
},
],
});
} catch (err) {
console.log(err);
}
addGlobalUncaughtErrorHandler((event) => console.log(event));
}

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
36
37
function getEntry(name, port) {
if (process.env.NODE_ENV === 'development') {
return `//localhost:${port}`;
}
return `/micro/${name}/`;
}

function getActiveRule(name, devRule, prodRule) {
return process.env.NODE_ENV === 'development' ? devRule : prodRule;
}

export default {
subApps: [
{
name: 'efficacy',
entry: getEntry('efficacy', 7001),
container: '#sub-container',
activeRule: getActiveRule('efficacy', '/efficacy/', '/efficacy/'),
props: {},
},
{
name: 'pipeline',
entry: getEntry('pipeline', 7002),
container: '#sub-container',
activeRule: getActiveRule('pipeline', '/pipeline/', '/pipeline/'),
props: {},
},
{
name: 'cmdb',
entry: getEntry('cmdb', 7003),
container: '#sub-container',
activeRule: getActiveRule('cmdb', '/cmdb/', '/cmdb/'),
props: {},
},
],
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// history模式需要通配所有路由,详见vue-router文档, 不添加这个会导致无法跳转路由,qiankun无法拦截进入子应用
{
path: '/efficacy/:pathMatch(.*)*',
name: 'efficacy-app',
meta: {},
component: () => import('@/views/Layout/index.vue'),
},
{
path: '/pipeline/:pathMatch(.*)*',
name: 'pipeline-app',
meta: {},
component: () => import('@/views/Layout/index.vue'),
},
{
path: '/cmdb/:pathMatch(.*)*',
name: 'cmdb-app',
meta: {},
component: () => import('@/views/Layout/index.vue'),
},

pipeline子应用

主应用配置

  1. 在 主应用的 src/config.ts 中配置子应用的信息
  2. 在 主应用的 src/router.ts 中引入子应用的路由

子应用配置

  1. 子应用安装接入插件 npm install vite-plugin-qiankun
  2. main.ts 初始化 qiankun
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

import {
renderWithQiankun,
qiankunWindow,
} from 'vite-plugin-qiankun/dist/helper';
import type { QiankunProps } from 'vite-plugin-qiankun/es/helper';

// @ts-ignore
let app: App<Element>;

const render = (container: HTMLElement) => {
// @ts-ignore
app = createApp(App);
app.use(store);
// app.use(router);
app
.use(Avatar)
.use(Button)
.use(Card)
.use(DatePicker)
.use(Divider)
.use(Menu)
.use(Table)
.use(Input)
.use(List)
.use(Modal);
app.use(InstallCodemirro);
app.config.globalProperties.$message = message;
message.config({
top: '120px',
});
app.use(router).mount(container ? container.querySelector('#app') : '#app');
};

const initQianKun = () => {
renderWithQiankun({
update(props: QiankunProps): void | Promise<void> {
console.log('update props in main app', props);
return undefined;
},
mount(props) {
const { container } = props;
render(container);
},
bootstrap() {},
unmount() {
app.unmount();
},
});
};

// @ts-ignore
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render();
  1. 在子应用的 vite.config.ts 中配置插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
plugins: [
vue(),
qiankun('your-app-name', {
useDevMode: true,
}),
]

// 设置生产环境的 base
base: getBase(),

function getBase() {
// @ts-ignore
const env = process.env.NODE_ENV;
return env === undefined || env === 'development' ? '/' : '/micro/efficacy/';
}

Nginx配置

1
2
3
4
5
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

子应用放置在 main 应用的 micro/ 目录下,nginx 配置示例:

1
2
3
4
5
6
7
.
├── assets
├── favicon.ico
├── index.html
└── micro
└── efficacy
└── ... app

相关资料

  1. qiankun(蚂蚁金服开源微前端框架):https://github.com/umijs/qiankun
  2. wuji(腾讯开源微前端框架):https://github.com/Tencent/wujie
  3. Vmware 开源框架: https://github.com/single-spa/single-spa
  4. 微前端在美团外卖的实践https://tech.meituan.com/2020/02/27/meituan-waimai-micro-frontends-practice.html
  5. Nginx:http://nginx.org/en/
  6. 阿里云控制台:https://github.com/aliyun/alibabacloud-console-design

微前端-落地与实践的探索
https://mikeygithub.github.io/2021/08/15/yuque/微前端-落地与实践的探索/
作者
Mikey
发布于
2021年8月15日
许可协议