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

技术架构(更新: 2026年5月12日)

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

単一リポジトリのフロントエンドが 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#架构