# 機能設計書 70-動的import

## 概要

本ドキュメントは、CoffeeScriptの動的import（Dynamic Import）機能の設計を記述する。動的importは、`import()`構文を使用して実行時にモジュールを非同期で読み込む言語機能である。

### 本機能の処理概要

CoffeeScriptの動的importは、ES2020で標準化された`import()`構文をサポートする。通常の`import`文がコンパイル時にモジュールを静的に解決するのに対し、`import()`は実行時に動的にモジュールを読み込み、Promiseを返す。これにより、条件付きインポート、遅延ロード、コード分割などが可能になる。

**業務上の目的・背景**：
大規模なWebアプリケーションでは、初期ロード時間を短縮するためにコード分割（Code Splitting）が重要である。動的importにより、必要になったタイミングでモジュールを読み込むことができ、初期バンドルサイズを削減できる。また、条件に基づいてモジュールを選択的に読み込むことも可能になる。

**機能の利用シーン**：
- 遅延ロード（例：ルート変更時に該当コンポーネントを読み込む）
- 条件付きインポート（例：環境に応じて異なるモジュールを読み込む）
- コード分割（Webpack/Rollupと連携）
- 動的モジュールパス（例：`import("./locale/#{lang}.js")`）
- import assertionsとの組み合わせ

**主要な処理内容**：
1. `import(`の検出とDYNAMIC_IMPORTトークン生成
2. 引数（モジュールパス、オプション）の解析
3. DynamicImport、DynamicImportCallノードの生成
4. 引数の数の検証（1-2個）
5. JavaScriptの`import()`呼び出しとしてコンパイル

**関連システム・外部連携**：
動的importはlexer.coffeeでトークン化され、grammar.coffeeのInvocation規則でDynamicImportCallノードとして生成される。生成されるJavaScriptはES2020の`import()`構文となり、モジュールバンドラーやNode.js ESMで処理される。

**権限による制御**：
本機能はコンパイル時の言語機能であり、権限による制御は適用されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面関連なし（言語機能） |

## 機能種別

言語構文変換 / モジュール管理 / 非同期処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| modulePath | Expression | Yes | モジュールパス（文字列または式） | - |
| options | Object | No | import assertions等のオプション（第2引数） | - |

### 入力データソース

CoffeeScriptソースコード内の`import()`呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| JavaScript | String | `import()`式 |
| Promise | Promise | モジュールオブジェクトを解決するPromise（実行時） |

### 出力先

JavaScriptコード（標準出力またはファイル）

## 処理フロー

### 処理シーケンス

