CloudFlareにデプロイするためのRemixプロジェクト 定数を別のファイルに外だししよう

## 概要

今まで手を入れてきたRemixのプロジェクトだが、ガッツリ定数系の値をマジックナンバーでハードコーディングしてしまっている。
バックエンドとは設計思想が違うからもしかすると今の状態でもいいかもしれないが、laravelのconfigディレクトリ直下よろしく定数を定義するファイルを作ってみようと思う。

## 前提

下記の内容が完了していること。

https://qiita.com/miriwo/items/12aabbc199c9f1d6b05b

## 方法

### 定数ファイルの追加

1. 下記を実行してappディレクトリ直下にconfigディレクトリを作成

    ```sh
    mkdir -p app/config
    ```

1. 下記を実行してconfigディレクトリ直下にconstants.tsファイルを作成

    ```sh
    touch app/config/constants.ts
    ```

### TypeScriptの設定を確認

- `tsconfig.json`を確認し、一部の記載が下記のようになっていることを確認

    ```tsconfig.json
    "paths": {
      "~/*": ["./app/*"]
    },
    ```

### `app/routes/tasks-loader.ts`のマジックナンバーを転記

- `app/config/constants.ts`に下記のように記載

    ```app/config/constants.ts
    import dotenv from "dotenv";
    dotenv.config();
    
    const constants = {
      // COMMON
      IS_TRUE: true,
    
      // タスク系
      DEFAULT_PAGE: 1, // ページネーションのデフォルトページ
      DEFAULT_PAGE_SIZE: 5, // ページネーション1ページあたりの表示数
    };
    
    export default constants;
    ```

### task-loader.tsの修正

- 下記のように記載して当該ファイルのマジックナンバーを排除

    ```app/routes/tasks-loader.ts
    import type { LoaderFunctionArgs } from "@remix-run/cloudflare";
    import constants from "~/config/constants";
    
    export const loader = async ({ context, request }: LoaderFunctionArgs) => {
    
      const url = new URL(request.url);
      const searchParams = url.searchParams;
    
      const isCompleteParam: unknown = searchParams.get("isComplete");
      const isComplete: boolean = isCompleteParam === "true" ? true : false;
    
      const isDeletedParam: unknown = searchParams.get("isDeleted");
      const isDeleted: boolean = isDeletedParam === "true" ? true : false;
    
      const taskCategoryMasterIdParams: string[] = searchParams.getAll("taskCategoryMasterId");
      const taskCategoryMasterIds: number[] = taskCategoryMasterIdParams.map(id => Number(id));
    
      const nowPage: number = Number(searchParams.get("page")) || constants.DEFAULT_PAGE; // 設定されていなかったらデフォルト値の1を設定
      const pageSize: number = constants.DEFAULT_PAGE_SIZE; // 1ページあたりの表示数
    
      interface WhereCondition {
        is_complete?: number;
        deleted_at?: { not: null };
        task_category_master_id?: { in: number[] };
      }
    
      // whereの条件を定義
      const whereCondition = {
        ...(isComplete && { is_complete: constants.IS_TRUE }),
        ...(isDeleted && { deleted_at: { not: null } }),
        ...(taskCategoryMasterIds.length > 0 && { task_category_master_id: { in: taskCategoryMasterIds } }),
      };
    
      try {
        const tasks = await context.db.tasks.findMany({
          where: whereCondition as WhereCondition,
          include: {
            task_category_master: true, // schema.prismaで定義したリレーションを指定
          },
          skip: (nowPage - 1) * pageSize, // スキップするレコード数
          take: pageSize, // 取得するレコード数
        });
        const tasksCount = await context.db.tasks.count({
          where: whereCondition as WhereCondition,
        });
        return {
          tasks,
          tasksCount,
          pageSize,
          nowPage,
        };
      } catch (error) {
        console.error("Failed to load tasks:", error);
        throw new Response("Internal Server Error", { status: 500 });
      }
    }
    ```

各種ページを移動し、エラーなどが出なければ完了