首页  |  学习总览  |  ← 返回专题总览 进阶专题 17 · 微前端

微前端架构实战 🔥 重点

从巨石应用到微前端,掌握 qiankun / Module Federation / single-spa 等主流方案的核心原理与落地实践
学习时长:约 20 小时 | 前置:工程化基础、Webpack | 产出:可运行的微前端基座+子应用

一、微前端核心概念

什么是微前端?

微前端(Micro Frontend)是一种类似于微服务的架构,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的片段,然后聚合为一个整体应用。

巨石应用
拆分
独立子应用
聚合
微前端

1.1 为什么需要微前端?

业务复杂度高

单应用代码量超过 10 万行,构建部署缓慢

团队协作瓶颈

多个团队维护同一仓库,冲突频繁

技术栈锁定

老技术无法升级,新技术无法渐进引入

独立部署需求

业务模块需要独立迭代和发布

1.2 微前端的核心价值

价值说明度量指标
技术栈无关各子应用可独立选型技术栈数量
独立开发团队各自为政,互不阻塞构建时间、冲突率
独立部署按需发布,不影响全局部署频率、MTTR
增量升级渐进式重构老系统迁移周期

二、主流方案对比

方案技术栈JS 沙箱样式隔离通信方式适用场景
qiankun基于 single-spaProxy 沙箱Shadow DOM / Scopeprops / Actions中大型应用
single-spa框架无关CustomEvent技术栈多样
Module FederationWebpack 5共享模块构建时集成
wujie基于 iframe/Shadow DOMiframe / ProxyShadow DOMprops / window低成本接入
micro-app基于 web componentProxyShadow DOM属性/事件轻量级
无界 (wujie)Web Components + iframeiframe天然隔离bus / props强隔离需求

三、qiankun 深度解析

3.1 架构设计

// qiankun 核心流程
注册子应用匹配路由加载资源JS 沙箱生命周期渲染

3.2 基座应用配置

// main.js - 基座
import { registerMicroApps, start } from 'qiankun';

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

start({
  prefetch: 'all',
  sandbox: {
    experimentalStyleIsolation: true
  }
});

3.3 子应用配置

// React 子应用入口渲染
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

let root: ReactDOM.Root | null = null;

function render(props: any) {
  const { container } = props;
  root = ReactDOM.createRoot(
    container 
      ? container.querySelector('#root') 
      : document.querySelector('#root')
  );
  root.render(<App />);
}

// 独立运行时直接渲染
if (!(window as any).__POWERED_BY_QIANKUN__) {
  render({});
}

// qiankun 生命周期钩子
export async function bootstrap() {
  console.log('react app bootstraped');
}

export async function mount(props: any) {
  console.log('props from main framework', props);
  render(props);
}

export async function unmount() {
  root?.unmount();
  root = null;
}

3.4 Webpack 配置

// 子应用 webpack.config.js
const { name } = require('./package.json');

module.exports = {
  devServer: {
    port: 3001,
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    historyApiFallback: true
  },
  output: {
    library: `${name}-[name]`,
    libraryTarget: 'umd',
    chunkLoadingGlobal: `webpackJsonp_${name}`,
    globalObject: 'window'
  }
};

3.5 JS 沙箱原理

三种沙箱模式

模式原理兼容性性能
SnapshotSandbox快照 diff 恢复IE 兼容
LegacySandboxProxy 代理IE 不支持
ProxySandbox多实例 Proxy现代浏览器
// ProxySandbox 简化实现
class ProxySandbox {
  constructor() {
    this.proxy = null;
    this.active();
  }
  
  active() {
    const fakeWindow = Object.create(null);
    const proxy = new Proxy(fakeWindow, {
      set: (target, prop, value) => {
        target[prop] = value;
        return true;
      },
      get: (target, prop) => {
        return target[prop];
      }
    });
    this.proxy = proxy;
  }
  
  inactive() {
    // 沙箱失效,清理
  }
}

四、Module Federation 深度解析

4.1 核心概念

4.2 配置示例

// remote 应用 webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote_app',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
        './Header': './src/components/Header',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
      }
    })
  ]
};

4.3 Host 应用配置

