前公司集团进行了公司收购,并购了一个上市公司的分公司,组织架构整体大调整了。所以,两个公司的一些系统就需要进行整合,比如管理系统及一些模块,要快速且保证稳定性的前提下,我们想到了用微前端方案解决。
我们经过了解发现将不同技术栈的系统整合到一起的微前端方案里,只有qiankun、Module Federation都是比较符合我们需求的。现在要整合三个不同框架的系统,我们考虑:
1. 如何选择合适的主应用和子应用? 2. 如何解决不同框架之间的冲突,比如全局样式、JavaScript隔离、路由管理等?
首先,主应用的选择。通常主应用作为基座,负责整体布局、路由导航和子应用的加载。主应用可以使用任何框架,但需要支持微前端框架的要求。比如qiankun推荐主应用使用React或者Vue,但也可以纯JavaScript。我司项目有React16和Vue2、3。因系统比较繁杂且我们不清楚业务细节,所以我们将主应用独立出来,或者改造其中一个现有的系统作为主应用。最后选择主应用使用Vue3,社区支持好且团队成员上手更速度。
接下来是子应用的改造。每个子应用需要暴露生命周期钩子,并配置webpack。对于Vue2、Vue3和React16,每个子应用都需要调整webpack配置,确保正确导出生命周期函数。同时,需要注意不同框架可能存在的全局变量污染,比如Vue的全局组件、React的全局状态等,这时候需要沙箱隔离机制。qiankun的JS沙箱和样式隔离是否能有效处理这些问题?
路由管理也是一个重点。主应用需要处理一级路由,子应用处理自己的子路由。需要确保路由切换时正确加载和卸载子应用,避免内存泄漏。比如使用history模式路由,主应用和子应用的路由前缀需要配置正确。
公共依赖的处理,比如Vue或React的版本不同,如何避免冲突?可能需要使用externals配置,将公共库从子应用中排除,由主应用统一提供,但不同版本的库可能会有问题。或者利用webpack的模块联邦(Module Federation)来共享依赖,但需要确认是否兼容不同版本的框架。
构建和部署方面,子应用需要独立部署,主应用只需加载子应用的入口文件。可能需要配置子应用的打包输出为umd格式,并设置publicPath为动态的,以适应不同环境。
此外我们可以还需要一些优化措施,比如预加载子应用资源,提升性能;统一的状态管理方案,比如使用redux或vuex在主应用和子应用之间共享状态;以及错误处理机制,比如子应用加载失败时的降级处理。
最后考虑到现有的项目可能已有一定的配置,qiankun可能更容易上手,而Module Federation需要webpack5支持,可能需要升级构建工具,这一系列更繁杂。
有了以上的简单思考后,我们进行了下面的工作,以下是简述:
一、技术选型对比
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
qiankun | 成熟度高、社区资源丰富、沙箱隔离完善 | 需手动配置子应用生命周期、依赖管理较复杂 | 多技术栈混合、快速落地 |
Module Federation | 原生Webpack支持、依赖共享灵活 | 需Webpack5、框架版本需兼容、调试复杂 | 同构建体系项目、深度定制需求 |
推荐选择 qiankun:更适合混合技术栈快速整合,已有大量生产环境验证
二、架构设计
主应用 (基座)
├── 导航系统 (处理全局路由/权限/状态)
├── 子应用容器
│ ├── Vue2 子系统 (独立仓库)
│ ├── Vue3 子系统 (独立仓库)
│ └── React16 子系统 (独立仓库)
└── 公共依赖管理 (共享工具库/样式规范)
三、具体实施步骤
1. 搭建主应用基座
技术栈:Vue3(组合式API + TypeScript)或纯JavaScript
// main-app/src/micro-fe-setup.js
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'vue2-app',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/vue2',
props: { authToken: 'xxx' } // 传递全局参数
},
{
name: 'vue3-app',
entry: '//localhost:7102',
container: '#subapp-container',
activeRule: '/vue3'
},
{
name: 'react16-app',
entry: '//localhost:7103',
container: '#subapp-container',
activeRule: '/react16'
}
]);
start({
prefetch: 'all', // 预加载子应用
sandbox: {
experimentalStyleIsolation: true // 开启样式沙箱
}
});
2. 子应用改造(Vue2)
关键配置:
// vue2-app/src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// vue2-app/src/main.js
let instance = null;
function render(props = {}) {
const { container } = props;
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 暴露qiankun生命周期钩子
export async function bootstrap() {
console.log('[vue2] app bootstraped');
}
export async function mount(props) {
console.log('[vue2] props from main framework', props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
}
Webpack配置:
// vue2-app/vue.config.js
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*' // 允许跨域
}
},
configureWebpack: {
output: {
library: `vue2App`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_vue2App`
}
}
};
3. 解决多框架冲突
样式隔离方案:
- 启用qiankun的
experimentalStyleIsolation
(添加前缀选择器) - 各子应用使用CSS Modules
- 主应用提供基础Reset CSS
JS沙箱策略:
// 主应用启动配置
start({
sandbox: {
strictStyleIsolation: true, // Shadow DOM隔离
speedy: false // 兼容IE
}
});
公共依赖处理:
// 主应用package.json
{
"sharedDependencies": {
"lodash": "^4.17.21",
"axios": "^0.21.1"
}
}
// 子应用webpack配置
externals: {
'lodash': 'lodash',
'axios': 'axios'
}
4. 路由统一管理
主应用路由配置:
// main-app/src/router.js
const routes = [ { path: '/vue2/*', name: 'vue2', meta: { title: 'Vue2子系统' } }, { path: '/vue3/*', name: 'vue3', meta: { title: 'Vue3子系统' } }, { path: '/react16/*', name: 'react16', meta: { title: 'React16子系统' } }];
子应用路由改造(以React16为例):
// react16-app/src/App.js
<Router basename={window.__POWERED_BY_QIANKUN__ ? '/react16' : '/'}>
<Switch>
<Route path="/detail" component={DetailPage} />
</Switch>
</Router>
四、部署优化策略
-
独立部署:每个子应用单独构建,主应用通过Nginx配置反向代理
location /vue2 { proxy_pass http://vue2-server; } location /vue3 { proxy_pass http://vue3-server; }
-
资源预加载:
start({ prefetch: (app) => app.name !== 'react16-app' // 按需预加载 });
-
性能监控:
// 主应用集成监控SDK import { performanceMonitor } from '@monitor/sdk'; performanceMonitor.init({ apps: ['vue2-app', 'vue3-app', 'react16-app'] });
五、常见问题解决方案
-
样式污染:
-
使用
scoped
样式(Vue)或CSS Modules -
主应用添加命名空间前缀:
#subapp-container .ant-btn { /* 覆盖Ant Design样式 */ }
-
-
全局变量冲突:
// 子应用卸载时清理全局变量 export async function unmount() { delete window.__VUE_APP_SHARED_DATA__; }
-
通信方案:
// 使用qiankun全局状态 import { initGlobalState } from 'qiankun'; const actions = initGlobalState({ user: null }); // 子应用监听变化 actions.onGlobalStateChange((state, prevState) => { console.log('全局状态变更:', state); });