paint-brush
ネイティブな方法でフロントエンド プロジェクトのパス エイリアスを構成する方法@nodge
10,905 測定値
10,905 測定値

ネイティブな方法でフロントエンド プロジェクトのパス エイリアスを構成する方法

Maksim Zemskov20m2023/05/04
Read on Terminal Reader

長すぎる; 読むには

imports フィールドは、今後数年間で多くの開発者にとってパス エイリアスを構成する標準的な方法になる可能性が高いです。従来の構成方法と比較して大きな利点があり、一般的な開発ツールで既にサポートされています (2023 年 4 月現在)。ただし、推奨される構成方法に従うことで緩和できるいくつかの制限もあります。
featured image - ネイティブな方法でフロントエンド プロジェクトのパス エイリアスを構成する方法
Maksim Zemskov HackerNoon profile picture
0-item
1-item

パスエイリアスについて

多くの場合、プロジェクトは複雑なネストされたディレクトリ構造に発展します。その結果、インポート パスが長くなり、より混乱する可能性があり、コードの外観に悪影響を及ぼし、インポートされたコードがどこから来たのかを理解するのがより困難になる可能性があります。


パスエイリアスを使用すると、事前定義されたディレクトリに関連するインポートの定義を許可することで問題を解決できます。このアプローチは、インポート パスの理解に関する問題を解決するだけでなく、リファクタリング中のコード移動のプロセスを簡素化します。


 // 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';


alias-hqtsconfig-pathsなど、Node.js でパス エイリアスを構成するために使用できる複数のライブラリがあります。しかし、Node.js のドキュメントを調べているうちに、サードパーティのライブラリに依存せずにパス エイリアスを構成する方法を発見しました。


さらに、このアプローチにより、ビルド手順を必要とせずにエイリアスを使用できます。


この記事では、 Node.js サブパス インポートと、それを使用してパス エイリアスを構成する方法について説明します。また、フロントエンド エコシステムでのサポートについても検討します。

輸入分野

Node.js v12.19.0 以降、開発者はサブパス インポートを使用して、npm パッケージ内でパス エイリアスを宣言できます。これは、 package.jsonファイルのimportsフィールドを使用して実行できます。 npm でパッケージを公開する必要はありません。


任意のディレクトリにpackage.jsonファイルを作成するだけで十分です。したがって、この方法はプライベートプロジェクトにも適しています。


ここに興味深い事実があります: Node.js は、「 node.js の Bare Module Specifier Resolution 」と呼ばれる RFC を通じて、2020 年にimportsフィールドのサポートを導入しました。この RFC は、npm パッケージのエントリ ポイントの宣言を可能にするexportsフィールドで主に認識されていますが、 exportsimportsフィールドは、似たような名前と構文を持っていても、まったく異なるタスクに対応しています。


パス エイリアスのネイティブ サポートには、理論的には次の利点があります。


  • サードパーティのライブラリをインストールする必要はありません。


  • コードを実行するために、その場でインポートを事前にビルドしたり処理したりする必要はありません。


  • エイリアスは、標準のインポート解決メカニズムを使用するすべての 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 つの新しい要件が生じます。


  1. ファイル拡張子を含むファイルへのフル パスを指定する必要があります。


  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 モジュールを含むプロジェクトでimportsフィールドを構成するときに問題が発生する可能性があります。ただし、すでに ES モジュールを使用している場合、コードはすべての要件を満たしています。


さらに、バンドラーを使用してコードをビルドしている場合は、これらの制限を回避できます。これを行う方法については、以下で説明します。

TypeScript でのサブパス インポートのサポート

型チェックのためにインポートされたモジュールを適切に解決するには、TypeScript がimportsフィールドをサポートする必要があります。この機能はバージョン 4.8.1 以降でサポートされていますが、上記の Node.js の制限が満たされている場合に限ります。


モジュールの解決に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" } }


