单体前端应用的痛点与微前端思路
一个大型中后台系统做了三年后,前端代码可能膨胀到几十万行:一个仓库里塞着用户管理、订单系统、报表中心、权限配置。每次发版要全量回归,两个团队改同一份代码经常冲突,技术栈升级要动整个项目。这就是单体前端的典型困境。
微前端的思路是把单体应用拆成多个独立部署的子应用,每个子应用由不同团队维护,用不同技术栈都行,最后由一个基座应用把它们组装到一起。类比后端的微服务——前端也做服务化拆分。
微前端不是银弹,它解决的是"大型团队协作开发大型前端应用"的问题。小项目不需要。
- 基座主应用,负责加载和管理子应用,提供统一布局和导航。
- 子应用独立开发部署的业务模块,注册到基座里。
- qiankun蚂蚁开源的微前端框架,基于 single-spa 封装,接入简单。
- 模块联邦Webpack 5 的能力,运行时跨应用共享模块代码。
微前端的核心概念:基座、子应用、通信
微前端架构有三个核心角色:
- 基座应用(Main):也叫主应用,是用户访问的入口。它提供整体布局(顶部导航、侧边栏),根据 URL 决定加载哪个子应用。
- 子应用(Sub):独立的业务模块,有自己的仓库、构建流程和部署地址。基座通过加载子应用的 HTML 入口来挂载它。
- 通信机制:基座和子应用之间需要传数据(比如用户登录状态、权限信息)。通常用全局事件总线或 props 传递。
技术实现上有两条路线:路由级微前端(qiankun 代表)和模块级微前端(模块联邦代表)。路由级是按 URL 切换子应用,每个子应用占一个路由前缀;模块级是在同一个页面里跨应用共享组件和工具函数。
qiankun 实战:从主应用到子应用接入
qiankun 是蚂蚁集团开源的微前端框架,是目前国内最流行的方案。接入分两步:主应用注册子应用,子应用导出生命周期钩子。
// 主应用 main.js
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'user-app', // 子应用名称
entry: '//localhost:8081', // 子应用访问地址
container: '#sub-app', // 挂载容器
activeRule: '/user', // 路由前缀
},
{
name: 'order-app',
entry: '//localhost:8082',
container: '#sub-app',
activeRule: '/order',
},
]);
start();
// 子应用 main.js(以 Vue 为例)
let app = null;
function render(props = {}) {
const { container } = props;
app = new Vue({
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 导出 qiankun 生命周期
export async function bootstrap() {}
export async function mount(props) {
render(props);
}
export async function unmount() {
app.$destroy();
}
子应用需要在 vite.config 或 vue.config 里配置跨域和公共路径,让主应用能加载到它的静态资源。这是接入过程中最容易踩坑的地方。
模块联邦:构建时共享代码
模块联邦(Module Federation)是 Webpack 5 内置的能力,和 qiankun 的思路不同——它不是按路由拆分应用,而是让多个应用在运行时互相引用对方的模块。
// app1 的 webpack.config.js(作为远程应用)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button', // 暴露 Button 组件
},
}),
],
};
// app2 的 webpack.config.js(作为消费应用)
new ModuleFederationPlugin({
name: 'app2',
remotes: {
app1: 'app1@http://localhost:8081/remoteEntry.js',
},
});
// app2 里直接用 app1 的组件
import Button from 'app1/Button';
模块联邦适合需要跨应用共享组件的场景——比如设计系统里的 Button、Input 组件,多个子应用都要用,不需要每个应用都复制一份。
样式隔离与 JavaScript 隔离
微前端最大的坑是隔离——多个应用跑在同一个页面里,样式会互相污染,全局变量会互相覆盖。qiankun 默认提供了两种隔离:
| 隔离类型 | 原理 | 注意事项 |
|---|---|---|
| 样式隔离 | 动态加载子应用 CSS,卸载时移除 | 子应用不要用全局选择器如 body |
| JS 隔离 | Proxy 代理 window,子应用沙箱运行 | 少量第三方库可能不兼容沙箱 |
样式隔离的补充手段是 CSS Module 或 scoped 样式——子应用组件样式加唯一前缀,从根源上避免冲突。如果子应用用了 UI 组件库(如 Element Plus),确保基座和子应用的组件库版本一致,否则会出现样式错乱。
路由集成与部署策略
微前端的路由是嵌套的:基座应用有自己的路由,子应用在基座的容器里再有自己的路由。URL 结构通常是 /子应用前缀/子应用内部路由。
# nginx 部署配置示例
# 主应用
location / {
root /opt/main-app/dist;
try_files $uri $uri/ /index.html;
}
# 子应用 A
location /user/ {
proxy_pass http://user-app:8081/;
}
# 子应用 B
location /order/ {
proxy_pass http://order-app:8082/;
}
部署上,每个子应用独立部署到不同的服务或路径下。基座应用通过 entry 地址加载子应用的 HTML。子应用可以独立发版,不需要等基座发版——这就是微前端的核心价值。
什么时候该用微前端
微前端增加了架构复杂度,不是所有项目都需要。判断标准:
- 该用:多个团队协作开发一个大型中后台,仓库太大合并冲突频繁,需要独立发版,技术栈不统一。
- 不该用:小团队小项目,单仓库管理简单,发版频率低,技术栈统一。
如果只是想拆仓库但不需要运行时集成,先试试 Monorepo(pnpm workspace)——比微前端简单得多,也能解决多包管理的问题。微前端是运行时拆分,Monorepo 是构建时拆分,先试简单的方案。
微前端解决的是组织协作问题,不是技术问题——如果团队协作没有痛点,上微前端只会增加复杂度。