微前端

What — 是什么

微前端将大型前端应用拆分为多个独立的小应用,各自开发、部署和运行,在运行时组合为统一体验。

核心概念:

  • 主应用(Container/Shell):负责加载和编排子应用
  • 子应用(Micro App):独立开发、独立部署的功能模块
  • JS 沙箱:隔离子应用的 JS 执行环境(Proxy 沙箱 / Snapshot 沙箱)
  • 样式隔离:Shadow DOM / CSS Modules / CSS Scope / 动态加载卸载

主流方案:

方案原理框架耦合沙箱适用场景
qiankunHTML Entry + Proxy 沙箱不耦合完善大部分场景
Module FederationWebpack 5 模块共享不耦合无(共享运行时)Webpack 项目
single-spa路由级应用挂载不耦合需自建定制化需求
无框架Web Components不耦合Shadow DOM轻量场景

关键特性:

  • 子应用独立仓库、独立部署、独立技术栈
  • 运行时动态加载,不需要重新部署主应用
  • qiankun 的 HTML Entry 方式接入成本最低

Why — 为什么

适用场景:

  • 大型项目多团队并行开发
  • 渐进式技术栈迁移(老项目 jQuery → 新项目 React)
  • 独立部署,互不影响

何时不需要:

  • 单团队中小项目
  • 统一技术栈且应用不复杂
  • 团队没有微前端运维经验

优缺点:

  • ✅ 优点:
    • 独立开发部署,团队自治
    • 技术栈无关,可渐进迁移
    • 局部故障不影响全局
  • ❌ 缺点:
    • 架构复杂度大幅增加
    • 公共依赖重复加载
    • 样式隔离不完美
    • 调试困难(跨应用问题)

How — 怎么用

快速上手(qiankun)

主应用:

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
    {
        name: 'app-react',
        entry: '//localhost:3001',
        container: '#subapp-container',
        activeRule: '/react',
    },
    {
        name: 'app-vue',
        entry: '//localhost:3002',
        container: '#subapp-container',
        activeRule: '/vue',
    },
]);

start();

子应用(React):

// 子应用入口导出生命周期
export async function bootstrap() {}
export async function mount(props) {
    ReactDOM.render(<App />, props.container.querySelector('#root'));
}
export async function unmount(props) {
    ReactDOM.unmountComponentAtNode(props.container.querySelector('#root'));
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
    ReactDOM.render(<App />, document.getElementById('root'));
}

代码示例

Module Federation(Webpack 5):

// app1: 暴露组件
new ModuleFederationPlugin({
    name: 'app1',
    filename: 'remoteEntry.js',
    exposes: {
        './UserCard': './src/components/UserCard',
    },
    shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});

// app2: 消费组件
new ModuleFederationPlugin({
    name: 'app2',
    remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
    },
    shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});

// app2 中使用
const UserCard = React.lazy(() => import('app1/UserCard'));

主子应用通信:

// 主应用通过 props 传递
registerMicroApps([{
    name: 'app-sub',
    entry: '//localhost:3001',
    container: '#container',
    activeRule: '/sub',
    props: {
        mainStore,      // 共享状态
        globalEvent,    // 通信事件
        userInfo,       // 用户信息
    },
}]);

// 子应用 mount 中接收
export async function mount(props) {
    props.onGlobalStateChange((state) => {
        console.log('主应用状态变更:', state);
    });
}

常见问题与踩坑

问题原因解决方案
样式污染子应用全局 CSS 影响其他应用qiankun 严格模式 / CSS Modules / Shadow DOM
公共依赖重复每个子应用独立打包 React 等Module Federation shared / CDN externals
子应用路由冲突多个应用路由模式冲突统一用 history 模式,activeRule 精确匹配
本地开发跨域子应用端口不同子应用配置 CORS,开发环境用代理

最佳实践

  • 优先用 Module Federation(Webpack 5 项目),次选 qiankun
  • 公共依赖(React/Vue)用 externals 或 shared 共享
  • 子应用避免全局样式,用 CSS Modules/Scoped CSS
  • 主应用统一管理用户认证和路由

面试题

Q1: qiankun、Module Federation、single-spa 三种微前端方案各有什么特点?

qiankun 基于 HTML Entry + Proxy 沙箱,接入成本最低,沙箱隔离完善,适合大部分场景;Module Federation 基于 Webpack 5 模块共享,无沙箱但共享运行时,适合 Webpack 项目间组件共享;single-spa 是底层路由级挂载框架,灵活但需自建沙箱,适合定制化需求。

Q2: JS 沙箱隔离有哪些实现方式?

两种主流方式:Proxy 沙箱(qiankun 默认)通过 Proxy 代理 window 对象,子应用对全局变量的读写被拦截和隔离,每个子应用有独立的 fakeWindow;Snapshot 沙箱在子应用挂载前快照 window 状态,卸载时恢复,兼容性好但无法支持多实例并行。

Q3: 微前端的样式隔离有哪些方案?各有什么局限?

Shadow DOM 隔离最彻底,但弹窗挂载到 body 时样式丢失且不支持全局 CSS 变量;CSS Modules/Scoped CSS 通过唯一选择器前缀隔离,需构建工具配合;qiankun 严格模式在子应用卸载时移除其样式标签。目前没有完美方案,实践中常组合使用。

Q4: 微前端的公共依赖如何处理?

三种方案:Module Federation 的 shared 配置,运行时共享单个 React/Vue 实例;externals + CDN 引入,所有子应用从同一 CDN 地址加载公共库;qiankun 的 prefetch 预加载 + 浏览器缓存复用。需确保公共库只加载一个版本,避免多实例冲突。


相关链接: