# Decap CMS コードリーディングガイドライン

## はじめに

このガイドラインは、Decap CMS のコードベースを効率的に理解するための手引きです。
JavaScript/TypeScript や React に精通していないエンジニアでも、段階的に学習できるよう構成されています。

Decap CMS は、Git ベースのヘッドレスコンテンツ管理システム（CMS）です。Web ブラウザ上で動作する SPA（Single Page Application）であり、コンテンツの編集結果を Git リポジトリ（GitHub、GitLab、Bitbucket 等）に直接コミットします。

**対象読者:**
- プロジェクトに新規参画するエンジニア
- 他言語からの経験者
- コードレビューを行う担当者

---

## 1. 言語基礎

> このセクションでは、JavaScript/TypeScript の基本構文と React の概念を解説します。

### 1.1 プログラム構造

本プロジェクトは **JavaScript (ES2015+)** と **TypeScript** の混在構成です。古いコードは `.js` / `.jsx` で書かれ、新しいコードは `.ts` / `.tsx` に移行中です。

Babel (`babel.config.js`) によってトランスパイルされるため、最新の JavaScript 構文が利用可能です。

```javascript
// ファイル: packages/decap-cms-core/src/bootstrap.js:1-19
import './lib/polyfill';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider, connect } from 'react-redux';
import { Route, Router } from 'react-router-dom';
import { GlobalStyles } from 'decap-cms-ui-default';
import { I18n } from 'react-polyglot';

import { store } from './redux';
import { history } from './routing/history';
import { loadConfig } from './actions/config';
import { authenticateUser } from './actions/auth';
```

- `import` 文で ES Modules をインポートします
- ファイル先頭に外部パッケージ、その後にプロジェクト内モジュールを配置する慣習があります

### 1.2 データ型と変数

TypeScript ファイルでは型定義が使われています。

```typescript
// ファイル: packages/decap-cms-core/src/reducers/auth.ts:14-18
export type Auth = {
  isFetching: boolean;
  user: User | undefined;
  error: string | undefined;
};
```

- `type` キーワードで型エイリアスを定義します
- `|` はユニオン型（「いずれか」）を表します
- `export` で外部モジュールに公開します

```typescript
// ファイル: packages/decap-cms-lib-util/src/implementation.ts:59-64
export type DataFile = {
  path: string;
  slug: string;
  raw: string;
  newPath?: string;     // ? は省略可能なプロパティ
};
```

### 1.3 制御構造

Redux の Reducer では `switch` 文が頻出します。

```typescript
// ファイル: packages/decap-cms-core/src/reducers/auth.ts:26-44
const auth = produce((state: Auth, action: AuthAction) => {
  switch (action.type) {
    case AUTH_REQUEST:
      state.isFetching = true;
      break;
    case AUTH_SUCCESS:
      state.user = action.payload;
      break;
    case AUTH_FAILURE:
      state.error = action.payload && action.payload.toString();
      break;
    case AUTH_REQUEST_DONE:
      state.isFetching = false;
      break;
    case LOGOUT:
      state.user = undefined;
      state.isFetching = false;
  }
}, defaultState);
```

- `produce` は Immer ライブラリの関数で、immutable な状態更新を直接変更のように書けます
- `action.type` の文字列でアクションの種類を判別します

### 1.4 関数/メソッド定義

本プロジェクトでは以下の関数定義パターンが使われています。

**通常の関数宣言:**
```javascript
// ファイル: packages/decap-cms-core/src/bootstrap.js:41-42
function bootstrap(opts = {}) {
  const { config } = opts;
```

**アロー関数（Redux Thunk アクション）:**
```typescript
// ファイル: packages/decap-cms-core/src/actions/entries.ts:93-100
export function entryLoading(collection: Collection, slug: string) {
  return {
    type: ENTRY_REQUEST,
    payload: {
      collection: collection.get('name'),
      slug,
    },
  };
}
```

**React クラスコンポーネントのメソッド:**
```javascript
// ファイル: packages/decap-cms-widget-string/src/StringControl.js:43-46
handleChange = e => {
  this._sel = e.target.selectionStart;
  this.props.onChange(e.target.value);
};
```

- `handleChange = e => { ... }` はクラスプロパティとしてのアロー関数で、`this` のバインドが自動的に行われます

### 1.5 モジュール/インポート

本プロジェクトはモノレポ構成のため、パッケージ間のインポートは npm パッケージ名で行います。

```javascript
// ファイル: packages/decap-cms-app/src/extensions.js:1-2
// Core
import { DecapCmsCore as CMS } from 'decap-cms-core';
```

パッケージ内部では相対パスを使用します。

```javascript
// ファイル: packages/decap-cms-core/src/bootstrap.js:9-13
import { store } from './redux';
import { history } from './routing/history';
import { loadConfig } from './actions/config';
import { authenticateUser } from './actions/auth';
import { getPhrases } from './lib/phrases';
```

- `'decap-cms-core'` → npm ワークスペース経由で `packages/decap-cms-core` を参照
- `'./redux'` → 同一パッケージ内の相対パス

---

## 2. プロジェクト固有の概念

> このセクションでは、当プロジェクト特有の概念を解説します。

### 2.1 フレームワーク固有の概念

#### React コンポーネント

UIの構成要素です。本プロジェクトではクラスコンポーネントが主流です（React 19 対応済み）。

```javascript
// ファイル: packages/decap-cms-widget-string/src/StringControl.js:4-12
export default class StringControl extends React.Component {
  static propTypes = {
    onChange: PropTypes.func.isRequired,
    forID: PropTypes.string,
    value: PropTypes.node,
    classNameWrapper: PropTypes.string.isRequired,
    setActiveStyle: PropTypes.func.isRequired,
    setInactiveStyle: PropTypes.func.isRequired,
  };
```

