单体前端应用的痛点与微前端思路

一个大型中后台系统做了三年后,前端代码可能膨胀到几十万行:一个仓库里塞着用户管理、订单系统、报表中心、权限配置。每次发版要全量回归,两个团队改同一份代码经常冲突,技术栈升级要动整个项目。这就是单体前端的典型困境。

微前端的思路是把单体应用拆成多个独立部署的子应用,每个子应用由不同团队维护,用不同技术栈都行,最后由一个基座应用把它们组装到一起。类比后端的微服务——前端也做服务化拆分。

微前端不是银弹,它解决的是"大型团队协作开发大型前端应用"的问题。小项目不需要。
本文核心知识点
  • 基座主应用,负责加载和管理子应用,提供统一布局和导航。
  • 子应用独立开发部署的业务模块,注册到基座里。
  • 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 组件,多个子应用都要用,不需要每个应用都复制一份。