# 機能設計書 46-requireフック

## 概要

本ドキュメントは、Node.jsの`require()`で.coffeeファイルを直接読み込み可能にするrequireフック機能の設計を記載する。

### 本機能の処理概要

requireフックは、Node.jsのモジュールシステムに対してCoffeeScriptファイル（.coffee、.litcoffee、.coffee.md）の拡張子を登録し、`require('./module.coffee')`のような呼び出しで直接CoffeeScriptファイルを読み込み・コンパイル・実行可能にする機能である。これにより、明示的なコンパイルステップなしにCoffeeScriptモジュールをNode.jsアプリケーションで使用できる。

**業務上の目的・背景**：Node.jsでCoffeeScriptを使用する際、通常はソースコードをJavaScriptにコンパイルしてから実行する必要がある。しかし、開発時には即座にコードを実行したいケースが多い。requireフックを使用することで、事前コンパイル不要でCoffeeScriptファイルをモジュールとして読み込め、開発サイクルを短縮できる。

**機能の利用シーン**：
- CoffeeScriptで書かれたNode.jsアプリケーションを直接実行するとき
- REPLでCoffeeScriptモジュールを読み込むとき
- テストランナーでCoffeeScriptのテストファイルを実行するとき
- coffeeコマンドでスクリプトを実行するとき

**主要な処理内容**：
1. require.extensionsにCoffeeScript拡張子（.coffee、.litcoffee、.coffee.md）を登録
2. マルチドット拡張子に対応するためModule.prototype.loadをパッチ
3. ファイル読み込み時にCoffeeScriptをコンパイルしてmodule._compileで実行
4. スタックトレース変換のためpatchStackTraceを有効化

**関連システム・外部連携**：
- Node.jsのモジュールシステム（require.extensions、Module）
- CoffeeScriptコンパイラ
- ソースマップ機能（デバッグ用）

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 21 | Annotated Source - Register | 主機能 | Node.js登録処理register.coffeeのソースコード解説表示 |
| 36 | Annotated Source - Register | 主機能 | Node.js登録処理register.coffeeのソースコード解説表示（v2） |

## 機能種別

モジュールシステム拡張 / ファイルローダー

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| filename | string | Yes | 読み込むCoffeeScriptファイルのパス | CoffeeScript拡張子であること |
| module | Module | Yes | Node.jsのモジュールオブジェクト | Moduleインスタンスであること |

### 入力データソース

- ファイルシステム（.coffeeファイル）
- 親モジュールからの`require()`呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| module.exports | any | モジュールのエクスポート内容 |
| module.loaded | boolean | モジュールが正常に読み込まれたかどうか |

### 出力先

Node.jsのモジュールキャッシュ（require.cache）

## 処理フロー

### 処理シーケンス

```
1. CoffeeScript.register()の呼び出し
   └─ register.coffeeがrequireされる

2. スタックトレースパッチの適用
   └─ Error.prepareStackTraceまたはNode.jsの--enable-source-mapsが無効な場合にpatchStackTrace

3. require.extensionsへの登録
   └─ 各CoffeeScript拡張子にloadFile関数を登録

4. Module.prototype.loadのパッチ
   └─ マルチドット拡張子に対応

5. ファイル読み込み時（loadFile）
   └─ CoffeeScript._compileFile → module._compile
```

### フローチャート

```mermaid
flowchart TD
    A["require('./module.coffee')"] --> B{拡張子は?}
    B -->|.coffee/.litcoffee/.coffee.md| C[loadFile呼び出し]
    B -->|その他| D[デフォルトローダー]
    C --> E[オプション取得]
    E --> F{ソースマップ有効?}
    F -->|Yes| G["options.inlineMap = true"]
    F -->|No| G
    G --> H["CoffeeScript._compileFile(filename)"]
    H --> I[JavaScriptコード生成]
    I --> J["module._compile(js, filename)"]
    J --> K[module.loaded = true]
    K --> L[module.exports返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 拡張子登録 | .coffee、.litcoffee、.coffee.mdの3種類の拡張子を登録 | register呼び出し時 |
| BR-02 | マルチドット拡張子対応 | .coffee.mdのようなマルチドット拡張子を正しく処理 | ファイル読み込み時 |
| BR-03 | ソースマップ自動生成 | スタックトレース変換またはNode.jsのソースマップが有効な場合、インラインソースマップを生成 | ファイル読み込み時 |
| BR-04 | ルートモジュールオプション継承 | 親モジュールチェーンを遡ってルートモジュールのoptionsを取得 | オプション適用時 |
| BR-05 | BOM除去 | ファイル先頭のUnicodeバイトオーダーマークを自動除去 | ファイル読み込み時 |

### 計算ロジック

マルチドット拡張子の検索（register.coffee 40-48行目）:
```coffeescript
findExtension = (filename) ->
  extensions = path.basename(filename).split '.'
  # Remove the initial dot from dotfiles.
  extensions.shift() if extensions[0] is ''
  # Start with the longest possible extension and work our way shortwards.
  while extensions.shift()
    curExtension = '.' + extensions.join '.'
    return curExtension if Module._extensions[curExtension]
  '.js'