- `propTypes` でコンポーネントに渡されるプロパティの型を定義します
- `isRequired` は必須プロパティを示します

#### Redux（状態管理）

アプリケーション全体の状態を一元管理するライブラリです。

```typescript
// ファイル: packages/decap-cms-core/src/redux/index.ts
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { waitUntilAction } from './middleware/waitUntilAction';
import createRootReducer from '../reducers/combinedReducer';

const store = createStore(
  createRootReducer(),
  composeWithDevTools(applyMiddleware(thunkMiddleware, waitUntilAction)),
);
```

- **Store**: アプリケーションの状態を保持するオブジェクト
- **Reducer**: アクションに応じて状態を変更する純粋関数
- **Action**: 状態変更の要求を表すオブジェクト（`{ type: 'ACTION_TYPE', payload: ... }`）
- **Thunk**: 非同期アクションを可能にするミドルウェア

#### Immutable.js

一部の Reducer（特に古いコード）は Immutable.js の `Map` や `List` を使用しています。

```javascript
// ファイル: packages/decap-cms-core/src/reducers/index.ts:1
import { List } from 'immutable';
```

- `Map` / `List` は変更不可なデータ構造です
- `.get('key')`, `.getIn(['nested', 'key'])` でアクセスします
- `.set('key', value)`, `.setIn(...)` で新しいオブジェクトを返します

#### Emotion（CSS-in-JS）

スタイリングには `@emotion/styled` が使われています。

```javascript
// ファイル: packages/decap-cms-core/src/components/App/App.js:34-38
const AppMainContainer = styled.div`
  min-width: 800px;
  max-width: 1440px;
  margin: 0 auto;
`;
```

- テンプレートリテラルで CSS を記述し、React コンポーネントを生成します

### 2.2 プロジェクト独自のパターン

#### レジストリパターン

プラグインの登録・解決にグローバルレジストリが使われています。

```javascript
// ファイル: packages/decap-cms-core/src/lib/registry.js:23-35
const registry = {
  backends: {},           // Gitバックエンド実装
  templates: {},          // プレビューテンプレート
  previewStyles: [],      // プレビュー用CSS
  widgets: {},            // エディタウィジェット
  editorComponents: Map(),// Markdownエディタのカスタムコンポーネント
  remarkPlugins: [],      // remarkプラグイン
  widgetValueSerializers: {},
  mediaLibraries: [],     // メディアライブラリ
  locales: {},            // i18nロケール
  eventHandlers,          // ライフサイクルフック
  formats: {},            // カスタムファイルフォーマット
};
```

登録例:
```javascript
// ファイル: packages/decap-cms-app/src/extensions.js:35-43
CMS.registerBackend('github', GitHubBackend);
CMS.registerBackend('gitlab', GitLabBackend);
CMS.registerBackend('bitbucket', BitbucketBackend);
```

#### Implementation インターフェース

全 Git バックエンドが実装すべき契約です。

```typescript
// ファイル: packages/decap-cms-lib-util/src/implementation.ts:16-35
export interface ImplementationMediaFile {
  name: string;
  id: string;
  size?: number;
  displayURL?: DisplayURL;
  path: string;
  draft?: boolean;
  url?: string;
  file?: File;
}

export interface ImplementationEntry {
  data: string;
  file: { path: string; label?: string; id?: string | null; author?: string; updatedOn?: string };
}
```

#### ウィジェットパターン

各ウィジェットは `{ name, controlComponent, previewComponent }` の形式で登録されます。

```javascript
// ファイル: packages/decap-cms-widget-string/src/index.js
function Widget(opts = {}) {
  return {
    name: 'string',
    controlComponent,    // 編集UI（入力フォーム）
    previewComponent,    // プレビューUI（表示）
    ...opts,
  };
}
```

---

## 3. 命名規則

> このセクションでは、プロジェクト全体で使用される命名規則を解説します。

### 3.1 ファイル・ディレクトリ命名

| パターン | 意味 | 例 |
|---------|------|-----|
| `decap-cms-backend-{name}` | Git バックエンド実装パッケージ | `decap-cms-backend-github` |
| `decap-cms-widget-{name}` | エディタウィジェットパッケージ | `decap-cms-widget-string` |
| `decap-cms-lib-{name}` | 共有ライブラリパッケージ | `decap-cms-lib-util` |
| `decap-cms-ui-{name}` | UIコンポーネントパッケージ | `decap-cms-ui-default` |
| `decap-cms-media-library-{name}` | メディアライブラリプラグイン | `decap-cms-media-library-cloudinary` |
| `{Name}Control.js` | ウィジェットの編集UIコンポーネント | `StringControl.js` |
| `{Name}Preview.js` | ウィジェットのプレビューコンポーネント | `StringPreview.js` |
| `implementation.ts(x)` | バックエンドの実装クラス | `packages/decap-cms-backend-github/src/implementation.tsx` |
| `API.ts` | バックエンドのAPIクライアント | `packages/decap-cms-backend-github/src/API.ts` |

### 3.2 クラス・関数・変数命名

