マイクロフロントエンドアーキテクチャ実践:Module Federation、qiankun、独自ソリューションの選定と実装

技术架构

マイクロフロントエンド:モノリシックアプリケーションの解毒剤

単一リポジトリのフロントエンドが 50 万行を超え、チームが 5 つ以上、ビルドが 10 分を超えるとき——マイクロフロントエンドは必然的な選択です。

課題 マイクロフロントエンドの解決策
ビルドが遅い サブアプリの独立ビルド、増分デプロイ
チーム間の衝突 サブアプリの独立リポジトリ、独立リリース
技術スタックの固定 各サブアプリで技術スタックを選択可能
密結合 契約による共有、直接参照ではない

1. ソリューション比較

ソリューション 分離性 通信 パフォーマンス 複雑さ 技術スタック
iframe 完璧 postMessage 悪い 低 任意
qiankun (JS サンドボックス) 良好 props/eventBus 良好 中 任意
Module Federation なし 共有モジュール 優 中 Webpack 5+
single-spa 良好 カスタム 良好 高 任意
EMP なし 共有モジュール 優 中 Webpack 5

2. iframe アプローチ:シンプルだが制限あり

実装

<iframe
  src="https://sub-app.example.com"
  sandbox="allow-scripts allow-same-origin allow-forms"
  style="width: 100%; height: 100%; border: none;"
></iframe>

長所と短所

長所 短所
完璧な CSS/JS 分離 パフォーマンスが悪い(各 iframe は別プロセス)
自然に安全 URL が同期されず、リフレッシュで状態消失
移行コストゼロ モーダル/オーバーレイが iframe で切り取られる
任意の技術スタック 通信は postMessage のみ

適したシナリオ:サードパーティ埋め込み、低インタラクション要件、セキュリティ分離優先。


3. qiankun:Alibaba の JS サンドボックスソリューション

メインアプリ設定

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'app-order',
    entry: '//localhost:8081',
    container: '#subapp-container',
    activeRule: '/order',
    props: { mainStore, userInfo },
  },
  {
    name: 'app-user',
    entry: '//localhost:8082',
    container: '#subapp-container',
    activeRule: '/user',
    props: { mainStore, userInfo },
  },
]);

start({
  prefetch: 'all',       // プリロード
  sandbox: { strictStyleIsolation: true }, // 厳格なスタイル分離
  singular: false,       // 複数サブアプリの同時マウントを許可
});

サブアプリの改修

// サブアプリエントリ
let root = null;

function render(props) {
  const { container, mainStore } = props;
  root = createApp(App);
  root.mount(container ? container.querySelector('#app') : '#app');
}

// ライフサイクルのエクスポート
export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount() { root.unmount(); }

// スタンドアロン実行
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

JS サンドボックスの原理

qiankun は Proxy を使用して window 操作を傍受します:

const fakeWindow = new Proxy(window, {
  get(target, key) {
    // サンドボックスからの値を優先
    if (sandbox.hasOwnProperty(key)) return sandbox[key];
    return target[key];
  },
  set(target, key, value) {
    // サンドボックスに書き込み、実際の window には書き込まない
    sandbox[key] = value;
    return true;
  },
});

スタイル分離

モード 実装 制限
strictStyleIsolation Shadow DOM モーダルスタイルが失われる、グローバルスタイルが浸透しない
experimentalStyleIsolation CSS Scope プレフィックス 動的に挿入されたスタイルは処理されない

4. Module Federation:Webpack 5 のランタイムモジュール共有

基本概念

App A (Host) ──リモート読み込み──→ App B (Remote) のモジュール
     ↑                                  ↑
  共有依存関係                        共有依存関係
  (react, lodash...)                 (react, lodash...)

Remote 設定(モジュール提供側)

// webpack.config.js (App B - Remote)
const { ModuleFederationPlugin } = require('webpack').container;

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

Host 設定(モジュール消費側)

