微前端架构实战——Java 工程师需要了解的前端架构演进
微前端架构实战——Java 工程师需要了解的前端架构演进
适读人群:关注全栈架构的 Java 工程师、技术 TL | 阅读时长:约14分钟 | 核心价值:微前端不只是前端的事,它影响着你的 API 设计、BFF 架构和团队协作模式
为什么 Java 工程师要了解微前端
我是后端工程师,但我在上一个项目里花了不少时间研究微前端。
原因是这样的:我们做了微服务化,后端拆成了十几个服务,订单服务、用户服务、商品服务……每个团队维护自己的服务,独立部署,互不干扰。
但前端还是一个大单体:一个 React 项目,十几个团队的功能都堆在里面,每次上线都要等所有人测完一起发布,上线节奏跟后端的独立部署完全不匹配。
更糟糕的是:前端有个状态管理的全局 store,A 团队改了 store 里的某个字段,B 团队的页面就崩了。
微前端就是为了解决这个问题出现的。理解它,能帮你更好地设计后端的 BFF 层,理解前端团队的诉求,在架构讨论里说出有价值的意见。
微前端是什么
微前端(Micro Frontend)把一个大的前端应用拆分成多个小的、独立的前端应用,每个小应用由不同的团队维护,可以独立开发、独立部署。
最终用户看到的还是一个完整的应用(比如 ERP 系统的主界面),但在技术上,这个界面可能是由 10 个独立的前端应用组合而成的。
类比微服务:微服务是把后端拆成多个服务,微前端是把前端拆成多个应用。
主流实现方案
方案一:iframe 组合
最古老的方式,主应用用 iframe 嵌入子应用。
<!-- 主应用 Shell -->
<div id="sidebar">主导航</div>
<div id="content">
<iframe src="https://order-app.internal/orders" />
</div>优点:完全隔离,子应用之间绝对不会互相影响。
缺点:
- URL 不同步(iframe 内跳转,浏览器地址栏不变)
- 无法共享全局状态(用户信息、权限等)
- UI 样式隔离过于彻底,主题统一难度大
- 弹窗/下拉框被 iframe 边界截断(z-index 问题)
现在很少用 iframe 做微前端,只在隔离要求极强的场景里(比如嵌入第三方内容)才用。
方案二:Module Federation(Webpack 5)
Webpack 5 的 Module Federation 允许不同应用在运行时共享代码和组件,是目前最主流的方案之一。
// 子应用(商品模块)的 webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'product_app',
filename: 'remoteEntry.js', // 暴露出去的入口文件
exposes: {
'./ProductList': './src/components/ProductList', // 暴露的组件
'./ProductDetail': './src/components/ProductDetail',
},
shared: ['react', 'react-dom'], // 与主应用共享的依赖
}),
],
};
// 主应用的 webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
product_app: 'product_app@http://product-app.example.com/remoteEntry.js',
order_app: 'order_app@http://order-app.example.com/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
};
// 主应用里动态加载子应用的组件
const ProductList = React.lazy(() => import('product_app/ProductList'));
function App() {
return (
<Suspense fallback="Loading...">
<ProductList />
</Suspense>
);
}优点:可以精细地共享公共依赖(react、antd 等),减少重复加载,性能好。
缺点:版本依赖管理复杂,不同子应用要求不同版本的共享包时可能冲突;Webpack 5 限定,换构建工具(Vite)需要额外插件。
方案三:qiankun(基于 single-spa)
qiankun 是阿里开源的微前端框架,封装了 single-spa 的底层逻辑,提供了更好用的 API。
// 主应用注册子应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'order-app',
entry: 'http://order.example.com',
container: '#order-container',
activeRule: '/orders', // 路由匹配规则
},
{
name: 'product-app',
entry: 'http://product.example.com',
container: '#product-container',
activeRule: '/products',
},
]);
start();子应用需要暴露生命周期函数:
// 子应用(order-app)
export async function bootstrap() {
console.log('order app bootstrap');
}
export async function mount(props) {
// 获取主应用传入的全局状态(用户信息等)
const { userInfo, onGlobalStateChange } = props;
ReactDOM.render(<App userInfo={userInfo} />, document.getElementById('root'));
}
export async function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}微前端对后端的影响
这是 Java 工程师最需要关注的部分。
影响一:BFF 层的设计
微前端架构下,每个子应用可能有自己的 BFF(Backend For Frontend):
每个前端团队可以和对应的后端团队"垂直对齐",减少跨团队依赖。
影响二:共享状态的传递
子应用之间需要共享一些全局状态:当前用户信息、权限、租户 ID 等。
这些信息如何传递?通常有几种方式:
- 主应用加载后,把用户信息存到 localStorage,子应用自己读取
- 主应用通过 props 把状态传给子应用(qiankun 支持)
- 通过 CustomEvent(浏览器原生事件)在子应用间通信
作为后端工程师,你需要理解:子应用在调用 API 时,需要携带这些共享状态(比如用户 token)。如果你的接口需要租户信息,确认前端有没有可靠的方式传递这个信息。
影响三:CORS 配置变复杂
微前端下,主应用在 shell.example.com,订单子应用在 order.example.com,商品子应用在 product.example.com。
子应用调用后端 API 时,可能出现跨域问题(不同子域名之间)。
后端需要正确配置 CORS,允许所有子应用域名的请求:
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
// 允许所有微前端子应用的域名
config.setAllowedOriginPatterns(List.of(
"http://shell.example.com",
"http://*.example.com" // 通配符匹配所有子域名
));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}踩坑记录
踩坑一:子应用样式互相污染
子应用之间如果没有做 CSS 隔离,A 应用的 .btn 样式可能覆盖 B 应用的 .btn 样式。
qiankun 提供了 Shadow DOM 模式的 CSS 隔离,但 Shadow DOM 有些场景不适用(比如 antd 的 ConfigProvider)。
更实用的方案:每个子应用所有 CSS 加命名空间前缀:
// 订单子应用的所有样式都加 .order-app 前缀
.order-app {
.btn { ... }
.table { ... }
}踩坑二:子应用独立运行时没问题,集成后出问题
子应用单独跑很正常,被主应用加载后各种奇怪报错。
常见原因:子应用里有 window.xxx 全局变量,和主应用或其他子应用冲突;子应用用了 document.querySelector,找到的是主应用的 DOM 元素而不是子应用的。
解决: 严格在子应用的容器节点范围内操作 DOM,避免使用全局变量。
踩坑三:部署 pipeline 管理复杂
10 个子应用 = 10 条 CI/CD pipeline。如果没有统一的发布规范,某个子应用的改动可能影响其他子应用(比如改了共享的 API 路径)。
解决: 建立子应用之间的 API 契约测试(Consumer-Driven Contract Testing),子应用更新后先跑契约测试,再部署。
Java 工程师的收获
不需要去写微前端代码,但理解微前端架构,你能做到:
- 在 API 设计时考虑"多个独立前端团队都会调用这个接口",设计出更通用的接口
- 在 BFF 层设计时,按前端团队的边界来划分 BFF 服务,而不是按后端领域划分
- 在系统设计评审时,理解前端同学提出的"微前端需要独立部署"背后的诉求
- CORS 配置时,考虑到微前端多域名的场景
前后端不是两个世界,架构是一体的。