| プレフィックス/サフィックス | 意味 | 例 |
|---------------------------|------|-----|
| `select*` | Redux セレクター（状態から値を取得） | `selectEntry`, `selectLocale`, `selectUseWorkflow` |
| `register*` | レジストリへの登録関数 | `registerBackend`, `registerWidget` |
| `get*` | レジストリからの取得関数 | `getBackend`, `getWidget` |
| `*_REQUEST` | 非同期処理の開始アクション | `ENTRY_REQUEST`, `AUTH_REQUEST` |
| `*_SUCCESS` | 非同期処理の成功アクション | `ENTRY_SUCCESS`, `AUTH_SUCCESS` |
| `*_FAILURE` | 非同期処理の失敗アクション | `ENTRY_FAILURE`, `AUTH_FAILURE` |
| `*Control` | ウィジェットの入力コンポーネント | `StringControl`, `NumberControl` |
| `*Preview` | ウィジェットのプレビューコンポーネント | `StringPreview`, `NumberPreview` |
| `load*` | データ読み込みの Thunk アクション | `loadEntry`, `loadEntries`, `loadConfig` |
| `persist*` | データ保存の Thunk アクション | `persistEntry`, `persistMedia` |
| `navigate*` | 画面遷移関数 | `navigateToCollection`, `navigateToEntry` |
| `DecapCms*` | パッケージのエクスポート名 | `DecapCmsCore`, `DecapCmsWidgetString` |
| `handle*` | イベントハンドラ | `handleChange`, `handleLogin` |

### 3.3 プログラム分類一覧

| 分類 | ファイルの場所 | 説明 |
|-----|--------------|------|
| エントリーポイント | `packages/decap-cms/src/index.js` | ブラウザ向け最終バンドル |
| アプリ層 | `packages/decap-cms-app/src/` | プラグイン登録のオーケストレーション |
| コア | `packages/decap-cms-core/src/` | Redux ストア、ルーティング、コンポーネント |
| バックエンド | `packages/decap-cms-backend-*/src/` | Git プロバイダ別の実装 |
| ウィジェット | `packages/decap-cms-widget-*/src/` | エディタ入力フィールド |
| ライブラリ | `packages/decap-cms-lib-*/src/` | 共有ユーティリティ |
| UI | `packages/decap-cms-ui-*/src/` | 共通UIコンポーネント |

---

## 4. ディレクトリ構造

> このセクションでは、プロジェクトのディレクトリ構造を解説します。

```
decap-cms-main/
├── packages/                          # モノレポ: 全パッケージ群
│   ├── decap-cms/                     # 最外層パッケージ（ブラウザ向けバンドル）
│   ├── decap-cms-app/                 # アプリ層（プラグイン登録）
│   ├── decap-cms-core/                # コア（Redux, ルーティング, コンポーネント）
│   │   └── src/
│   │       ├── actions/               # Redux アクションクリエイター
│   │       ├── components/            # React コンポーネント
│   │       │   ├── App/               # メインアプリケーション
│   │       │   ├── Collection/        # コレクション一覧
│   │       │   ├── Editor/            # エディタ
│   │       │   ├── EditorWidgets/     # ウィジェットラッパー
│   │       │   ├── MediaLibrary/      # メディアライブラリ
│   │       │   ├── UI/               # 共通UIパーツ
│   │       │   └── Workflow/          # Editorial Workflow
│   │       ├── constants/             # 定数定義
│   │       ├── formats/               # ファイルフォーマット（YAML, TOML, JSON, Frontmatter）
│   │       ├── lib/                   # 内部ライブラリ（レジストリ, i18n, フォーマッタ）
│   │       ├── reducers/              # Redux リデューサー
│   │       ├── redux/                 # Redux ストア設定
│   │       │   └── middleware/        # カスタムミドルウェア
│   │       ├── routing/               # ルーティング
│   │       ├── types/                 # TypeScript 型定義
│   │       └── valueObjects/          # 値オブジェクト（Entry, AssetProxy）
│   ├── decap-cms-backend-github/      # GitHub バックエンド
│   ├── decap-cms-backend-gitlab/      # GitLab バックエンド
│   ├── decap-cms-backend-bitbucket/   # Bitbucket バックエンド
│   ├── decap-cms-backend-git-gateway/ # Git Gateway バックエンド
│   ├── decap-cms-backend-gitea/       # Gitea バックエンド
│   ├── decap-cms-backend-azure/       # Azure バックエンド
│   ├── decap-cms-backend-proxy/       # プロキシバックエンド
│   ├── decap-cms-backend-test/        # テスト用バックエンド
│   ├── decap-cms-widget-string/       # 文字列ウィジェット
│   ├── decap-cms-widget-number/       # 数値ウィジェット
│   ├── decap-cms-widget-text/         # テキストエリアウィジェット
│   ├── decap-cms-widget-markdown/     # Markdownウィジェット
│   ├── decap-cms-widget-image/        # 画像ウィジェット
│   ├── decap-cms-widget-file/         # ファイルウィジェット
│   ├── decap-cms-widget-select/       # セレクトボックスウィジェット
│   ├── decap-cms-widget-list/         # リストウィジェット
│   ├── decap-cms-widget-object/       # オブジェクトウィジェット
│   ├── decap-cms-widget-relation/     # リレーションウィジェット
│   ├── decap-cms-widget-boolean/      # ブーリアンウィジェット
│   ├── decap-cms-widget-datetime/     # 日時ウィジェット
│   ├── decap-cms-widget-map/          # マップウィジェット
│   ├── decap-cms-widget-code/         # コードブロックウィジェット
│   ├── decap-cms-widget-colorstring/  # カラーウィジェット
│   ├── decap-cms-lib-util/            # 共有ユーティリティ（Implementation インターフェース等）
│   ├── decap-cms-lib-auth/            # 認証ライブラリ
│   ├── decap-cms-lib-widgets/         # ウィジェット共通ライブラリ
│   ├── decap-cms-ui-default/          # デフォルトUI（GlobalStyles, Loader等）
│   ├── decap-cms-ui-auth/             # 認証UI
│   ├── decap-cms-media-library-cloudinary/  # Cloudinary メディアライブラリ
│   ├── decap-cms-media-library-uploadcare/  # Uploadcare メディアライブラリ
│   ├── decap-cms-locales/             # i18n ロケール
│   ├── decap-cms-default-exports/     # デフォルトエクスポート
│   ├── decap-cms-editor-component-image/ # 画像エディタコンポーネント
│   └── decap-server/                  # ローカル開発サーバー
├── cypress/                           # E2E テスト（Cypress）
├── dev-test/                          # 開発テスト用設定
├── scripts/                           # ビルド・リリーススクリプト
├── __mocks__/                         # Jestグローバルモック
├── babel.config.js                    # Babel 設定
├── jest.config.js                     # Jest 設定
├── tsconfig.json                      # TypeScript 設定
├── lerna.json                         # Lerna モノレポ設定
├── nx.json                            # Nx ビルドキャッシュ設定
└── package.json                       # ルート package.json（ワークスペース定義）
```