```

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SyntaxError | 構文エラー | CoffeeScriptファイルに構文エラーがある | エラー箇所を修正 |
| ENOENT | ファイルエラー | 指定されたファイルが存在しない | ファイルパスを確認 |
| Error | 登録エラー | register()を呼び出さずにCoffeeScriptファイルをrequire | CoffeeScript.register()を先に呼び出す |

### リトライ仕様

該当なし。エラー発生時はrequire呼び出しが失敗し、例外がスローされる。

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

該当なし

## パフォーマンス要件

- ファイルの読み込みとコンパイルはモジュール初回ロード時のみ実行
- 一度ロードされたモジュールはrequire.cacheにキャッシュされ、2回目以降の呼び出しはキャッシュから返される
- コンパイル時間はファイルサイズとコードの複雑さに依存

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

- CoffeeScriptファイルはJavaScriptにコンパイルされて実行されるため、信頼できるソースからのファイルのみを読み込むこと
- register()はグローバルなモジュールシステムに影響するため、ライブラリ作成時は慎重に使用

## 備考

- `require('coffeescript/register')`を呼び出すことでもregisterを有効化可能
- Node.jsの`--enable-source-maps`フラグが有効な場合、CoffeeScript独自のスタックトレースパッチは無効化される
- Babelトランスパイルを使用する場合は、親モジュールのoptionsでtranspileを設定

---

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

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

### 推奨読解順序

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

requireフックが操作するNode.jsのモジュールシステムのデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | register.coffee | `src/register.coffee` | require.extensionsとModule参照 |

**読解のコツ**: Node.jsでは`require.extensions`オブジェクトに拡張子をキーとしてローダー関数を登録できる。`Module`はNode.jsの内部モジュールクラス。

#### Step 2: スタックトレースパッチを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | register.coffee | `src/register.coffee` | patchStackTrace呼び出し条件（9-16行目） |

**主要処理フロー**:
- **9-11行目**: Node.jsの`--enable-source-maps`フラグ有効性チェック
- **14-16行目**: `Error.prepareStackTrace`が未定義かつNode.jsのソースマップが無効な場合にpatchStackTraceを呼び出し

#### Step 3: loadFile関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | register.coffee | `src/register.coffee` | loadFile関数（19-28行目） |

**主要処理フロー**:
- **20行目**: `module.options`または`getRootModule(module).options`でオプション取得
- **24-25行目**: ソースマップが必要な場合`options.inlineMap = true`
- **26行目**: `CoffeeScript._compileFile`でコンパイル
- **28行目**: `module._compile`でJavaScriptを実行

#### Step 4: require.extensionsへの登録を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | register.coffee | `src/register.coffee` | 拡張子登録ループ（32-34行目） |

**主要処理フロー**:
- **32行目**: `if require.extensions`でNode.js環境を確認
- **33-34行目**: `for ext in CoffeeScript.FILE_EXTENSIONS`で各拡張子にloadFileを登録

#### Step 5: マルチドット拡張子対応を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | register.coffee | `src/register.coffee` | findExtension関数とModule::loadパッチ（40-55行目） |

**主要処理フロー**:
- **40-48行目**: `findExtension`関数 - ファイル名から最長マッチする拡張子を検索
- **50-55行目**: `Module::load`をオーバーライドして拡張子検索ロジックを適用

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

```
CoffeeScript.register() (index.coffee:105)
    │
    └─ require './register' (register.coffee)
           │
           ├─ patchStackTrace() (coffeescript.coffee:253) [条件付き]
           │
           ├─ require.extensions['.coffee'] = loadFile
           │  require.extensions['.litcoffee'] = loadFile
           │  require.extensions['.coffee.md'] = loadFile
           │
           └─ Module::load パッチ適用
                  │
                  └─ findExtension(filename)

require('./module.coffee')
    │
    └─ Module::load (パッチ済み)
           │
           ├─ findExtension('.coffee.md')
           │      └─ '.coffee.md' を返す
           │
           └─ require.extensions['.coffee'] = loadFile
                  │
                  ├─ getRootModule(module)
                  │      └─ 親を再帰的に探索
                  │
                  ├─ CoffeeScript._compileFile(filename, options)
                  │      │
                  │      ├─ fs.readFileSync (ファイル読み込み)
                  │      │
                  │      └─ CoffeeScript.compile (コンパイル)
                  │
                  └─ module._compile(js, filename)
```

### データフロー図

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

.coffeeファイル ────▶ loadFile
                         │
                         ├─▶ getRootModule → options取得
                         │
                         ├─▶ CoffeeScript._compileFile
                         │       │
                         │       ├─▶ fs.readFileSync
                         │       │
                         │       └─▶ CoffeeScript.compile
                         │                 │
                         │                 ▼
                         │           JavaScript + SourceMap
                         │
                         └─▶ module._compile
                                 │
                                 ▼
                           module.exports ──▶ require呼び出し元
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| register.coffee | `src/register.coffee` | ソース | requireフックのメイン実装 |
| index.coffee | `src/index.coffee` | ソース | CoffeeScript.register()の定義 |
| coffeescript.coffee | `src/coffeescript.coffee` | ソース | patchStackTraceの実装 |
| helpers.coffee | `src/helpers.coffee` | ソース | isCoffee関数（拡張子判定） |