// webpack.config.js (App A - Host)
new ModuleFederationPlugin({
  name: 'appMain',
  remotes: {
    appOrder: 'appOrder@http://localhost:8081/remoteEntry.js',
  },
  shared: {
    react: { singleton: true, requiredVersion: '^18.0.0' },
    'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
  },
})

リモートコンポーネントの動的読み込み

const OrderList = React.lazy(() => import('appOrder/OrderList'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <OrderList />
    </Suspense>
  );
}

Module Federation 2.0 (2026)

新機能 説明
ネイティブ ESM 対応 Webpack ランタイムに依存しない
Rspack 互換性 ビルド速度 5-10 倍向上
Vite プラグイン @originjs/vite-plugin-federation
型安全性 リモートモジュールの型自動推論
ランタイム動的登録 ビルド時にリモート URL を決定する必要なし

5. 通信戦略

オプション 1:CustomEvent

// サブアプリが発行
window.dispatchEvent(new CustomEvent('order:created', {
  detail: { orderId: '12345' },
}));

// メインアプリが購読
window.addEventListener('order:created', (e) => {
  console.log('新規注文:', e.detail);
});

オプション 2:共有状態(Micro Store)

class MicroStore {
  state = {};
  listeners = new Map();

  setState(key, value) {
    this.state[key] = value;
    this.listeners.get(key)?.forEach(fn => fn(value));
  }

  subscribe(key, listener) {
    if (!this.listeners.has(key)) this.listeners.set(key, new Set());
    this.listeners.get(key).add(listener);
    return () => this.listeners.get(key).delete(listener);
  }
}

// メインアプリが作成、props 経由でサブアプリに渡す
const store = new MicroStore();

オプション 3:URL 同期

// サブアプリが URL パラメータで通信
const params = new URLSearchParams(location.search);
const token = params.get('token');

// メインアプリがルート変更を監視
router.afterEach((to) => {
  // サブアプリにルート変更を通知
  store.setState('route', to.path);
});

6. 共有依存関係の管理

戦略 実装 長所/短所
externals + CDN すべての共有ライブラリを CDN 経由で シンプルだがバージョン固定
Module Federation shared ランタイムバージョン交渉 柔軟だが設定が複雑
Monorepo 共有パッケージ pnpm workspace 強い一貫性だが結合
NPM プライベートパッケージ プライベートレジストリに公開 分離されているが公開フロー必要

Module Federation shared ベストプラクティス

shared: {
  react: {
    singleton: true,        // 1 つのバージョンのみ読み込み
    strictVersion: true,    // バージョン不一致時にエラー
    requiredVersion: deps.react,
    eager: false,           // 非同期読み込み
  },
  lodash: {
    singleton: false,       // 複数バージョン許可
    requiredVersion: deps.lodash,
  },
}

7. デプロイ戦略

独立デプロイ

CDN
├── /main/          → メインアプリ (hash: a1b2c3)
├── /app-order/     → 注文サブアプリ (hash: d4e5f6)
├── /app-user/      → ユーザーサブアプリ (hash: g7h8i9)
└── /shared/        → 共有リソース (react, lodash...)

バージョン管理

// remote-config.json(メインアプリが動的に読み込み)
{
  "apps": {
    "app-order": {
      "url": "https://cdn.example.com/app-order/d4e5f6/remoteEntry.js",
      "version": "2.3.1"
    },
    "app-user": {
      "url": "https://cdn.example.com/app-user/g7h8i9/remoteEntry.js",
      "version": "1.8.0"
    }
  }
}

メインアプリは起動時にこの設定を取得し、サブアプリの独立デプロイとカナリアリリースを実現します。


8. 選定決定木

完全な分離が必要?
  ├── はい → iframe
  └── いいえ → 同じ技術スタック?
                ├── はい → Module Federation
                └── いいえ → サンドボックス分離が必要?
                              ├── はい → qiankun
                              └── いいえ → single-spa / 独自

2026 年の推奨:新規プロジェクトでは、パフォーマンスと開発体験の両面で Module Federation 2.0 + Rspack を優先。強力な分離が必要な場合のみ qiankun を検討。

#微前端#Module Federation#qiankun#架构