### 各ディレクトリの役割

| ディレクトリ | 役割 | 主要ファイル |
|-------------|------|-------------|
| `packages/decap-cms/` | ブラウザ向け最終パッケージ。`window.CMS` を公開 | `src/index.js` |
| `packages/decap-cms-app/` | 全プラグインの登録を集約 | `src/extensions.js` |
| `packages/decap-cms-core/` | アプリケーションのコアロジック | `src/bootstrap.js`, `src/backend.ts` |
| `packages/decap-cms-core/src/actions/` | Redux アクションクリエイター | `entries.ts`, `auth.ts`, `config.ts` |
| `packages/decap-cms-core/src/reducers/` | Redux リデューサー | `auth.ts`, `entries.ts`, `entryDraft.js` |
| `packages/decap-cms-core/src/components/` | React UIコンポーネント | `App/App.js`, `Editor/Editor.js` |
| `packages/decap-cms-core/src/formats/` | ファイルフォーマット変換 | `formats.ts`, `yaml.ts`, `frontmatter.ts` |
| `packages/decap-cms-core/src/lib/` | 内部ユーティリティ | `registry.js`, `formatters.ts`, `i18n.ts` |
| `packages/decap-cms-backend-github/` | GitHub API連携 | `src/implementation.tsx`, `src/API.ts` |
| `packages/decap-cms-lib-util/` | 共有型定義・ユーティリティ | `src/implementation.ts` |

---

## 5. アーキテクチャ

> このセクションでは、プロジェクトのアーキテクチャパターンを解説します。

### 5.1 全体アーキテクチャ

Decap CMS は **プラグインアーキテクチャ** を持つモノレポ構成の React + Redux SPA です。

```mermaid
graph TB
    subgraph "ブラウザ"
        A[decap-cms<br/>エントリーポイント] --> B[decap-cms-app<br/>プラグイン登録]
        B --> C[decap-cms-core<br/>コアロジック]

        subgraph "decap-cms-core"
            C --> D[Redux Store]
            C --> E[React Components]
            C --> F[Registry]
            C --> G[Router]
        end

        F --> H[Backends<br/>GitHub/GitLab/Bitbucket/...]
        F --> I[Widgets<br/>String/Number/Markdown/...]
        F --> J[Media Libraries<br/>Cloudinary/Uploadcare]
    end

    H --> K[Git API<br/>GitHub/GitLab/Bitbucket]
```

**初期化の流れ:**
```
decap-cms/index.js → decap-cms-app（extensions.js でプラグイン登録）→ decap-cms-core（bootstrap.js）
  → Redux Store 作成
  → config.yml 読み込み
  → 認証処理
  → React ツリーレンダリング
```

### 5.2 レイヤー構成

| レイヤー | 責務 | 代表的なファイル |
|---------|------|-----------------|
| エントリーポイント層 | ブラウザ向けバンドル、グローバル公開 | `packages/decap-cms/src/index.js` |
| プラグイン登録層 | バックエンド・ウィジェットの一括登録 | `packages/decap-cms-app/src/extensions.js` |
| UIコンポーネント層 | ユーザーインターフェース | `packages/decap-cms-core/src/components/` |
| 状態管理層 | Redux Store, Reducers, Actions | `packages/decap-cms-core/src/redux/`, `src/reducers/`, `src/actions/` |
| ビジネスロジック層 | バックエンドアダプタ、フォーマット変換 | `packages/decap-cms-core/src/backend.ts`, `src/formats/` |
| データアクセス層 | Git API クライアント | `packages/decap-cms-backend-*/src/API.ts` |
| 共通基盤層 | 型定義、ユーティリティ | `packages/decap-cms-lib-util/` |

### 5.3 データフロー

Decap CMS のデータフローは **単方向データフロー（Flux パターン）** です。

```
[ユーザー操作] → [Action Dispatch] → [Reducer] → [Store 更新] → [Component 再レンダリング]
```

具体的な保存フロー:

```
ユーザーが「Save」クリック
    ↓
EditorToolbar → dispatch(persistEntry(...))
    ↓
actions/entries.ts: persistEntry() [Thunk]
    ↓
backend.ts: Backend.persistEntry()
  - preSave イベントフック実行
  - エントリーを raw 文字列に変換 (resolveFormat)
  - コミットメッセージ生成
    ↓
backend-github/implementation.tsx: GitHub.persistEntry()
    ↓
backend-github/API.ts: API.persistFiles()
  - ファイルを Base64 エンコード
  - GitHub Git API でコミット作成
    ↓
dispatch(entryPersisted(...)) → Store 更新 → UI 反映
```

---

## 6. 主要コンポーネント

> このセクションでは、主要なコンポーネントとその連携を解説します。

### 6.1 エントリーポイント

アプリケーションの起点は `bootstrap()` 関数です。