```
1. import( の検出
   └─ Lexer.literalToken()で`import`の後に`(`が続くことを検出
2. DYNAMIC_IMPORTトークン生成
   └─ 通常のIMPORTトークンをDYNAMIC_IMPORTに変更
3. 引数の解析
   └─ Arguments規則で引数リストを解析
4. DynamicImportCallノード生成
   └─ grammar.coffeeのInvocation規則で生成
5. バリデーション
   └─ 引数の数が1または2であることを確認
6. コンパイル処理
   └─ import(path)またはimport(path, options)として出力
```

### フローチャート

```mermaid
flowchart TD
    A[importトークン検出] --> B{次が ( ?}
    B -->|No| C[通常のimport文]
    B -->|Yes| D[DYNAMIC_IMPORTトークンに変更]
    D --> E[引数解析]
    E --> F{引数の数}
    F -->|0| G[エラー: accepts one or two arguments]
    F -->|1| H[DynamicImportCall生成]
    F -->|2| H
    F -->|3以上| G
    H --> I[import(...)として出力]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-70-1 | 引数制約 | import()は1つまたは2つの引数を受け取る | 常時 |
| BR-70-2 | 非同期処理 | import()はPromiseを返す | 常時 |
| BR-70-3 | 動的パス | モジュールパスに式を使用可能 | - |
| BR-70-4 | トップレベル非必須 | import()は関数内やif文内で使用可能 | - |
| BR-70-5 | import.meta | import.metaも利用可能（別機能） | - |

### 計算ロジック

特になし（関数呼び出しとしてそのまま出力）

## データベース操作仕様

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

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SyntaxError | 構文エラー | import() accepts either one or two arguments | 引数を1つまたは2つにする |
| RuntimeError | 実行時エラー | モジュールが見つからない | モジュールパスを修正 |

### リトライ仕様

実行時のモジュール読み込みエラーはアプリケーション側で処理。

## トランザクション仕様

本機能はデータベーストランザクションを使用しない。

## パフォーマンス要件

- コンパイル時間: 通常の関数呼び出しと同等
- 実行時: モジュールのダウンロード・解析時間に依存

## セキュリティ考慮事項

- 動的なモジュールパスは信頼できる入力のみを使用
- ユーザー入力をモジュールパスに直接使用することは危険
- CSP（Content Security Policy）との互換性を確認

## 備考

- ES2020で標準化された機能
- WebpackやRollupでコード分割ポイントとして認識される
- Node.js 14.0.0以降でサポート
- import assertionsとの組み合わせで`{ assert: { type: 'json' } }`等が使用可能

---

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

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

### 推奨読解順序

#### Step 1: LexerのDYNAMIC_IMPORT検出を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | lexer.coffee | `src/lexer.coffee` | literalToken内のDYNAMIC_IMPORT変換（784-785行目） |

**読解のコツ**:
- 784-785行目: `import`の後に`(`が続く場合、IMPORTトークンをDYNAMIC_IMPORTに変更
- これにより文法規則で静的importと区別可能に

**主要処理フロー**:
1. **784行目**: `value is '(' and prev?[0] is 'IMPORT'`を検出
2. **785行目**: `prev[0] = 'DYNAMIC_IMPORT'`でトークンを変更

#### Step 2: 文法定義を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | grammar.coffee | `src/grammar.coffee` | Invocation規則内のDYNAMIC_IMPORT（564行目）、ObjSpreadExpr内（301行目） |

**主要処理フロー**:
- **564行目**: `DYNAMIC_IMPORT Arguments` → `new DynamicImportCall`
- **301行目**: ObjSpreadExprでも同様のパターン
- DynamicImportノードとDynamicImportCallノードを使用

#### Step 3: ノードクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | nodes.coffee | `src/nodes.coffee` | DynamicImport、DynamicImportCallクラス（3458-3475行目） |

**主要処理フロー**:
- **3458行目**: `class DynamicImport extends Base`
- **3459-3460行目**: `compileNode`は単に`'import'`を出力
- **3464行目**: `class DynamicImportCall extends Call`
- **3465-3467行目**: `compileNode`でcheckArguments後にsuper呼び出し
- **3469-3471行目**: `checkArguments`で引数数を検証

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

```
CoffeeScript.compile()
    │
    ├─ Lexer.tokenize()
    │      └─ literalToken()
    │             └─ import + ( → DYNAMIC_IMPORT
    │
    ├─ parser.parse()
    │      └─ Invocation規則
    │             └─ DYNAMIC_IMPORT Arguments
    │                    └─ DynamicImportCall生成
    │                           └─ checkArguments()
    │
    └─ DynamicImportCall.compileNode()
           └─ import(...) として出力
```

### データフロー図

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

CoffeeScript           Lexer           Parser              Compiler
import()呼び出し   →  トークン列  →   DynamicImportCall →  JavaScript
                                    ノード               import()

例:
module = await import("./module-#{name}.js")
    ↓
[DYNAMIC_IMPORT, CALL_START, STRING_START, ..., STRING_END, CALL_END]
    ↓
DynamicImportCall {
  variable: DynamicImport,
  args: [StringWithInterpolations(...)]
}
    ↓
var module = await import(`./module-${name}.js`);
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| lexer.coffee | `src/lexer.coffee` | ソース | DYNAMIC_IMPORTトークン変換（784-785行目） |
| grammar.coffee | `src/grammar.coffee` | ソース | Invocation規則（564行目）、ObjSpreadExpr（301行目） |
| nodes.coffee | `src/nodes.coffee` | ソース | DynamicImport、DynamicImportCallクラス（3458-3475行目） |