この構成により、 importsフィールドは Node.js と同じように機能します。これは、モジュールのインポートにファイル拡張子を含めるのを忘れた場合、TypeScript はそれについて警告するエラーを生成することを意味します。


 // 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 つあります。この問題は、パス エイリアスとは関係ありません。 nodenextモードを使用するようにモジュール解決を構成したため、TypeScript はエラーをスローします。


幸いなことに、最近の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 を使用したコードのビルド

tscコンパイラを使用してソース コードをビルドする場合、追加の構成が必要になる場合があります。 TypeScript の 1 つの制限は、 importsフィールドを使用する場合、コードを CommonJS モジュール形式にビルドできないことです。


したがって、Node.js でコンパイルされたコードを実行するには、コードを ESM 形式でコンパイルし、 typeフィールドをpackage.jsonに追加する必要があります。


 { "name": "my-awesome-project", "type": "module", "imports": { "#*": "./src/*" } }


コードがbuild/などの別のディレクトリにコンパイルされている場合、パス エイリアスがsrc/などの元の場所を指しているため、モジュールが Node.js によって検出されない可能性があります。この問題を解決するために、 package.jsonファイルで条件付きインポート パスを使用できます。


これにより、ビルド済みのコードをsrc/ディレクトリではなくbuild/ディレクトリからインポートできます。


 { "name": "my-awesome-project", "type": "module", "imports": { "#*": { "default": "./src/*", "production": "./build/*" } } }


特定のインポート条件を使用するには、 --conditionsフラグを指定して Node.js を起動する必要があります。


 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 からimportsフィールドをサポートします。パス エイリアスは、追加の構成なしで機能します。 TypeScript でテスト プロジェクトをビルドするために使用した Webpack 構成は次のとおりです。


 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;


バイト

importsフィールドのサポートは、Vite バージョン 4.2.0 で追加されました。ただし、バージョン 4.3.3 で重要なバグが修正されているため、少なくともこのバージョンを使用することをお勧めします。 Vite では、パス エイリアスは、 devbuildモードの両方で追加の構成を必要とせずに機能します。


したがって、完全に空の構成でテスト プロジェクトをビルドしました。

巻き上げる

ロールアップは Vite 内で使用されますが、 importsフィールドはそのままでは機能しません。有効にするには、 @rollup/plugin-node-resolveプラグイン バージョン 11.1.0 以降をインストールする必要があります。構成例を次に示します。


 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 には、パス エイリアスに関する次の問題があります。


  1. モジュール解決設定がnodenextまたはbundlerに設定されるまで、TypeScript はimportsフィールドを使用しません。したがって、VS Code で使用するには、プロジェクトでモジュールの解像度を指定する必要があります。


  2. IntelliSense は現在、 importsフィールドを使用したインポート パスの提案をサポートしていません。この問題には未解決の問題があります。


両方の問題を回避するために、 tsconfig.jsonファイルでパス エイリアス構成を複製できます。 TypeScript を使用していない場合は、 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 はimportsフィールドをサポートしています。 WebStorm は独自のコード アナライザーを使用するため、この機能は TypeScript バージョンとは無関係に機能します。ただし、PhpStorm には、パス エイリアスのサポートに関して別の問題があります。


  1. エディターは、パス エイリアスの使用に関して Node.js によって課される制限に厳密に従います。ファイル拡張子が明示的に指定されていない場合、コード ナビゲーションは機能しません。同じことが、 index.jsファイルを使用してディレクトリをインポートする場合にも当てはまります。


  2. 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 で実行されるプロジェクトを対象としています。使用するには、次の手順に従います。


  1. package.jsonファイルでimportsフィールドを構成します。この場合、非常に基本的な構成で十分です。


  2. コード エディターでコード ナビゲーションを機能させるには、 jsconfig.jsonファイルでパス エイリアスを構成する必要があります。


 // jsconfig.json { "compilerOptions": { "baseUrl": "./", "paths": { "#*": ["./src/*"] } } } // package.json { "name": "my-awesome-project", "imports": { "#*": "./src/*" } }