```javascript
// ファイル: packages/decap-cms-core/src/bootstrap.js:41-101
function bootstrap(opts = {}) {
  const { config } = opts;

  // 1. config.yml を読み込み、Redux Store にディスパッチ
  store.dispatch(
    loadConfig(config, function onLoad() {
      // 2. 設定読み込み完了後に認証処理を開始
      store.dispatch(authenticateUser());
    }),
  );

  // 3. React ツリーをレンダリング
  function Root() {
    return (
      <>
        <GlobalStyles />
        <Provider store={store}>
          <ConnectedTranslatedApp />
        </Provider>
      </>
    );
  }

  const root = createRoot(getRoot());
  root.render(<Root />);
}
```

呼び出しチェーン:
1. `packages/decap-cms/src/index.js` → `CMS.init()` を呼び出し
2. `packages/decap-cms-core/src/index.js` → `init` は `bootstrap` にマッピング
3. `packages/decap-cms-core/src/bootstrap.js` → 実際の初期化処理

### 6.2 ビジネスロジック

ビジネスロジックの中心は `Backend` クラスです。各 Git バックエンド（GitHub, GitLab 等）の `Implementation` をラップし、共通の処理を提供します。

```typescript
// ファイル: packages/decap-cms-core/src/backend.ts:1-9
import attempt from 'lodash/attempt';
import flatten from 'lodash/flatten';
import isError from 'lodash/isError';
import uniq from 'lodash/uniq';
import trim from 'lodash/trim';
import sortBy from 'lodash/sortBy';
import get from 'lodash/get';
import set from 'lodash/set';
import { List, fromJS, Set } from 'immutable';
```

`Backend` クラスの主な責務:
- ローカルバックアップの管理
- i18n（国際化）処理
- コミットメッセージの生成
- ファイルフォーマットの変換（Markdown ↔ データオブジェクト）
- エントリーのスラッグ生成
- 各バックエンド `Implementation` への委譲

### 6.3 データアクセス

データアクセス層は各バックエンドパッケージの `implementation.ts(x)` と `API.ts` で構成されます。

```typescript
// ファイル: packages/decap-cms-lib-util/src/implementation.ts:32-35
export interface ImplementationEntry {
  data: string;
  file: { path: string; label?: string; id?: string | null; author?: string; updatedOn?: string };
}
```

全バックエンドが満たすべき主要メソッド:
- `authenticate(credentials)` — ユーザー認証
- `entriesByFolder(folder, extension, depth)` — フォルダ内のエントリー取得
- `getEntry(path)` — 単一エントリー取得
- `persistEntry(entry, opts)` — エントリー保存（Git コミット）
- `persistMedia(file, opts)` — メディアファイル保存
- `deleteFiles(paths, commitMessage)` — ファイル削除

### 6.4 ユーティリティ/共通機能

#### レジストリ (`packages/decap-cms-core/src/lib/registry.js`)

全プラグイン（バックエンド、ウィジェット、メディアライブラリ等）の登録・取得を管理します。

```javascript
// ファイル: packages/decap-cms-core/src/lib/registry.js:200-212
export function registerBackend(name, BackendClass) {
  if (!name || !BackendClass) {
    console.error(
      "Backend parameters invalid. example: CMS.registerBackend('myBackend', BackendClass)",
    );
  } else if (registry.backends[name]) {
    console.error(`Backend [${name}] already registered. Please choose a different name.`);
  } else {
    registry.backends[name] = {
      init: (...args) => new BackendClass(...args),
    };
  }
}
```

#### フォーマット変換 (`packages/decap-cms-core/src/formats/formats.ts`)

ファイル拡張子やコレクション設定に基づいて、適切なフォーマッターを解決します。

```typescript
// ファイル: packages/decap-cms-core/src/formats/formats.ts:32-40
export const extensionFormatters = {
  yml: yamlFormatter,
  yaml: yamlFormatter,
  toml: tomlFormatter,
  json: jsonFormatter,
  md: FrontmatterInfer,
  markdown: FrontmatterInfer,
  html: FrontmatterInfer,
};
```

#### ルーティング (`packages/decap-cms-core/src/routing/history.ts`)

ハッシュベースの SPA ルーティングを提供します。

```typescript
// ファイル: packages/decap-cms-core/src/routing/history.ts:1-17
import { createHashHistory } from 'history';

const history = createHashHistory();

export function navigateToCollection(collectionName: string) {
  return history.push(`/collections/${collectionName}`);
}

export function navigateToNewEntry(collectionName: string) {
  return history.replace(`/collections/${collectionName}/new`);
}

export function navigateToEntry(collectionName: string, slug: string) {
  return history.replace(`/collections/${collectionName}/entries/${slug}`);
}
```

---

## 7. よく使われるパターン

> このセクションでは、コード内で頻出するパターンを解説します。

### パターン一覧

| パターン | 説明 | 出現頻度 | 代表的なファイル |
|---------|------|---------|-----------------|
| Redux Thunk アクション | 非同期処理のアクションクリエイター | 高 | `packages/decap-cms-core/src/actions/entries.ts` |
| REQUEST/SUCCESS/FAILURE 三点セット | 非同期処理の状態管理定数 | 高 | `packages/decap-cms-core/src/actions/entries.ts` |
| Immer produce | immutable な状態更新 | 高 | `packages/decap-cms-core/src/reducers/auth.ts` |
| connect(mapStateToProps) | Redux Store と React コンポーネントの接続 | 高 | `packages/decap-cms-core/src/components/App/App.js` |
| Implementation インターフェース | バックエンドの契約 | 中 | `packages/decap-cms-lib-util/src/implementation.ts` |
| Widget パターン | ウィジェットの Control/Preview 分離 | 中 | `packages/decap-cms-widget-string/src/` |
| レジストリパターン | プラグインの登録と解決 | 中 | `packages/decap-cms-core/src/lib/registry.js` |
| イベントフック | ライフサイクルイベント処理 | 低 | `packages/decap-cms-core/src/lib/registry.js:243-261` |

### 各パターンの詳細

#### パターン1: Redux Thunk アクション

