# 画面設計書 19-App (Host)

## 概要

本ドキュメントは、webpackサンプルプロジェクト「module-federation」に含まれるApp (Host)画面の設計仕様を記載する。Module Federationのホストアプリケーションとして、リモートのmfe-bおよびmfe-cからコンポーネントを消費するReactアプリケーションである。マイクロフロントエンドアーキテクチャのデモンストレーションを目的とする。

### 本画面の処理概要

App (Host)は、module-federationサンプルにおけるホストアプリケーションであり、Module Federation機能を使用して複数のリモートマイクロフロントエンドからコンポーネントを取得・レンダリングする。

**業務上の目的・背景**：本画面はwebpack 5のModule Federation機能を検証するために存在する。Module Federationは独立してビルド・デプロイされた複数のアプリケーション間でモジュールを動的に共有するメカニズムである。本画面はホストとして、mfe-b（Component）とmfe-c（Component、LazyComponent）という2つのリモートからコンポーネントを消費し、reactをsingleton制約で共有する構成を実証する。これはマイクロフロントエンドアーキテクチャの代表的なパターンである。

**画面へのアクセス方法**：index.htmlを直接ブラウザで開く。HTMLには`/dist/aaa/app.js`（ホスト）、`/dist/bbb/mfeBBB.js`（リモートB）、`/dist/ccc/mfeCCC.js`（リモートC）のスクリプトがasync属性付きで読み込まれる。ローディング中はスピナーが表示される。

**主要な操作・処理内容**：
1. index.js（エントリーポイント）が`import("./bootstrap")`で非同期境界を作成し、共有モジュール初期化を待機する
2. bootstrap.jsがReactDomを使用してAppコンポーネントをDOMにマウントする
3. App.jsがmfe-b/Componentとmfe-c/Componentをリモートモジュールとしてインポートする
4. mfe-c/Component2はReact.lazyで遅延読み込みされ、Suspenseのfallbackでローディング表示する
5. ComponentB、ComponentC、ComponentD（遅延）がそれぞれレンダリングされる

**画面遷移**：単一ページアプリケーションであり、他画面への遷移は定義されていない。リモートコンポーネントの読み込みはページ内で完結する。

**権限による表示制御**：権限による表示制御は実装されていない。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 37 | ModuleFederationPlugin | 主機能 | ホストアプリケーションとしてリモートモジュール(mfe-b,mfe-c)を消費 |
| 39 | リモートモジュール | 主機能 | mfe-b/Component, mfe-c/Componentのリモートモジュール取得 |
| 38 | コンテナ管理 | 補助機能 | ContainerReferencePluginによるリモートコンテナの参照・接続 |
| 40 | SharePlugin | 補助機能 | react,date-fns等の依存パッケージをリモート間で共有 |
| 41 | 共有モジュール提供・消費 | 補助機能 | ConsumeSharedPluginによる共有モジュールの消費 |
| 13 | 動的インポート | 補助機能 | ComponentD(mfe-c/Component2)のReact.lazyによる遅延読み込み |

## 画面種別

SPA（シングルページアプリケーション）- マイクロフロントエンドホスト

## URL/ルーティング

- `index.html` - ホストアプリケーション表示用
- エントリポイント: `app` -> `./src/index.js`
- ビルド出力: `dist/aaa/app.js`（publicPath: `dist/aaa/`）
- リモートエントリ: `mfeBBB@/dist/bbb/mfeBBB.js`, `mfeCCC@/dist/ccc/mfeCCC.js`

## 入出力項目

| 項目名 | 種別 | データ型 | 必須 | 説明 |
|--------|------|----------|------|------|
| なし | - | - | - | 本画面はユーザー入力を受け付けない |

## 表示項目

| 項目名 | 種別 | コンポーネント | 説明 |
|--------|------|----------------|------|
| タイトル | テキスト | h1 | "Hello World" |
| リモートB説明 | テキスト | p | "This component is from a remote container:" |
| ComponentB | リモートコンポーネント | mfe-b/Component | 青ボーダー付きdiv。date-fnsで日付表示。localeプロパティにde（ドイツ語）を渡す |
| リモートC説明 | テキスト | p | "And this component is from another remote container:" |
| ComponentC | リモートコンポーネント | mfe-c/Component | 赤ボーダー付きdiv。date-fnsで日付表示。localeプロパティにde（ドイツ語）を渡す |
| ローディングメッセージ | テキスト | Suspense fallback | "Lazy loading component..." |
| 遅延読み込み説明 | テキスト | p | "And this component is from this remote container too, but lazy loaded:" |
| ComponentD | 遅延リモートコンポーネント | mfe-c/Component2 (LazyComponent) | 緑ボーダー付きdiv。lodash/randomを使用。React.lazyで遅延読み込み |
| スピナー | CSS表示 | div.spinner | ページ読み込み中に表示されるCSSアニメーションスピナー |

