【ITニュース解説】Enforce Module Imports in FSD (using eslint-plugin-import)
2025年09月22日に「Dev.to」が公開したITニュース「Enforce Module Imports in FSD (using eslint-plugin-import)」について初心者にもわかりやすく解説しています。
ITニュース概要
開発プロジェクトでコードのインポート方法がバラバラだと管理が大変で、エラーの原因にもなる。ESLintというツールを使い、FSD設計原則に基づいたインポートルールを強制することで、コードの一貫性を保ち、効率的なリファクタリングと品質維持を実現する。
ITニュース解説
ソフトウェア開発プロジェクトでは、複数のエンジニアが協力して一つのコードベースを構築する。この際、コードの一貫性と保守性は非常に重要だ。しかし、開発チームが大きくなり、プロジェクトが複雑化するにつれて、コードの書き方や構造に違いが生まれることは避けられない。特に「インポート文」のスタイルがバラバラになることは、多くの問題を引き起こす。
インポート文とは、あるファイルで別のファイルやモジュールで定義された機能を使いたいときに、「このファイルからあれを持ってくる」と宣言する部分のことだ。例えば、import Component from "./path/to/Component"のように書かれる。これがプロジェクト内で「絶対パスで書く人」「相対パスで書く人」「エイリアスを使う人」「index.jsファイルを経由する人」など、様々な書き方が混在してしまうと、コードを読むのが難しくなるだけでなく、システム全体に悪影響を及ぼすことがある。
このようなインポートスタイルの不統一は、リファクタリング(コードの構造を改善すること)を困難にする。あるファイルの場所を変更したとき、関連するすべてのインポートパスを自動的に修正する機能が正しく動作しないケースが頻繁に発生するためだ。さらに深刻な問題として、「循環インポート」がある。これは、モジュールAがモジュールBをインポートし、モジュールBがモジュールCをインポートし、さらにモジュールCがモジュールAをインポートするといった形で、お互いが依存し合う状態を指す。特に、複数のコンポーネントをまとめてエクスポートするindex.jsのようなファイルを経由して発生しやすい。例えば、src/shared/ui/index.jsがComment、Like、ViewsCounterをエクスポートしているとして、もしsrc/shared/ui/Commentがsrc/shared/ui/index.jsをインポートしてしまうと、まさに循環インポートが発生する。このような循環インポートは、開発時には気づきにくく、プログラムの実行時に予期せぬエラーや動作を引き起こすことがあり、その原因を特定して修正することは非常に難しい。
これらの問題を解決し、コードベースの一貫性と管理しやすさを保つための強力な設計原則の一つが「Feature Sliced Design (FSD)」だ。FSDは、アプリケーションの機能を細かく「スライス(切り分け)」し、それぞれのスライスが独立性を保ちつつ、明確なルールに基づいて相互作用するように設計する。FSDの中心的なアイデアは、厳格なインポート/エクスポートの原則を強制することにある。具体的には、以下の二つのルールが主要となる。
一つ目は、「モジュールはパブリックAPI(indexファイル)経由で、絶対パスを使ってのみインポートできる」というものだ。これは、各モジュールが外部に公開するインターフェース(パブリックAPI)をindex.jsファイルに集約し、他のモジュールからはそのindex.jsファイルを通してのみアクセスを許可するという考え方だ。これにより、モジュールの内部実装の詳細が外部に漏れるのを防ぎ、依存関係を明確にする。
二つ目は、「モジュール内部では、すべてのインポートは相対パスを使用する」というものだ。これは、同じモジュール内でファイル同士が依存し合う場合には、./や../といった相対パスを使うことで、循環インポートのリスクを低減し、モジュール内の結合度を低く保つことを目的としている。
FSDのような素晴らしい設計原則があっても、人間が手作業でこれらのルールを常に守り続けるのは難しい。そこで役立つのが「ESLint」というツールだ。ESLintは、JavaScriptコードの静的解析ツールであり、コードの品質やスタイルに関する問題を自動的に検出・報告・修正する。FSDのルールをESLintで強制することで、開発者は意識せずにこれらのルールに従ったコードを書くことができるようになる。
FSDの公式では、feature-sliced/eslint-configというESLintの設定を提供しており、これにはFSDの原則を適用するためのルールが含まれている。主要なルールは以下の三つだ。
public-api:これはeslint-plugin-importというESLintプラグインを利用し、インポート文がパブリックAPI(indexファイル)経由の絶対パスであるかどうかをチェックする。開発者が誤ってモジュールの内部ファイルに直接アクセスしようとした場合に、ESLintがエラーを報告してくれる。layers-slices:これはeslint-plugin-boundariesというプラグインを利用し、FSDにおけるモジュール間の依存関係が許可されているかどうかを分析する。FSDでは、層(layers)やスライス(slices)といった概念があり、それぞれがどのように依存し合って良いか、または悪いかが定義されているため、このルールでその関係性を強制できる。import-order:これもeslint-plugin-importを利用し、インポート文の記述順序をアルファベット順に並べたり、種類ごとにグループ化したりすることで、コードの可読性を向上させる。
実際にFSDを導入したプロジェクトでは、これらのESLintルールをカスタマイズして運用することがよくある。例えば、Next.jsを使ったプロジェクトでFSDを適用する場合を考えてみよう。プロジェクトのsrc/ディレクトリ以下には、app/、_pages/、_features/といったFSDの「層」があり、それぞれの層の下にはui/(ユーザーインターフェース)、types/(型定義)、lib/(ユーティリティ関数)といった「セグメント」が存在する。
ESLintの設定では、これらのFSDの層やセグメントのパターンを定義し、どのインポートを許可し、どのインポートを禁止するかを厳密に制御できる。例えば、import/no-internal-modulesというルールを使って、「src/_features/signin/lib/index.tsより深い階層からのインポートは禁止する」といった具体的なルールを設定できる。これにより、lib/index.tsがその_features/signin層の公開されたインターフェースとなり、他のモジュールからはlib/index.ts経由でのみアクセスが許可されるようになる。
チームによっては、FSDの公式ガイドラインである「パブリックAPI(indexファイル)からのみインポート」というルールを少し緩和し、「特定のセグメント(例えばuiやlibなど)からのインポートも許可する」といった調整を行うこともある。その場合も、ESLintの設定を修正することで、チームの独自のニーズに合わせた柔軟なルールを適用できる。重要なのは、どのようなルールを採用するにせよ、それがESLintによって一貫して強制されることだ。
このようなESLintとFSDを組み合わせたシステムは、コードの一貫性を高め、循環インポートのような複雑なバグの発生を防ぎ、長期的なプロジェクトの保守性を大きく向上させる。しかし、まだ完全に解決されていない課題も存在する。例えば、FSDが公式に推奨する「クロスインポート」(異なるスライスや層間での制御されたインポート)をESLintでどのように効果的に強制するか、コードを分割してパフォーマンスを最適化する「動的インポート」をパブリックAPIのルールと両立させるにはどうすればよいか、そしてテストコードでは通常のアプリケーションコードと同じインポートルールを適用すべきか、あるいはプライベートなモジュールへのアクセスを許可すべきか、といった点が今後の探求テーマとなる。これらの課題は、FSDとESLintの導入が進む中で、より実践的な解決策が求められている部分だ。