**目的:** API呼び出しなどの非同期処理を Redux のアクションとして記述する

**実装例:**
```typescript
// ファイル: packages/decap-cms-core/src/actions/entries.ts:93-100
export function entryLoading(collection: Collection, slug: string) {
  return {
    type: ENTRY_REQUEST,
    payload: {
      collection: collection.get('name'),
      slug,
    },
  };
}
```

```typescript
// Thunk アクション（非同期処理を含む）
// 戻り値が関数である点が特徴
export function loadEntry(collection, slug) {
  return async (dispatch, getState) => {
    dispatch(entryLoading(collection, slug));      // 1. 開始を通知
    try {
      const entry = await tryLoadEntry(...);       // 2. API呼び出し
      dispatch(entryLoaded(collection, entry));     // 3. 成功を通知
    } catch (error) {
      dispatch(entryLoadError(error, ...));         // 4. 失敗を通知
    }
  };
}
```

**解説:** `redux-thunk` ミドルウェアにより、アクションクリエイターが関数を返すことが可能になります。この関数は `dispatch` と `getState` を引数に受け取り、非同期処理の各段階で適切なアクションをディスパッチします。

#### パターン2: REQUEST/SUCCESS/FAILURE 三点セット

**目的:** 非同期処理の3状態（開始・成功・失敗）を一貫して管理する

**実装例:**
```typescript
// ファイル: packages/decap-cms-core/src/actions/entries.ts:46-48
export const ENTRY_REQUEST = 'ENTRY_REQUEST';
export const ENTRY_SUCCESS = 'ENTRY_SUCCESS';
export const ENTRY_FAILURE = 'ENTRY_FAILURE';
```

**解説:** 全ての非同期処理に `_REQUEST`, `_SUCCESS`, `_FAILURE` のサフィックスを付けた3つの定数を定義します。Reducer はこれらを `switch` で分岐し、ローディング状態・データ・エラーの管理を行います。

#### パターン3: Immer produce による Reducer

**目的:** immutable な状態更新を、ミュータブルな書き方で実現する

**実装例:**
```typescript
// ファイル: packages/decap-cms-core/src/reducers/auth.ts:26-44
const auth = produce((state: Auth, action: AuthAction) => {
  switch (action.type) {
    case AUTH_REQUEST:
      state.isFetching = true;   // 直接変更に見えるが、Immer が新しいオブジェクトを生成
      break;
    case AUTH_SUCCESS:
      state.user = action.payload;
      break;
  }
}, defaultState);
```

**解説:** `produce` 関数内では `state` を直接変更するように書けますが、実際には Immer がプロキシを使って新しい immutable なオブジェクトを生成します。これにより Redux の原則（状態の不変性）を守りつつ、簡潔なコードが書けます。

#### パターン4: connect(mapStateToProps) パターン

**目的:** Redux Store の状態を React コンポーネントの props に接続する

**実装例:**
```javascript
// ファイル: packages/decap-cms-core/src/components/App/App.js:261-286
function mapStateToProps(state) {
  const { auth, config, collections, globalUI, mediaLibrary } = state;
  const user = auth.user;
  const isFetching = globalUI.isFetching;
  const publishMode = config.publish_mode;
  return { auth, config, collections, user, isFetching, publishMode, ... };
}

const mapDispatchToProps = {
  openMediaLibrary,
  loginUser,
  logoutUser,
};

export default connect(mapStateToProps, mapDispatchToProps)(translate()(App));
```

**解説:** `connect` は Higher-Order Component（HOC）パターンで、Redux Store の状態変更を React コンポーネントに自動的に反映します。`mapStateToProps` で必要な状態を選択し、`mapDispatchToProps` でアクションディスパッチを props として注入します。

---

## 8. 業務フロー追跡の実践例

> このセクションでは、実際の業務フローをコードで追跡する方法を解説します。

### 8.1 フロー追跡の基本手順

1. エントリーポイントを特定
2. 処理の流れを追跡（呼び出し関係を追う）
3. データの変換を確認
4. 最終的な出力を確認

### 8.2 フロー追跡の実例

#### 例1: CMS 初期化フロー

**概要:** ブラウザでページを開いてからCMS画面が表示されるまでの流れ

**処理フロー:**
```
index.js → extensions.js（プラグイン登録）→ bootstrap() → loadConfig() → authenticateUser() → App レンダリング
```

**詳細な追跡:**

1. **ブラウザエントリーポイント** (`packages/decap-cms/src/index.js:1-7`)
   ```javascript
   import { DecapCmsApp as CMS } from 'decap-cms-app';
   import './extensions';

   if (!window.CMS_MANUAL_INIT) {
     CMS.init();
   }
   ```

2. **プラグイン登録** (`packages/decap-cms-app/src/extensions.js:35-60`)
   ```javascript
   CMS.registerBackend('github', GitHubBackend);
   CMS.registerBackend('gitlab', GitLabBackend);
   // ...
   CMS.registerWidget([
     DecapCmsWidgetString.Widget(),
     DecapCmsWidgetNumber.Widget(),
     // ...
   ]);
   ```

3. **初期化（bootstrap）** (`packages/decap-cms-core/src/bootstrap.js:41-101`)
   ```javascript
   function bootstrap(opts = {}) {
     store.dispatch(
       loadConfig(config, function onLoad() {
         store.dispatch(authenticateUser());
       }),
     );
     const root = createRoot(getRoot());
     root.render(<Root />);
   }
   ```

4. **設定読み込み完了後、認証チェック** → ユーザー未認証ならログイン画面表示

5. **認証成功後** → `App.js` がコレクション一覧をレンダリング

#### 例2: エントリー保存フロー

**概要:** ユーザーがエディタで「Save」を押してから、Git リポジトリにコミットされるまでの流れ