## イベント仕様

### 1-ページ初期読み込み

1. index.htmlがロードされ、スピナーが表示される
2. app.js、mfeBBB.js、mfeCCC.jsがasyncで並列読み込みされる
3. index.jsが`import("./bootstrap")`で非同期境界を作成
4. 共有モジュール（react）の初期化とリモートコンテナの接続が完了

### 2-Appコンポーネントマウント

1. bootstrap.jsがReactDom.renderでAppコンポーネントをmain要素にマウント
2. スピナーがDOMから削除される（`document.body.removeChild`）
3. ComponentB（mfe-b/Component）がレンダリングされ、date-fnsで日付を表示
4. ComponentC（mfe-c/Component）がレンダリングされ、date-fnsで日付を表示

### 3-遅延コンポーネント読み込み

1. React.Suspenseのfallbackとして"Lazy loading component..."が表示される
2. React.lazy(() => import("mfe-c/Component2"))によりComponentDが非同期ロードされる
3. LazyComponent（src-c/LazyComponent.js）がロード完了後にレンダリングされる
4. lodash/randomを使用してランダムな数値を表示する

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| なし | - | - | 本画面はフロントエンドのみのデモであり、データベース操作は存在しない |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| M-01 | 画面表示 | "Hello World" | ページ初期表示時 |
| M-02 | 画面表示 | "I'm a Component exposed from container B!" | ComponentBレンダリング時 |
| M-03 | 画面表示 | "I'm a Component exposed from container C!" | ComponentCレンダリング時 |
| M-04 | 画面表示 | "Lazy loading component..." | ComponentD読み込み中 |
| M-05 | 画面表示 | "I'm a lazy Component exposed from container C!" | ComponentDレンダリング時 |

## 例外処理

| 例外 | 条件 | 動作 |
|------|------|------|
| リモートエントリロード失敗 | mfeBBB.jsまたはmfeCCC.jsが読み込めない場合 | リモートコンポーネントが解決できずエラー |
| 共有モジュール競合 | reactのバージョン不整合が発生した場合 | singleton制約によりエラーまたは警告 |
| 遅延コンポーネントロード失敗 | mfe-c/Component2のチャンクが読み込めない場合 | Suspenseのfallbackが表示され続ける。エラーバウンダリ未設定のためアンキャッチエラーの可能性 |
| スピナー削除エラー | spinnerクラスのDOM要素が見つからない場合 | getElementsByClassName()[0]がundefinedとなりTypeError |

## 備考

- webpack.config.jsでは3つのビルド設定（app, mfe-b, mfe-c）が1ファイルに定義されている
- 実際のプロダクションではリモートは別リポジトリ・別ビルドであることが一般的
- react はsingleton: trueで共有され、全コンテナで同一インスタンスが使用される
- date-fnsはmfe-bとmfe-cの間で共有されるが、ホスト(app)は認知していない
- uniqueNameが各ビルドに設定されており（module-federation-aaa, bbb, ccc）、ランタイムの衝突を防止
- `nodeEnv: "production"`により、常にReactのプロダクション版が使用される

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

Module Federationの構成（ホスト・リモート・共有モジュール）を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | webpack.config.js | `examples/module-federation/webpack.config.js` | 行32-79: appビルド設定。行58-77: ModuleFederationPlugin設定。remotesでmfe-bとmfe-cのURLを指定、sharedでreactをsingleton共有 |

**読解のコツ**: Module Federationは3つの概念を理解する必要がある。(1) ホスト（コンシューマ）はremotes設定でリモートを参照、(2) リモート（プロバイダ）はexposes設定でモジュールを公開、(3) shared設定で共通モジュールの重複を排除。

#### Step 2: エントリーポイントを理解する

index.jsの非同期境界パターンを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `examples/module-federation/src/index.js` | 行10: `import("./bootstrap")`で非同期境界を作成。共有モジュールの初期化とリモートモジュールのロードには非同期処理が必要であるため、この境界が必須 |

**主要処理フロー**:
1. **行1-8**: コメントによる設計意図の説明（共有モジュールとリモートモジュールの非同期初期化の必要性）
2. **行10**: `import("./bootstrap")` - 非同期境界の作成。これにより共有モジュールが初期化される

#### Step 3: ブートストラップ処理を理解する

bootstrap.jsでのReactアプリケーションマウントを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | bootstrap.js | `examples/module-federation/src/bootstrap.js` | 行1-3: ReactDomとAppのインポート。行6-7: main要素を作成しReactDom.renderでマウント。行8: DOMにappendChild。行11: スピナー削除 |