TypeScript を使用したコードの構築

この構成は、ソース コードが TypeScript で記述され、 tscコンパイラを使用してビルドされるプロジェクトに使用する必要があります。この構成では、以下を構成することが重要です。


  1. package.jsonファイルのimportsフィールド。この場合、条件付きパス エイリアスを追加して、Node.js がコンパイル済みコードを正しく解決できるようにする必要があります。


  2. importsフィールドを使用する場合、TypeScript は ESM 形式のコードしかコンパイルできないため、 package.jsonファイルで ESM パッケージ形式を有効にする必要があります。


  3. tsconfig.jsonファイルで、ESM モジュールの形式とmoduleResolutionを設定します。これにより、TypeScript はインポートで忘れられたファイル拡張子を提案できるようになります。ファイル拡張子が指定されていない場合、コードはコンパイル後に Node.js で実行されません。


  4. コード エディターでコード ナビゲーションを修正するには、パス エイリアスを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 の制限を回避できることです。


以下を構成することが重要です。


  1. package.jsonファイルでimportsフィールドを構成します。この場合、各エイリアスにパスの配列を追加する必要があります。これにより、バンドラーはファイル拡張子を指定しなくても、インポートされたモジュールを見つけることができます。


  2. コード エディターでコード ナビゲーションを修正するには、 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" ] } }


結論

importsフィールドを介してパス エイリアスを構成することには、サードパーティのライブラリを介して構成する場合と比較して、長所と短所の両方があります。このアプローチは一般的な開発ツールでサポートされていますが (2023 年 4 月現在)、制限もあります。


この方法には、次の利点があります。

  • コードを「その場で」コンパイルまたはトランスパイルする必要なく、パス エイリアスを使用する機能。


  • 一般的な開発ツールのほとんどは、追加構成なしでパス エイリアスをサポートしています。これは、Webpack、Vite、Jest、および Vitest で確認されています。


  • このアプローチにより、1 つの予測可能な場所 ( package.jsonファイル) でパス エイリアスを構成することが促進されます。


  • パス エイリアスを構成するために、サードパーティ ライブラリをインストールする必要はありません。


ただし、開発ツールが進化するにつれて解消される一時的な欠点があります。


  • 一般的なコード エディターでさえ、 importsフィールドのサポートに問題があります。これらの問題を回避するには、 jsconfig.jsonファイルを使用できます。ただし、これにより、2 つのファイルでパス エイリアス構成が重複することになります。


  • 一部の開発ツールでは、そのままではimportsフィールドが機能しない場合があります。たとえば、ロールアップには追加のプラグインのインストールが必要です。


  • Node.js でimportsフィールドを使用すると、インポート パスに新しい制約が追加されます。これらの制約は ES モジュールの制約と同じですが、 importsフィールドの使用を開始するのがより難しくなる可能性があります。


  • Node.js の制約により、Node.js と他の開発ツールとの間で実装に違いが生じる可能性があります。たとえば、コード バンドラーは Node.js の制約を無視できます。これらの違いにより、特に TypeScript をセットアップするときに、構成が複雑になることがあります。


では、 importsフィールドを使用してパス エイリアスを設定する価値はありますか?新しいプロジェクトでは、サードパーティ ライブラリの代わりにこの方法を使用する価値があると思います。


importsフィールドは、従来の構成方法と比較して大きな利点を提供するため、今後数年間で多くの開発者にとってパス エイリアスを構成する標準的な方法になる可能性が十分にあります。


ただし、パス エイリアスが設定されたプロジェクトが既にある場合は、 importsフィールドに切り替えても大きなメリットはありません。


この記事から何か新しいことを学んでいただければ幸いです。読んでくれてありがとう!

便利なリンク

  1. エクスポートとインポートを実装するための RFC
  2. imports フィールドの機能をよりよく理解するための一連のテスト
  3. Node.js の imports フィールドに関するドキュメント
  4. ES モジュールのインポート パスに関する Node.js の制限

こちらにも掲載