**処理フロー:**
```
Save ボタン → dispatch(persistEntry) → Backend.persistEntry() → GitHub.persistEntry() → GitHub API
```

**詳細な追跡:**

1. **アクションディスパッチ** (`packages/decap-cms-core/src/actions/entries.ts`)
   ```typescript
   // persistEntry Thunk アクション
   export function persistEntry(collection) {
     return async (dispatch, getState) => {
       // 1. 保存開始通知
       dispatch(entryPersisting(collection, entry));
       // 2. バックエンドに保存を委譲
       const backend = currentBackend(state.config);
       await backend.persistEntry({ config, collection, entryDraft, assetProxies });
       // 3. 保存成功通知
       dispatch(entryPersisted(collection, entry, slug));
     };
   }
   ```

2. **バックエンドアダプタ** (`packages/decap-cms-core/src/backend.ts`)
   ```typescript
   // Backend.persistEntry()
   // - preSave イベントフック実行
   // - エントリーを raw 文字列に変換（resolveFormat で YAML/TOML/JSON/Frontmatter）
   // - スラッグ生成
   // - コミットメッセージ生成
   // - implementation.persistEntry() に委譲
   ```

3. **フォーマット変換** (`packages/decap-cms-core/src/formats/formats.ts:66-97`)
   ```typescript
   export function resolveFormat(collection, entry) {
     const formatSpecification = collection.get('format');
     if (formatSpecification) {
       return formatByName(formatSpecification, customDelimiter);
     }
     // ファイル拡張子からフォーマットを推定
     const fileExtension = filePath.split('.').pop();
     return get(extensionFormatters, fileExtension);
   }
   ```

4. **GitHub API 呼び出し** (`packages/decap-cms-backend-github/src/API.ts`)
   ```
   POST /repos/{owner}/{repo}/git/blobs   → ファイルの blob 作成
   POST /repos/{owner}/{repo}/git/trees    → ツリー作成
   POST /repos/{owner}/{repo}/git/commits  → コミット作成
   PATCH /repos/{owner}/{repo}/git/refs/heads/{branch} → ブランチ ref 更新
   ```

### 8.3 フロー追跡チェックリスト

- [ ] エントリーポイントを特定したか
- [ ] 呼び出し関係を把握したか
- [ ] データの変換ポイントを確認したか
- [ ] エラーハンドリングを確認したか
- [ ] 最終的な出力を確認したか

---

## 9. 設計書の参照順序

> このセクションでは、プロジェクト理解のための設計書参照順序を案内します。

### 9.1 目的別ロードマップ

#### 全体像を把握したい場合
1. `README.md` — プロジェクト概要と基本的な使い方
2. `CONTRIBUTING.md` — 開発環境のセットアップと貢献ガイド
3. `packages/decap-cms-app/src/extensions.js` — 登録されている全プラグイン一覧
4. `packages/decap-cms-core/src/bootstrap.js` — 初期化フロー

#### 特定機能を理解したい場合
1. `packages/decap-cms-core/src/lib/registry.js` — プラグインの登録メカニズム
2. 対象機能のパッケージ（例: `packages/decap-cms-backend-github/`）
3. 対応する Redux のアクション/リデューサー（`src/actions/`, `src/reducers/`）

#### 改修作業を行う場合
1. 対象パッケージの `src/` ディレクトリを確認
2. `packages/decap-cms-core/src/actions/` — 関連するアクション
3. `packages/decap-cms-core/src/reducers/` — 関連するリデューサー
4. `cypress/` — E2E テストで影響範囲を確認

#### 新しいバックエンドを追加したい場合
1. `packages/decap-cms-lib-util/src/implementation.ts` — Implementation インターフェース
2. `packages/decap-cms-backend-github/` — 参考実装
3. `packages/decap-cms-app/src/extensions.js` — 登録方法

#### 新しいウィジェットを追加したい場合
1. `packages/decap-cms-widget-string/` — 最もシンプルな参考実装
2. `packages/decap-cms-core/src/lib/registry.js:94-148` — ウィジェット登録メカニズム
3. `packages/decap-cms-app/src/extensions.js:44-60` — ウィジェット登録例

### 9.2 ドキュメント一覧

| ドキュメント | 概要 | 参照タイミング |
|-------------|------|---------------|
| `README.md` | プロジェクト概要・インストール手順 | 初回参画時 |
| `CONTRIBUTING.md` | 開発環境構築・コーディング規約 | 開発開始時 |
| `CHANGELOG.md` | バージョン別変更履歴 | リリース確認時 |
| `CODE_OF_CONDUCT.md` | 行動規範 | 初回参画時 |
| `LICENSE` | MIT ライセンス | 利用時 |

---

## 10. トラブルシューティング

> このセクションでは、コードリーディング時によくある問題と解決法を解説します。

### よくある疑問と回答

#### Q: Immutable.js の `Map` と通常のオブジェクトが混在していて混乱する
A: 本プロジェクトは移行期にあり、古いコード（`entryDraft.js` 等）は Immutable.js を使用し、新しいコード（`auth.ts` 等）は Immer の `produce` を使用しています。`.get()`, `.getIn()` が呼ばれていれば Immutable.js、直接プロパティアクセスなら通常のオブジェクトです。

#### Q: パッケージ間のインポートが解決できない
A: モノレポ構成のため、`npm install` で npm ワークスペースのシンボリックリンクが作成されます。`tsconfig.json` の `paths` 設定もパッケージ解決に使われます。`package.json` の `workspaces` フィールドを参照してください。

#### Q: `config.yml` の設定項目とコードの対応が分からない
A: `packages/decap-cms-core/src/actions/config.ts` で設定の読み込みと検証が行われます。設定値は Redux Store の `config` ステートに格納されます。

