微前端架构实战:大型应用的拆分与治理之道

"这个项目太大了,每次发布都提心吊胆!"三个月前,我们的电商平台已经膨胀到 30 万行代码,构建时间超过 15 分钟,任何小改动都可能影响整个系统。作为技术负责人,我决定带领团队进行微前端改造。今天,我想分享这个过程中的经验和教训。🏗️

为什么需要微前端

首先看看我们遇到的问题:

// 原有的单体应用结构
src/
  ├── pages/
  │   ├── order/        // 订单系统 (5万行)
  │   ├── product/      // 商品系统 (8万行)
  │   ├── user/         // 用户中心 (4万行)
  │   └── admin/        // 后台管理 (13万行)
  ├── components/       // 公共组件 (3万行)
  ├── utils/           // 工具函数 (2万行)
  └── store/           // 全局状态 (5万行)

主要痛点:

  1. 构建时间长
  2. 团队协作困难
  3. 技术栈更新受限
  4. 发布风险大

微前端架构设计

1. 基础架构搭建

使用 Module Federation 实现应用间的代码共享:

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

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        order: 'order@http://localhost:3001/remoteEntry.js',
        product: 'product@http://localhost:3002/remoteEntry.js',
        user: 'user@http://localhost:3003/remoteEntry.js',
        admin: 'admin@http://localhost:3004/remoteEntry.js'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

2. 应用路由设计

实现了一个智能的路由系统:

// router/index.ts
interface MicroApp {
  name: string;
  entry: string;
  activeRule: string;
  container: string;
}

const apps: MicroApp[] = [
  {
    name: 'order',
    entry: '//localhost:3001',
    activeRule: '/order',
    container: '#micro-container'
  },
  // ... 其他应用配置
];

class MicroRouter {
  private apps: MicroApp[] = [];

  constructor(apps: MicroApp[]) {
    this.apps = apps;
    window.addEventListener('popstate', this.handleRouteChange);
  }

  private async loadApp(app: MicroApp) {
    const container = document.querySelector(app.container);
    if (!container) return;

    try {
      // 动态加载微应用
      const { mount, unmount } = await System.import(app.entry);
      await mount(container);

      return () => unmount(container);
    } catch (error) {
      console.error(`Failed to load ${app.name}:`, error);
    }
  }

  private handleRouteChange = () => {
    const path = window.location.pathname;
    const app = this.apps.find(app => 
      path.startsWith(app.activeRule)
    );

    if (app) {
      this.loadApp(app);
    }
  }
}

3. 通信机制设计

实现了一个事件总线来处理应用间通信:

// utils/eventBus.ts
class EventBus {
  private events: Map<string, Function[]> = new Map();

  on(event: string, callback: Function) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event)!.push(callback);
  }

  emit(event: string, data?: any) {
    if (!this.events.has(event)) return;
    this.events.get(event)!.forEach(callback => {
      try {
        callback(data);
      } catch (error) {
        console.error(`Error in event ${event}:`, error);
      }
    });
  }

  off(event: string, callback: Function) {
    if (!this.events.has(event)) return;
    const callbacks = this.events.get(event)!;
    const index = callbacks.indexOf(callback);
    if (index > -1) {
      callbacks.splice(index, 1);
    }
  }
}

export const eventBus = new EventBus();

// 使用示例
// 订单系统
eventBus.emit('orderCreated', { orderId: '123' });

// 商品系统
eventBus.on('orderCreated', (data) => {
  updateInventory(data.orderId);
});

4. 公共依赖管理

创建了一个共享依赖加载器:

// utils/dependencyLoader.ts
interface Dependency {
  name: string;
  version: string;
  url: string;
}

class DependencyLoader {
  private loaded: Set<string> = new Set();
  private loading: Map<string, Promise<void>> = new Map();

  async load(dep: Dependency): Promise<void> {
    const key = `${dep.name}@${dep.version}`;

    if (this.loaded.has(key)) return;
    if (this.loading.has(key)) {
      return this.loading.get(key);
    }

    const promise = new Promise<void>((resolve, reject) => {
      const script = document.createElement('script');
      script.src = dep.url;
      script.onload = () => {
        this.loaded.add(key);
        resolve();
      };
      script.onerror = reject;
      document.head.appendChild(script);
    });

    this.loading.set(key, promise);
    return promise;
  }
}

// 使用示例
const loader = new DependencyLoader();
await loader.load({
  name: 'lodash',
  version: '4.17.21',
  url: 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js'
});

5. 性能优化

实现了预加载策略:

// utils/prefetch.ts
class Prefetcher {
  private prefetchedApps: Set<string> = new Set();

  async prefetch(apps: MicroApp[]) {
    const prefetchPromises = apps.map(async app => {
      if (this.prefetchedApps.has(app.name)) return;

      try {
        // 创建 link 标签进行预加载
        const link = document.createElement('link');
        link.rel = 'prefetch';
        link.href = app.entry;
        document.head.appendChild(link);

        this.prefetchedApps.add(app.name);
      } catch (error) {
        console.error(`Failed to prefetch ${app.name}:`, error);
      }
    });

    await Promise.all(prefetchPromises);
  }

  prefetchOnIdle() {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => this.prefetch(apps));
    } else {
      setTimeout(() => this.prefetch(apps), 3000);
    }
  }
}

实践效果

经过三个月的改造,我们取得了显著的成效:

  1. 构建时间:

    • 主应用:3分钟
    • 子应用:平均2分钟
  2. 首屏加载:

    • 改造前:5s
    • 改造后:2s
  3. 团队协作:

    • 4个团队可以独立开发部署
    • 技术栈可以灵活选择
  4. 代码量:

    • 每个子应用控制在5万行以内
    • 复用组件数量提升50%

经验总结

  1. 技术选型建议:
    interface TechStack {
    moduleFederation: boolean;  // 推荐使用
    singleSpa: boolean;        // 特定场景考虑
    iframe: boolean;           // 不推荐
    }
    

const recommendations: Record<string, string> = { 新项目: 'Module Federation', 遗留系统: 'Single-spa', 快速验证: 'Iframe' };


2. 拆分原则:
```typescript
interface SplitStrategy {
  byDomain: boolean;      // 按业务域拆分
  byTeam: boolean;        // 按团队拆分
  byFrequency: boolean;   // 按更新频率拆分
}

写在最后

微前端不是银弹,关键是要:

  • 合理评估收益成本
  • 循序渐进地改造
  • 建立统一的技术标准
  • 重视团队协作

有什么问题欢迎在评论区讨论,我们一起学习进步!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值