**主要処理フロー**:
- **行1**: `import ReactDom from "react-dom"` - ReactDOMのインポート（共有モジュール）
- **行2**: `import React from "react"` - Reactのインポート（共有モジュール、singleton）
- **行3**: `import App from "./App"` - Appコンポーネントのインポート
- **行6**: `const el = document.createElement("main")` - main要素の作成
- **行7**: `ReactDom.render(<App />, el)` - Appのマウント
- **行8**: `document.body.appendChild(el)` - DOMに追加
- **行11**: `document.body.removeChild(document.getElementsByClassName("spinner")[0])` - スピナー削除

#### Step 4: Appコンポーネントを理解する

リモートモジュールの消費パターンを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | App.js | `examples/module-federation/src/App.js` | 行2-3: リモートモジュールをimportで取得（通常のパッケージと同様に使用）。行4: date-fnsのlocaleインポート。行7: React.lazyでmfe-c/Component2を遅延読み込み。行9-25: JSXテンプレート |

**主要処理フロー**:
- **行2**: `import ComponentB from "mfe-b/Component"` - リモートBからコンポーネント取得
- **行3**: `import ComponentC from "mfe-c/Component"` - リモートCからコンポーネント取得
- **行4**: `import { de } from "date-fns/locale"` - ドイツ語ロケール
- **行7**: `const ComponentD = React.lazy(() => import("mfe-c/Component2"))` - 遅延読み込み
- **行15**: `<ComponentB locale={de} />` - リモートBコンポーネントのレンダリング
- **行17**: `<ComponentC locale={de} />` - リモートCコンポーネントのレンダリング
- **行18-23**: `<React.Suspense>` でComponentDの遅延読み込みをラップ

#### Step 5: HTMLテンプレートを理解する

スクリプト読み込み構成とスピナーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | index.html | `examples/module-federation/index.html` | 行1-64: CSSスピナーの定義。行68: スピナーDOM要素。行72: app.jsをasyncで読み込み。行77-78: mfeBBB.jsとmfeCCC.jsをasyncで読み込み（性能最適化のためオプションだが推奨） |

### プログラム呼び出し階層図

```
index.html
    |
    +-- app.js (async)
    |       |
    |       +-- src/index.js [エントリポイント]
    |               |
    |               +-- import("./bootstrap")  [非同期境界]
    |                       |
    |                       +-- bootstrap.js
    |                               |
    |                               +-- import ReactDom (共有singleton)
    |                               +-- import React (共有singleton)
    |                               +-- import App
    |                               |       |
    |                               |       +-- App.js
    |                               |               |
    |                               |               +-- import ComponentB (mfe-b/Component)  [リモート]
    |                               |               +-- import ComponentC (mfe-c/Component)  [リモート]
    |                               |               +-- React.lazy(import("mfe-c/Component2"))  [遅延リモート]
    |                               |               +-- import { de } from "date-fns/locale"
    |                               |
    |                               +-- ReactDom.render(<App />, el)
    |                               +-- document.body.removeChild(spinner)
    |
    +-- mfeBBB.js (async) [リモートBエントリ]
    |       +-- src-b/Component.js [expose]
    |
    +-- mfeCCC.js (async) [リモートCエントリ]
            +-- src-c/Component.js [expose]
            +-- src-c/LazyComponent.js [expose, 遅延]
```

### データフロー図

```
[入力]                         [処理]                            [出力]

index.html              スピナー表示
                             |
app.js (async)          index.js実行                        main要素にApp描画
mfeBBB.js (async)       import("./bootstrap")                    |
mfeCCC.js (async)            |                            +------+------+
                        共有モジュール初期化               |      |      |
                        リモートコンテナ接続          ComponentB ComponentC ComponentD
                             |                        (mfe-b)  (mfe-c)  (mfe-c lazy)
                        bootstrap.js実行                   |      |      |
                             |                          date-fns date-fns lodash/random
                        ReactDom.render(<App />)           |      |      |
                             |                        "container "container "lazy
                        スピナー削除                    B!"     C!"     Component C!"
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `examples/module-federation/src/index.js` | ソース | ホストのエントリポイント。非同期境界を作成 |
| bootstrap.js | `examples/module-federation/src/bootstrap.js` | ソース | Reactアプリケーションのマウント処理 |
| App.js | `examples/module-federation/src/App.js` | ソース | メインコンポーネント。リモートモジュールを消費 |
| Component.js (B) | `examples/module-federation/src-b/Component.js` | ソース | リモートBが公開するコンポーネント |
| Component.js (C) | `examples/module-federation/src-c/Component.js` | ソース | リモートCが公開するコンポーネント |
| LazyComponent.js | `examples/module-federation/src-c/LazyComponent.js` | ソース | リモートCが公開する遅延読み込みコンポーネント |
| webpack.config.js | `examples/module-federation/webpack.config.js` | 設定 | 3つのビルド（app, mfe-b, mfe-c）の設定 |
| index.html | `examples/module-federation/index.html` | テンプレート | ホストアプリケーション用HTML。スピナーとスクリプト読み込み |
