🤧

Bulletproof-react ディレクトリ構成編

2024/01/07に公開

概要

どうもukmashiです。最近、Reactに触れることが増え、Bulletproofを参考にプロジェクトを進めることが多いのですが、公式ドキュメント以外に、ユーザーベースのユースケースがあまり見つからなくて、困っていました。

自分があとで見返せるように、欲しい部分だけ勝手に翻訳して公開しようという考えで、本稿は書いています。ディレクトリ構成を真似るだけでも、結構救われるので、まずはディレクトリ構成を翻訳します。時間が出来たら、他のドキュメントに関しても翻訳していこうと思います。

また、最後に、独自解釈でこうしたほうがいいのでは?という提案も行っています。
暇だったら見て頂けると幸いです。

Bulletproof-react

bulletproofはReactのための一種の多層アーキテクチャです。DDDやAtomicDesignなどのように学習コストが多くなく、バックエンド寄りの直感で比較的わかりやすいアーキテクチャなので採用しています。

https://github.com/alan2207/bulletproof-react

ディレクトリ構成

application/src

ほどんどのコードはsrcフォルダにあり、下記のようになっています。

src
|
+-- assets            # assetsフォルダには、画像やフォントなどの静的ファイルをすべて入れることができます。
|
+-- components        # アプリケーション全体で使用される共有コンポーネント
|
+-- config            # すべての設定、環境変数などはここからエクスポートされ、アプリで使用する。
|
+-- features          # 機能ベースのモジュール
|
+-- hooks             # アプリケーション全体で使用される共有フック
|
+-- lib               # アプリケーション用に事前に設定された異なるライブラリを再エクスポートする
|
+-- providers         # すべてのアプリケーション・プロバイダ
|
+-- routes            # ルート設定
|
+-- stores            # global状態管理
|
+-- test              # テストユーティリティとmockサーバー
|
+-- types             # アプリケーション全体で使用される基本型
|
+-- utils             # 共通のユーティリティ

最も簡単で保守しやすい方法でアプリケーションを拡張するために、ほとんどのコードをfeaturesフォルダ内に保持しましょう。すべてのfeaturesフォルダには、与えられた機能のドメイン固有のコードを含める必要があります。こうすることで、機能性をその機能に特化し、その宣言を共有のものと混在させないことができる。これは、多くのファイルを含むモノリシックな構造よりもはるかにメンテナンスが簡単です。

application/src/feature

featureの内部は次のような構造を持ちます。

src/features/awesome-feature
|
+-- api         # 特定の機能に関連する、エクスポートされたAPIリクエストとapi hooks
|
+-- assets      # 特定の機能のすべての静的ファイルを格納
|
+-- components  # 特定の機能にスコープされたコンポーネント
|
+-- hooks       # 特定の機能にスコープされたフック
|
+-- routes      # 特定のrootコンポーネント
|
+-- stores      # 特定の機能のステートストア
|
+-- types       # 特定の領域のためのTypeScriptの型
|
+-- utils       # 特定の機能のためのユーティリティ関数
|
+-- index.ts    # 機能のエントリーポイント
		# 指定された機能のパブリックAPIとして機能し
		# その機能の外部で使用されるすべてのものをエクスポートします。

index.ts

index.tsは、その機能のパブリックAPIとして動作します。

他の機能からのインポートは、この機能を使ってのみ行いましょう

import {AwesomeComponent} from "@/features/awesome-feature"

下記はやめましょう

import {AwesomeComponent} from "@/features/awesome-feature/components/AwesomeComponent

index.tsを用いて、ESLintの設定で、以下のルールで後のインポートを許可しないように設定することもできます。

{
    rules: {
        'no-restricted-imports': [
            'error',
            {
                patterns: ['@/features/*/*'],
            },
        ],

    // ...rest of the configuration
}

これは分離されているが他のモジュールから使用可能なライブラリをどのように扱うか、NXからヒントを得たものです。機能を、自己完結型でありながら、エントリーポイントを介して他の機能にさまざまな部分を公開できるライブラリやモジュールとして考えてみよう。

独自ルール

ここまで読んでいただきありがとうございます。
bulletproofを採用すると、「特化」と「汎化」となっており、非常に保守性が高そうなのがわかりますね。

ここからは少々、独自解釈を紹介します。

ページそのものをレンダリングするコンポーネントの配置について、bulletproofに従ってしまうと分かりづらいのがネックでした。bulletproof的にはapplication/src/features/[awesome-feature]/routesに配置するのがルールとなっています。

ここで問題となるのが、ページと機能は必ずしも一致しない点です。

  • モーダルやカードコンポーネントでしか使用しない機能
  • urlは存在しても、機能として存在しない
  • 機能とページが同じレイヤーにいるの気持ち悪くない?

などなど、開発を勧めていると、「あれ?これいいの?」という疑問・問題が出てきます。

やはり保守運用を考えると、「urlとpageは一致していたほうがいいよね」というのが社内での共通認識だったので、弊社では、application/src/features/[awesome-feature]/routesは廃止。

代わりに、NextJSと同じようにapplication/pages/[awesome-page]というディレクトリ構成にしました。これにより、urlとページコンポーネントの配置が同じになり、直感的なディレクトリ構成となりました。

最後に独自解釈のディレクトリ構成を記述して、終わります。

application/src

src
|
+-- assets            # assetsフォルダには、画像やフォントなどの静的ファイルをすべて入れることができます。
|
+-- components        # アプリケーション全体で使用される共有コンポーネント
|
+-- config            # すべての設定、環境変数などはここからエクスポートされ、アプリで使用する。
|
+-- features          # 機能ベースのモジュール
|
+-- pages             # ページ別のコンポーネントを格納
|
+-- hooks             # アプリケーション全体で使用される共有フック
|
+-- lib               # アプリケーション用に事前に設定された異なるライブラリを再エクスポートする
|
+-- providers         # すべてのアプリケーション・プロバイダ
|
+-- routes            # ルート設定
|
+-- stores            # global状態管理
|
+-- test              # テストユーティリティとmockサーバー
|
+-- types             # アプリケーション全体で使用される基本型
|
+-- utils             # 共通のユーティリティ

application/src/feature

src/features/awesome-feature
|
+-- api         # 特定の機能に関連する、エクスポートされたAPIリクエストとapi hooks
|
+-- assets      # 特定の機能のすべての静的ファイルを格納
|
+-- components  # 特定の機能にスコープされたコンポーネント
|
+-- hooks       # 特定の機能にスコープされたフック
|
+-- stores      # 特定の機能のステートストア
|
+-- types       # 特定の領域のためのTypeScriptの型
|
+-- utils       # 特定の機能のためのユーティリティ関数
|
+-- index.ts    # 機能のエントリーポイント
		# 指定された機能のパブリックAPIとして機能し
		# その機能の外部で使用されるすべてのものをエクスポートします。

Discussion