#### Q: `registry` に登録されたプラグインがどこで使われるのか分からない
A: `registry.js` の `get*` 関数をグローバル検索してください。例えば `getBackend` は `backend.ts` から呼ばれ、`resolveWidget` は `EditorWidgets` コンポーネントから呼ばれます。

#### Q: Editorial Workflow とは何か
A: コンテンツのレビュー・承認フローを実現する機能です。エントリーを「Draft → In Review → Ready」のステータスで管理し、Pull Request/Merge Request を使って公開します。`config.yml` で `publish_mode: editorial_workflow` を設定すると有効になります。関連コード: `packages/decap-cms-core/src/reducers/editorialWorkflow.ts`

### デバッグのヒント

- **Redux DevTools**: ブラウザ拡張機能で Redux Store の状態変化を確認できます（`composeWithDevTools` が有効）
- **ハッシュルーティング**: URL の `#/collections/...` でナビゲーション状態を確認できます
- **テストバックエンド**: `config.yml` で `backend: { name: test-repo }` を設定すると、Git API なしでローカルテストが可能です
- **コンソールログ**: `bootstrap.js:47-49` でバージョン番号が出力されます。CMS が正しく初期化されたか確認する際の目安になります

---

## 付録

### A. 用語集

| 用語 | 説明 |
|-----|------|
| Collection | CMS で管理するコンテンツの種類。ブログ記事、ページなど |
| Entry | Collection 内の個々のコンテンツアイテム |
| Widget | エディタのフィールドUI。string, number, markdown など |
| Backend | Git プロバイダ（GitHub, GitLab 等）との連携実装 |
| Slug | エントリーの一意識別子。URL やファイル名に使われる |
| Editorial Workflow | コンテンツのレビュー・承認フロー機能 |
| Frontmatter | Markdown ファイル先頭のメタデータ（YAML/TOML/JSON） |
| Media Library | 画像等のメディアファイル管理UI |
| Registry | プラグイン（バックエンド、ウィジェット等）の登録・解決システム |
| Implementation | バックエンドが満たすべきインターフェース（契約） |
| AssetProxy | メディアファイルのプロキシオブジェクト |
| Thunk | redux-thunk を使った非同期アクションクリエイター |
| Monorepo | 複数パッケージを1つのリポジトリで管理する構成 |
| Lerna | モノレポの管理ツール（バージョン管理・パブリッシュ） |
| Nx | ビルドキャッシュとタスク実行の最適化ツール |

### B. ファイル一覧

| ファイル/ディレクトリ | 説明 | 主な内容 |
|---------------------|------|---------|
| `packages/decap-cms/src/index.js` | ブラウザエントリーポイント | `window.CMS` の公開、自動初期化 |
| `packages/decap-cms-app/src/index.js` | アプリ層エントリー | Core の再エクスポート |
| `packages/decap-cms-app/src/extensions.js` | プラグイン一括登録 | 全バックエンド・ウィジェット・ロケールの登録 |
| `packages/decap-cms-core/src/index.js` | コアエントリー | `DecapCmsCore` の公開（Registry + bootstrap） |
| `packages/decap-cms-core/src/bootstrap.js` | 初期化ロジック | Store 設定、設定読み込み、認証、レンダリング |
| `packages/decap-cms-core/src/backend.ts` | バックエンドアダプタ | `Backend` クラス（共通ロジック層） |
| `packages/decap-cms-core/src/lib/registry.js` | グローバルレジストリ | プラグイン登録・取得API |
| `packages/decap-cms-core/src/redux/index.ts` | Redux Store 設定 | Store 作成、ミドルウェア適用 |
| `packages/decap-cms-core/src/reducers/index.ts` | Reducer 集約 | 全 Reducer のエクスポートとセレクター |
| `packages/decap-cms-core/src/actions/entries.ts` | エントリーアクション | CRUD操作の全アクション定数・クリエイター |
| `packages/decap-cms-core/src/components/App/App.js` | メインアプリコンポーネント | ルーティング定義、認証フロー制御 |
| `packages/decap-cms-core/src/routing/history.ts` | ルーティング | ハッシュベースのナビゲーション関数 |
| `packages/decap-cms-core/src/formats/formats.ts` | フォーマット解決 | ファイル拡張子からフォーマッターを決定 |
| `packages/decap-cms-lib-util/src/implementation.ts` | Implementation インターフェース | バックエンドの型定義・契約 |
| `packages/decap-cms-backend-github/src/implementation.tsx` | GitHub バックエンド | GitHub API を使ったバックエンド実装 |
| `packages/decap-cms-backend-github/src/API.ts` | GitHub API クライアント | REST API でのファイル操作 |
| `packages/decap-cms-widget-string/src/StringControl.js` | String ウィジェット | 最もシンプルなウィジェット実装例 |
| `babel.config.js` | Babel 設定 | トランスパイル設定（React, TypeScript, Emotion） |
| `jest.config.js` | Jest 設定 | テスト環境設定（jsdom, モジュールマッピング） |
| `tsconfig.json` | TypeScript 設定 | コンパイル設定（パス解決含む） |
| `lerna.json` | Lerna 設定 | 独立バージョン管理、npm クライアント |

### C. 参考資料

- [Decap CMS 公式ドキュメント](https://decapcms.org/docs/)
- [React 公式ドキュメント](https://react.dev/)
- [Redux 公式ドキュメント](https://redux.js.org/)
- [Immutable.js ドキュメント](https://immutable-js.com/)
- [Immer ドキュメント](https://immerjs.github.io/immer/)
- [Emotion ドキュメント](https://emotion.sh/docs/introduction)
- [Lerna ドキュメント](https://lerna.js.org/)
- [Nx ドキュメント](https://nx.dev/)
- [GitHub REST API リファレンス](https://docs.github.com/en/rest)
