モジュールフェデレーションによる多様なフロントエンドアプリケーションのシームレスな統合
Ethan Miller
Product Engineer · Leapcell

はじめに
現代のWeb開発、特に大規模組織や複雑な製品スイートの急速に進化する状況において、モノリシックなフロントエンドアーキテクチャはしばしば大きな課題を提示します。チームは、ビルド時間の遅延、独立した機能のデプロイの困難さ、そして密接に結合されたデプロイメントの常に存在するリスクに苦労しています。アプリケーションが複雑さと機能セットを増大させるにつれて、よりモジュラーでスケーラブル、かつ独立してデプロイ可能なアプローチの必要性がますます重要になります。ここで、複数の独立したフロントエンドアプリケーションを単一の統合されたユーザーエクスペリエンスに構成するという概念が登場します。この記事では、これらの課題にまさに対処する強力な機能であるモジュールフェデレーションについて掘り下げ、開発者が別個のフロントエンドアプリケーションをシームレスに統合することで洗練されたシステムを構築できるようにします。
モジュールフェデレーションのコアコンセプトを理解する
モジュールフェデレーションの仕組みと利点について詳しく説明する前に、その動作の基盤となるいくつかのコアコンセプトを把握することが不可欠です。
- ホスト(またはコンテナ)アプリケーション: これは、他のリモートアプリケーションからモジュールを消費し、オーケストレーションするメインアプリケーションです。シェルまたはラッパーとして機能します。
- リモートアプリケーション: これらは、他のホストまたはリモートアプリケーションによって消費されるために特定のモジュールまたはコンポーネントを公開する独立したアプリケーションです。
- 公開されたモジュール: これらは、リモートアプリケーションが他のアプリケーションが使用するために一般に利用可能にすることを選択したコード(コンポーネント、関数、ユーティリティなど)の特定の断片です。
- 共有モジュール: モジュールフェデレーションは、アプリケーション間で依存関係を共有することを本質的にサポートしています。これは、バンドルサイズの最適化や、共有ライブラリ(ReactやVueなど)の単一バージョンのみが実行時にロードされることを保証し、潜在的な競合や肥大化を防ぐために重要です。
モジュールフェデレーションの仕組み
Webpack 5で導入されたモジュールフェデレーションは、JavaScriptアプリケーションが堅牢かつ効率的な方法で別のアプリケーションからコードを動的にロードし、依存関係を共有できるようにします。その核心において、モジュールフェデレーションは、「ホスト」アプリケーションが実行時に「リモート」モジュールをロードすることを可能にします。
eコマースプラットフォームがあるシナリオを考えてみてください。プラットフォーム全体を1つの巨大なフロントエンドとして構築するのではなく、懸念事項を独立したマイクロフロントエンドに分離できます。「製品カタログ」アプリケーション、「ショッピングカート」アプリケーション、「ユーザープロファイル」アプリケーションです。モジュールフェデレーションを使用すると、「製品カタログ」はFeaturedProduct
コンポーネントを公開し、「ショッピングカート」はAddToCartButton
を公開し、「ユーザープロファイル」はAuthWidget
を公開する可能性があります。メインの「シェル」アプリケーションがホストとして機能し、必要に応じてこれらのコンポーネントを取得してレンダリングします。
簡単な例で説明しましょう。
リモートアプリケーションの設定(コンポーネントの公開)
まず、product-catalog
という名前のリモートアプリケーションを定義します。その目標は、ProductCard
コンポーネントを公開することです。
// product-catalog/src/components/ProductCard.jsx import React from 'react'; const ProductCard = ({ name, price }) => { return ( <div style={{ border: '1px solid #ccc', padding: '10px', margin: '10px' }}> <h3>{name}</h3> <p>Price: ${price}</p> <button>View Details</button> </div> ); }; export default ProductCard;
次に、product-catalog
のwebpack.config.js
を構成して、このコンポーネントを公開します。
// product-catalog/webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; module.exports = { mode: 'development', entry: './src/index.js', output: { publicPath: 'http://localhost:3001/', // 公開されたモジュールのパブリックパス }, devServer: { port: 3001, }, module: { rules: [ { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ['@babel/preset-react'], }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'product_catalog', // このリモートアプリケーションの一意の名前 filename: 'remoteEntry.js', // 公開されたモジュールのメタデータが含まれるファイル exposes: { './ProductCard': './src/components/ProductCard', // ProductCardコンポーネントを公開 }, shared: { // 依存関係の重複排除に不可欠 react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], };
ここでは、name
はこのリモートの一意の識別子です。filename
はホストが取得するマニフェストファイルです。exposes
は利用可能なモジュールを定義します。shared
は依存関係の重複排除に不可欠です。
ホストアプリケーションの設定(コンポーネントの消費)
次に、product-catalog
からProductCard
を消費するホストアプリケーションmain-shell
を作成します。
// main-shell/src/App.jsx import React, { Suspense } from 'react'; // リモートコンポーネントを動的にインポート const ProductCard = React.lazy(() => import('product_catalog/ProductCard')); const App = () => { return ( <div> <h1>Welcome to the Main Shell!</h1> <h2>Featured Products:</h2> <Suspense fallback={<div>Loading Product Card...</div>}> <ProductCard name="Stylish Headphones" price="199.99" /> <ProductCard name="Ergonomic Keyboard" price="120.00" /> </Suspense> </div> ); }; export default App;
そしてそのwebpack.config.js
です。
// main-shell/webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; module.exports = { mode: 'development', entry: './src/index.js', output: { publicPath: 'http://localhost:3000/', }, devServer: { port: 3000, }, module: { rules: [ { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: ['@babel/preset-react'], }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'main_shell', remotes: { product_catalog: 'product_catalog@http://localhost:3001/remoteEntry.js', // リモートアプリケーションを定義 }, shared: { // ここでの共有構成はリモートのものと一致し、singletonインスタンスを保証します。 react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], };
ホストの構成のremotes
フィールドは、Webpackにproduct_catalog
のremoteEntry.js
ファイルがどこにあるかを伝えます。shared
構成はリモートのものと一致し、react
とreact-dom
がsingletonインスタンスであることを保証します。
main-shell
アプリケーションがロードされると、product_catalog
のremoteEntry.js
(小さなマニフェストファイル)を動的にフェッチします。これはProductCard
コンポーネントを取得する方法をWebpackに伝えます。これにより、物理的に分離されたアプリケーションの実行時統合が可能になります。
アプリケーションシナリオ
モジュールフェデレーションは、次のようなシナリオで優れています。
- モノリシックなフロントエンドをマイクロフロントエンドに分解する: 各マイクロフロントエンドは、異なるチームによって個別に開発、デプロイ、スケーリングできます。
- 共通のコンポーネントまたはライブラリを共有する: UIコンポーネントライブラリ、ユーティリティ関数、またはビジネスロジックモジュール全体を複数のアプリケーションに共有することで、コードの重複を回避します。
- A/Bテストまたは機能フラグを実装する: アプリケーション全体を再デプロイすることなく、ユーザーグループまたは実験条件に基づいてコンポーネントまたは機能の異なるバージョンを動的にロードします。
- 拡張可能なプラットフォームを構築する: サードパーティ開発者または他の内部チームが、フェデレーションできる独自のモジュールを提供することで、コアプラットフォームを拡張できるようにします。
結論
モジュールフェデレーションは、大規模なフロントエンド開発へのアプローチ方法におけるパラダイムシフトとして位置づけられます。開発者が複数の独立したアプリケーションを単一のまとまりのあるエクスペリエンスに構成できるようにすることで、スケーラビリティを大幅に向上させ、デプロイメントを簡素化し、多様なチームやプロジェクト間でのコードの再利用を促進します。この強力なWebpack機能は、Webアプリケーションのモジュラーな未来を真に可能にし、真に独立した堅牢なマイクロフロントエンドアーキテクチャの約束に私たちを近づけます。