// host 应用 webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host_app',
      remotes: {
        remote_app: 'remote_app@http://localhost:3001/remoteEntry.js'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// host 组件中使用远程组件
import RemoteButton from 'remote_app/Button';

function App() {
  return (
    <div>
      <h1>Host App</h1>
      <RemoteButton>远程按钮</RemoteButton>
    </div>
  );
}

五、样式隔离方案

方案优点缺点推荐度
CSS Module零依赖不够彻底⭐⭐⭐⭐
CSS-in-JS动态样式运行时开销⭐⭐⭐⭐
Shadow DOM彻底隔离兼容性问题⭐⭐⭐
命名空间简单人为约束⭐⭐⭐
Style ScopeVue 友好仅限 Vue⭐⭐⭐⭐
Dynamic Schema动态添加删除性能开销⭐⭐⭐

5.1 Shadow DOM 示例

// 创建 Shadow DOM 容器
const host = document.querySelector('#subapp-container');
const shadowRoot = host.attachShadow({ mode: 'open' });

// 复制样式到 Shadow DOM
const styles = document.querySelectorAll('style');
styles.forEach(style => {
  shadowRoot.appendChild(style.cloneNode(true));
});

// 渲染子应用到 Shadow DOM
shadowRoot.appendChild(appContainer);

六、应用通信方案

6.1 基于 CustomEvent

// 主应用发送消息
const event = new CustomEvent('global-event', {
  detail: { type: 'LOGIN_SUCCESS', payload: userInfo }
});
window.dispatchEvent(event);

// 子应用监听消息
window.addEventListener('global-event', (e) => {
  if (e.detail.type === 'LOGIN_SUCCESS') {
    store.dispatch(setUser(e.detail.payload));
  }
});

6.2 基于全局状态

// 主应用创建全局 store
import { initGlobalState } from 'qiankun';

const actions = initGlobalState({
  user: null,
  theme: 'light',
  locale: 'zh-CN'
});

actions.onGlobalStateChange((state, prev) => {
  console.log('主应用状态变更', prev, '→', state);
});

actions.setGlobalState({ user: { name: '张三' } });

// 子应用接收 actions
export async function mount(props) {
  props.onGlobalStateChange((state) => {
    // 同步状态
  });
  props.setGlobalState({ theme: 'dark' });
}

七、性能优化

预加载策略

资源优化

运行时优化

八、工程化与 DevOps

8.1 Monorepo 架构

// 项目结构
monorepo/
├── packages/
│   ├── main/          # 主应用
│   ├── sub-react/     # React 子应用
│   ├── sub-vue/       # Vue 子应用
│   └── shared/        # 公共依赖
├── package.json
└── lerna.json         # 或 pnpm-workspace.yaml

8.2 CI/CD 流程

// .gitlab-ci.yml 示例
stages:
  - install
  - build
  - deploy

variables:
  NODE_VERSION: "18"

install:
  stage: install
  script:
    - npm ci --cache .npm
  cache:
    paths:
      - .npm/

build_main:
  stage: build
  script:
    - npm run build --scope=main
  artifacts:
    paths:
      - packages/main/dist

deploy_main:
  stage: deploy
  script:
    - npm run deploy --scope=main
  only:
    - main

九、实战案例:搭建 qiankun 微前端平台

9.1 基座项目初始化

// 创建基座
npx create-react-app main-app --template typescript
npm install qiankun

// src/App.tsx
import { useEffect, useState } from 'react';
import { registerMicroApps, start, loadMicroApp } from 'qiankun';

function App() {
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    registerMicroApps([
      {
        name: 'sub-react',
        entry: '//localhost:3001',
        container: '#container',
        activeRule: '/sub-react'
      },
      {
        name: 'sub-vue',
        entry: '//localhost:3002',
        container: '#container',
        activeRule: '/sub-vue'
      }
    ]);
    
    start({
      prefetch: 'all',
      sandbox: { strictStyleIsolation: true }
    });
    
    setLoading(false);
  }, []);
  
  return (
    <div>
      <nav>
        <Link to="/sub-react">React 子应用</Link>
        <Link to="/sub-vue">Vue 子应用</Link>
      </nav>
      {loading && <div>加载中...</div>}
      <div id="container" />
    </div>
  );
}

9.2 登录与权限

// 基座统一登录
class AuthService {
  private token: string | null = null;
  
  login(username: string, password: string) {
    return fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    }).then(res => res.json())
      .then(data => {
        this.token = data.token;
        localStorage.setItem('token', data.token);
        return data;
      });
  }
  
  getToken() {
    return this.token || localStorage.getItem('token');
  }
  
  // 传递给子应用
  getAuthProps() {
    return {
      token: this.getToken(),
      logout: () => this.logout()
    };
  }
  
  logout() {
    this.token = null;
    localStorage.removeItem('token');
  }
}

十、面试高频问题

Q1:微前端和微服务有什么区别和联系?
查看答案 ▼

微服务是后端服务拆分,通过 API 通信;微前端是前端应用拆分,通过事件/状态通信。

联系:两者都遵循单一职责原则,独立部署、独立团队、技术栈可选。

区别

维度微服务微前端
拆分粒度服务级应用级
通信HTTP/RPC事件/状态共享
状态后端管理前端管理
部署容器化静态资源
Q2:qiankun 的沙箱原理是什么?
查看答案 ▼
  • 快照沙箱:在子应用加载前打快照,卸载时恢复 diff
  • 代理沙箱:用 Proxy 代理 window 对象,所有操作都在代理上
  • 多实例沙箱:每个子应用一个独立 Proxy 实例,互不干扰