パスエイリアスについて 多くの場合、プロジェクトは複雑なネストされたディレクトリ構造に発展します。その結果、インポート パスが長くなり、より混乱する可能性があり、コードの外観に悪影響を及ぼし、インポートされたコードがどこから来たのかを理解するのがより困難になる可能性があります。 パスエイリアスを使用すると、事前定義されたディレクトリに関連するインポートの定義を許可することで問題を解決できます。このアプローチは、インポート パスの理解に関する問題を解決するだけでなく、リファクタリング中のコード移動のプロセスを簡素化します。 // Without Aliases import { apiClient } from '../../../../shared/api'; import { ProductView } from '../../../../entities/product/components/ProductView'; import { addProductToCart } from '../../../add-to-cart/actions'; // With Aliases import { apiClient } from '#shared/api'; import { ProductView } from '#entities/product/components/ProductView'; import { addProductToCart } from '#features/add-to-cart/actions'; や など、Node.js でパス エイリアスを構成するために使用できる複数のライブラリがあります。しかし、Node.js のドキュメントを調べているうちに、サードパーティのライブラリに依存せずにパス エイリアスを構成する方法を発見しました。 alias-hq tsconfig-paths さらに、このアプローチにより、ビルド手順を必要とせずにエイリアスを使用できます。 この記事では、 と、それを使用してパス エイリアスを構成する方法について説明します。また、フロントエンド エコシステムでのサポートについても検討します。 Node.js サブパス インポート 輸入分野 Node.js v12.19.0 以降、開発者は 使用して、npm パッケージ内でパス エイリアスを宣言できます。これは、 ファイルの フィールドを使用して実行できます。 npm でパッケージを公開する必要はありません。 サブパス インポートを package.json imports 任意のディレクトリに ファイルを作成するだけで十分です。したがって、この方法はプライベートプロジェクトにも適しています。 package.json ここに興味深い事実があります: Node.js は、「 」と呼ばれる RFC を通じて、2020 年に フィールドのサポートを導入しました。この RFC は、npm パッケージのエントリ ポイントの宣言を可能にする フィールドで主に認識されていますが、 と フィールドは、似たような名前と構文を持っていても、まったく異なるタスクに対応しています。 node.js の Bare Module Specifier Resolution imports exports exports imports パス エイリアスのネイティブ サポートには、理論的には次の利点があります。 サードパーティのライブラリをインストールする必要はありません。 コードを実行するために、その場でインポートを事前にビルドしたり処理したりする必要はありません。 エイリアスは、標準のインポート解決メカニズムを使用するすべての Node.js ベースのツールでサポートされています。 コード ナビゲーションとオートコンプリートは、追加の設定を必要とせずにコード エディターで機能する必要があります。 私は自分のプロジェクトでパス エイリアスを設定しようとし、それらのステートメントを実際にテストしました。 パス エイリアスの設定 例として、次のディレクトリ構造を持つプロジェクトを考えてみましょう。 my-awesome-project ├── src/ │ ├── entities/ │ │ └── product/ │ │ └── components/ │ │ └── ProductView.js │ ├── features/ │ │ └── add-to-cart/ │ │ └── actions/ │ │ └── index.js │ └── shared/ │ └── api/ │ └── index.js └── package.json パス エイリアスを構成するには、ドキュメントで説明されているように、 に数行を追加します。たとえば、 ディレクトリに相対的なインポートを許可する場合は、次の フィールドを に追加します。 package.json src imports package.json { "name": "my-awesome-project", "imports": { "#*": "./src/*" } } 構成されたエイリアスを使用するには、インポートを次のように記述できます。 import { apiClient } from '#shared/api'; import { ProductView } from '#entities/product/components/ProductView'; import { addProductToCart } from '#features/add-to-cart/actions'; セットアップ フェーズから始めて、最初の制限に直面します。 フィールドのエントリは 記号で開始する必要があります。これにより、 などのパッケージ指定子と確実に区別されます。 imports # @ この制限は、パス エイリアスがインポートでいつ使用されるか、およびエイリアス構成がどこにあるかを開発者がすばやく判断できるため、便利だと思います。 一般的に使用されるモジュールのパス エイリアスを追加するには、 フィールドを次のように変更します。 imports { "name": "my-awesome-project", "imports": { "#modules/*": "./path/to/modules/*", "#logger": "./src/shared/lib/logger.js", "#*": "./src/*" } } 「他のすべてはすぐに使える」というフレーズで記事を締めくくるのが理想的です。しかし、実際には、 フィールドを使用する場合、いくつかの問題に直面する可能性があります。 imports Node.js の制限事項 でパス エイリアスを使用する予定がある場合は、悪いニュースがあります。次のコードは機能しません。 CommonJS モジュール const { apiClient } = require('#shared/api'); const { ProductView } = require('#entities/product/components/ProductView'); const { addProductToCart } = require('#features/add-to-cart/actions'); Node.js でパス エイリアスを使用する場合、ESM ワールドのモジュール解決ルールに従う必要があります。これは ES モジュールと CommonJS モジュールの両方に適用され、満たす必要がある 2 つの新しい要件が生じます。 ファイル拡張子を含むファイルへのフル パスを指定する必要があります。 ディレクトリへのパスを指定して ファイルをインポートすることは許可されていません。代わりに、 ファイルへのフル パスを指定する必要があります。 index.js index.js Node.js がモジュールを正しく解決できるようにするには、インポートを次のように修正する必要があります。 const { apiClient } = require('#shared/api/index.js'); const { ProductView } = require('#entities/product/components/ProductView.js'); const { addProductToCart } = require('#features/add-to-cart/actions/index.js'); これらの制限により、多数の CommonJS モジュールを含むプロジェクトで フィールドを構成するときに問題が発生する可能性があります。ただし、すでに ES モジュールを使用している場合、コードはすべての要件を満たしています。 imports さらに、バンドラーを使用してコードをビルドしている場合は、これらの制限を回避できます。これを行う方法については、以下で説明します。 TypeScript でのサブパス インポートのサポート 型チェックのためにインポートされたモジュールを適切に解決するには、TypeScript が フィールドをサポートする必要があります。この機能はバージョン 4.8.1 以降で が、上記の Node.js の制限が満たされている場合に限ります。 imports サポートされています モジュールの解決に フィールドを使用するには、 ファイルでいくつかのオプションを構成する必要があります。 imports tsconfig.json { "compilerOptions": { /* Specify what module code is generated. */ "module": "esnext", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "nodenext" } } この構成により、 フィールドは Node.js と同じように機能します。これは、モジュールのインポートにファイル拡張子を含めるのを忘れた場合、TypeScript はそれについて警告するエラーを生成することを意味します。 imports // OK import { apiClient } from '#shared/api/index.js'; // Error: Cannot find module '#src/shared/api/index' or its corresponding type declarations. import { apiClient } from '#shared/api/index'; // Error: Cannot find module '#src/shared/api' or its corresponding type declarations. import { apiClient } from '#shared/api'; // Error: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './relative.js'? import { foo } from './relative'; 私のプロジェクトのほとんどはバンドラーを使用してコードをビルドし、モジュールをインポートするときにファイル拡張子を追加することは決してないため、すべてのインポートを書き直したくありませんでした。この制限を回避するために、次のようにプロジェクトを構成する方法を見つけました。 { "name": "my-awesome-project", "imports": { "#*": [ "./src/*", "./src/*.ts", "./src/*.tsx", "./src/*.js", "./src/*.jsx", "./src/*/index.ts", "./src/*/index.tsx", "./src/*/index.js", "./src/*/index.jsx" ] } } この構成により、拡張子を指定する必要なく、モジュールをインポートする通常の方法が可能になります。これは、インポート パスがディレクトリを指している場合でも機能します。 // OK import { apiClient } from '#shared/api/index.js'; // OK import { apiClient } from '#shared/api/index'; // OK import { apiClient } from '#shared/api'; // Error: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './relative.js'? import { foo } from './relative'; 相対パスを使用したインポートに関する問題が 1 つあります。この問題は、パス エイリアスとは関係ありません。 モードを使用するようにモジュール解決を構成したため、TypeScript はエラーをスローします。 nodenext 幸いなことに、最近の で新しいモジュール解決モードが追加され、インポート内でフル パスを指定する必要がなくなりました。このモードを有効にするには、 ファイルでいくつかのオプションを構成する必要があります。 TypeScript 5.0 リリース tsconfig.json { "compilerOptions": { /* Specify what module code is generated. */ "module": "esnext", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "bundler" } } セットアップが完了すると、相対パスのインポートは通常どおり機能します。 // OK import { apiClient } from '#shared/api/index.js'; // OK import { apiClient } from '#shared/api/index'; // OK import { apiClient } from '#shared/api'; // OK import { foo } from './relative'; これで、インポート パスの記述方法に関する追加の制限なしに、 フィールドを介してパス エイリアスを完全に利用できるようになりました。 imports TypeScript を使用したコードのビルド コンパイラを使用してソース コードをビルドする場合、追加の構成が必要になる場合があります。 TypeScript の 1 つの制限は、 フィールドを使用する場合、コードを CommonJS モジュール形式にビルドできないことです。 tsc imports したがって、Node.js でコンパイルされたコードを実行するには、コードを ESM 形式でコンパイルし、 フィールドを に追加する必要があります。 type package.json { "name": "my-awesome-project", "type": "module", "imports": { "#*": "./src/*" } } コードが などの別のディレクトリにコンパイルされている場合、パス エイリアスが などの元の場所を指しているため、モジュールが Node.js によって検出されない可能性があります。この問題を解決するために、 ファイルで条件付きインポート パスを使用できます。 build/ src/ package.json これにより、ビルド済みのコードを ディレクトリではなく ディレクトリからインポートできます。 src/ build/ { "name": "my-awesome-project", "type": "module", "imports": { "#*": { "default": "./src/*", "production": "./build/*" } } } 特定のインポート条件を使用するには、 フラグを指定して Node.js を起動する必要があります。 --conditions node --conditions=production build/index.js コード バンドラでのサブパス インポートのサポート 通常、コード バンドラーは、Node.js に組み込まれているものではなく、独自のモジュール解決の実装を使用します。したがって、 フィールドのサポートを実装することが重要です。 imports プロジェクトで Webpack、Rollup、および Vite を使用してパス エイリアスをテストしましたが、その結果を共有する準備ができています。 以下は、バンドラーのテストに使用したパス エイリアスの構成です。インポート内のファイルへのフル パスを指定する必要がないように、TypeScript と同じトリックを使用しました。 { "name": "my-awesome-project", "type": "module", "imports": { "#*": [ "./src/*", "./src/*.ts", "./src/*.tsx", "./src/*.js", "./src/*.jsx", "./src/*/index.ts", "./src/*/index.tsx", "./src/*/index.js", "./src/*/index.jsx" ] } } ウェブパック Webpack は v5.0 から フィールド 。パス エイリアスは、追加の構成なしで機能します。 TypeScript でテスト プロジェクトをビルドするために使用した Webpack 構成は次のとおりです。 imports をサポートします const config = { mode: 'development', devtool: false, entry: './src/index.ts', module: { rules: [ { test: /\.tsx?$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-typescript'], }, }, }, ], }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], }, }; export default config; バイト フィールドのサポートは、Vite バージョン 4.2.0 で ました。ただし、バージョン 4.3.3 で重要なバグが修正されているため、少なくともこのバージョンを使用することをお勧めします。 Vite では、パス エイリアスは、 と モードの両方で追加の構成を必要とせずに機能します。 imports 追加され dev build したがって、完全に空の構成でテスト プロジェクトをビルドしました。 巻き上げる ロールアップは Vite 内で使用されますが、 フィールドはそのままでは機能しません。有効にするには、 プラグイン バージョン 11.1.0 以降をインストールする必要があります。構成例を次に示します。 imports @rollup/plugin-node-resolve import { nodeResolve } from '@rollup/plugin-node-resolve'; import { babel } from '@rollup/plugin-babel'; export default [ { input: 'src/index.ts', output: { name: 'mylib', file: 'build.js', format: 'es', }, plugins: [ nodeResolve({ extensions: ['.ts', '.tsx', '.js', '.jsx'], }), babel({ presets: ['@babel/preset-typescript'], extensions: ['.ts', '.tsx', '.js', '.jsx'], }), ], }, ]; 残念ながら、この構成では、パス エイリアスは Node.js の制限内でしか機能しません。つまり、拡張子を含む完全なファイル パスを指定する必要があります。ロールアップは配列の最初のパスのみを使用するため、 フィールド内に配列を指定しても、この制限は回避されません。 imports Rollup プラグインを使用してこの問題を解決できると信じていますが、主に小さなライブラリに Rollup を使用しているため、試したことはありません。私の場合、プロジェクト全体でインポート パスを書き換える方が簡単でした。 テスト ランナーでのサブパス インポートのサポート テスト ランナーは、モジュール解決メカニズムに大きく依存する開発ツールの別のグループです。多くの場合、コード バンドラーと同様に、独自のモジュール解決の実装を使用します。その結果、 フィールドが期待どおりに機能しない可能性があります。 imports 幸いなことに、私がテストしたツールはうまく機能します。 Jest v29.5.0 と Vite v0.30.1 でパス エイリアスをテストしました。どちらの場合も、パス エイリアスは追加の設定や制限なしでシームレスに機能しました。バージョン v29.4.0 以降、Jest は フィールドを 。 imports サポートしています Vitest のサポート レベルは、Vite のバージョンのみに依存しており、少なくとも v4.2.0 である必要があります。 コード エディターでのサブパス インポートのサポート 一般的なライブラリの フィールドは、現在十分にサポートされています。しかし、コード エディターはどうでしょうか。パス エイリアスを使用するプロジェクトで、コード ナビゲーション、特に「定義へ移動」機能をテストしました。コード エディターでのこの機能のサポートには、いくつかの問題があることが判明しました。 imports VSコード VS Code に関しては、TypeScript のバージョンが重要です。 TypeScript 言語サーバーは、JavaScript および TypeScript コードの分析とナビゲートを担当します。 設定に応じて、VS Code は TypeScript の組み込みバージョンまたはプロジェクトにインストールされているバージョンのいずれかを使用します。 TypeScript v5.0.4 と組み合わせて、VS Code v1.77.3 で フィールドのサポートをテストしました。 imports VS Code には、パス エイリアスに関する次の問題があります。 モジュール解決設定が または に設定されるまで、TypeScript は フィールドを使用しません。したがって、VS Code で使用するには、プロジェクトでモジュールの解像度を指定する必要があります。 nodenext bundler imports IntelliSense は現在、 フィールドを使用したインポート パスの提案をサポートしていません。この問題には があります。 imports 未解決の問題 両方の問題を回避するために、 ファイルでパス エイリアス構成を複製できます。 TypeScript を使用していない場合は、 で同じことができます。 tsconfig.json jsconfig.json // tsconfig.json OR jsconfig.json { "compilerOptions": { "baseUrl": "./", "paths": { "#*": ["./src/*"] } } } // package.json { "name": "my-awesome-project", "imports": { "#*": "./src/*" } } ウェブストーム バージョン 2021.3 (私は 2022.3.4 でテストしました) 以降、WebStorm は フィールド 。 WebStorm は独自のコード アナライザーを使用するため、この機能は TypeScript バージョンとは無関係に機能します。ただし、PhpStorm には、パス エイリアスのサポートに関して別の問題があります。 imports をサポートしています エディターは、パス エイリアスの使用に関して Node.js によって課される制限に厳密に従います。ファイル拡張子が明示的に指定されていない場合、コード ナビゲーションは機能しません。同じことが、 ファイルを使用してディレクトリをインポートする場合にも当てはまります。 index.js WebStorm には、 フィールド内でパスの配列を使用できないというバグがあります。この場合、コード ナビゲーションは完全に機能しなくなります。 imports { "name": "my-awesome-project", // OK "imports": { "#*": "./src/*" }, // This breaks code navigation "imports": { "#*": ["./src/*", "./src/*.ts", "./src/*.tsx"] } } 幸いなことに、VS Code のすべての問題を解決する同じトリックを使用できます。具体的には、 または ファイルでパス エイリアス構成を複製できます。これにより、制限なしでパス エイリアスを使用できます。 tsconfig.json jsconfig.json 推奨構成 さまざまなプロジェクトで フィールドを使用した実験と経験に基づいて、さまざまな種類のプロジェクトに最適なパス エイリアス構成を特定しました。 imports TypeScript または Bundler を使用しない場合 この構成は、追加のビルド手順を必要とせずにソース コードが Node.js で実行されるプロジェクトを対象としています。使用するには、次の手順に従います。 ファイルで フィールドを構成します。この場合、非常に基本的な構成で十分です。 package.json imports コード エディターでコード ナビゲーションを機能させるには、 ファイルでパス エイリアスを構成する必要があります。 jsconfig.json // jsconfig.json { "compilerOptions": { "baseUrl": "./", "paths": { "#*": ["./src/*"] } } } // package.json { "name": "my-awesome-project", "imports": { "#*": "./src/*" } } TypeScript を使用したコードの構築 この構成は、ソース コードが TypeScript で記述され、 コンパイラを使用してビルドされるプロジェクトに使用する必要があります。この構成では、以下を構成することが重要です。 tsc ファイルの フィールド。この場合、条件付きパス エイリアスを追加して、Node.js がコンパイル済みコードを正しく解決できるようにする必要があります。 package.json imports フィールドを使用する場合、TypeScript は ESM 形式のコードしかコンパイルできないため、 ファイルで ESM パッケージ形式を有効にする必要があります。 imports package.json ファイルで、ESM モジュールの形式と を設定します。これにより、TypeScript はインポートで忘れられたファイル拡張子を提案できるようになります。ファイル拡張子が指定されていない場合、コードはコンパイル後に Node.js で実行されません。 tsconfig.json moduleResolution コード エディターでコード ナビゲーションを修正するには、パス エイリアスを ファイルで繰り返す必要があります。 tsconfig.json // tsconfig.json { "compilerOptions": { "module": "esnext", "moduleResolution": "nodenext", "baseUrl": "./", "paths": { "#*": ["./src/*"] }, "outDir": "./build" } } // package.json { "name": "my-awesome-project", "type": "module", "imports": { "#*": { "default": "./src/*", "production": "./build/*" } } } バンドラーを使用したコードのビルド この構成は、ソース コードがバンドルされているプロジェクトを対象としています。この場合、TypeScript は必要ありません。存在しない場合は、すべての設定を ファイルで設定できます。 jsconfig.json この構成の主な機能は、インポートでのファイル拡張子の指定に関する Node.js の制限を回避できることです。 以下を構成することが重要です。 ファイルで フィールドを構成します。この場合、各エイリアスにパスの配列を追加する必要があります。これにより、バンドラーはファイル拡張子を指定しなくても、インポートされたモジュールを見つけることができます。 package.json imports コード エディターでコード ナビゲーションを修正するには、 または ファイルでパス エイリアスを繰り返す必要があります。 tsconfig.json jsconfig.json // tsconfig.json { "compilerOptions": { "baseUrl": "./", "paths": { "#*": ["./src/*"] } } } // package.json { "name": "my-awesome-project", "imports": { "#*": [ "./src/*", "./src/*.ts", "./src/*.tsx", "./src/*.js", "./src/*.jsx", "./src/*/index.ts", "./src/*/index.tsx", "./src/*/index.js", "./src/*/index.jsx" ] } } 結論 フィールドを介してパス エイリアスを構成することには、サードパーティのライブラリを介して構成する場合と比較して、長所と短所の両方があります。このアプローチは一般的な開発ツールでサポートされていますが (2023 年 4 月現在)、制限もあります。 imports この方法には、次の利点があります。 コードを「その場で」コンパイルまたはトランスパイルする必要なく、パス エイリアスを使用する機能。 一般的な開発ツールのほとんどは、追加構成なしでパス エイリアスをサポートしています。これは、Webpack、Vite、Jest、および Vitest で確認されています。 このアプローチにより、1 つの予測可能な場所 ( ファイル) でパス エイリアスを構成することが促進されます。 package.json パス エイリアスを構成するために、サードパーティ ライブラリをインストールする必要はありません。 ただし、開発ツールが進化するにつれて解消される一時的な欠点があります。 一般的なコード エディターでさえ、 フィールドのサポートに問題があります。これらの問題を回避するには、 ファイルを使用できます。ただし、これにより、2 つのファイルでパス エイリアス構成が重複することになります。 imports jsconfig.json 一部の開発ツールでは、そのままでは フィールドが機能しない場合があります。たとえば、ロールアップには追加のプラグインのインストールが必要です。 imports Node.js で フィールドを使用すると、インポート パスに新しい制約が追加されます。これらの制約は ES モジュールの制約と同じですが、 フィールドの使用を開始するのがより難しくなる可能性があります。 imports imports Node.js の制約により、Node.js と他の開発ツールとの間で実装に違いが生じる可能性があります。たとえば、コード バンドラーは Node.js の制約を無視できます。これらの違いにより、特に TypeScript をセットアップするときに、構成が複雑になることがあります。 では、 フィールドを使用してパス エイリアスを設定する価値はありますか?新しいプロジェクトでは、サードパーティ ライブラリの代わりにこの方法を使用する価値があると思います。 imports フィールドは、従来の構成方法と比較して大きな利点を提供するため、今後数年間で多くの開発者にとってパス エイリアスを構成する標準的な方法になる可能性が十分にあります。 imports ただし、パス エイリアスが設定されたプロジェクトが既にある場合は、 フィールドに切り替えても大きなメリットはありません。 imports この記事から何か新しいことを学んでいただければ幸いです。読んでくれてありがとう! 便利なリンク エクスポートとインポートを実装するための RFC imports フィールドの機能をよりよく理解するための一連のテスト Node.js の imports フィールドに関するドキュメント ES モジュールのインポート パスに関する Node.js の制限 も掲載 こちらに