マイクロフロントエンドアーキテクチャ実践: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#架构