Projekte entwickeln sich häufig zu komplexen, verschachtelten Verzeichnisstrukturen. Dadurch können Importpfade länger und unübersichtlicher werden, was sich negativ auf das Erscheinungsbild des Codes auswirken und es schwieriger machen kann, zu verstehen, woher der importierte Code stammt.
Die Verwendung von Pfadaliasnamen kann das Problem lösen, indem sie die Definition von Importen ermöglichen, die relativ zu vordefinierten Verzeichnissen sind. Dieser Ansatz löst nicht nur Probleme beim Verständnis von Importpfaden, sondern vereinfacht auch den Prozess der Codeverschiebung während des Refactorings.
// 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';
Für die Konfiguration von Pfadaliasen in Node.js stehen mehrere Bibliotheken zur Verfügung, z. B. alias-hq und tsconfig-paths . Beim Durchsehen der Node.js-Dokumentation habe ich jedoch eine Möglichkeit entdeckt, Pfadaliase zu konfigurieren, ohne auf Bibliotheken von Drittanbietern angewiesen zu sein.
Darüber hinaus ermöglicht dieser Ansatz die Verwendung von Aliasen, ohne dass der Build-Schritt erforderlich ist.
In diesem Artikel besprechen wir Subpath-Importe von Node.js und wie man damit Pfad-Aliase konfiguriert. Wir werden auch ihre Unterstützung im Frontend-Ökosystem untersuchen.
Ab Node.js v12.19.0 können Entwickler Subpath Imports verwenden, um Pfadaliase innerhalb eines npm-Pakets zu deklarieren. Dies kann über das imports
in der Datei package.json
erfolgen. Es ist nicht erforderlich, das Paket auf npm zu veröffentlichen.
Es reicht aus, eine package.json
Datei in einem beliebigen Verzeichnis zu erstellen. Daher eignet sich diese Methode auch für private Projekte.
Hier ist eine interessante Tatsache: Node.js hat bereits 2020 durch den RFC mit dem Namen „ Bare Module Specifier Resolution in node.js “ die Unterstützung für das imports
eingeführt. Während dieser RFC hauptsächlich für das Feld „ exports
bekannt ist, das die Deklaration von Einstiegspunkten für npm-Pakete ermöglicht, adressieren die Felder exports
“ und imports
“ völlig unterschiedliche Aufgaben, obwohl sie ähnliche Namen und Syntax haben.
Die native Unterstützung für Pfadaliase bietet theoretisch die folgenden Vorteile:
Ich habe versucht, Pfadaliase in meinen Projekten zu konfigurieren und diese Anweisungen in der Praxis getestet.
Betrachten wir als Beispiel ein Projekt mit der folgenden Verzeichnisstruktur:
my-awesome-project ├── src/ │ ├── entities/ │ │ └── product/ │ │ └── components/ │ │ └── ProductView.js │ ├── features/ │ │ └── add-to-cart/ │ │ └── actions/ │ │ └── index.js │ └── shared/ │ └── api/ │ └── index.js └── package.json
Um Pfadaliase zu konfigurieren, können Sie wie in der Dokumentation beschrieben einige Zeilen zu package.json
hinzufügen. Wenn Sie beispielsweise Importe relativ zum src
Verzeichnis zulassen möchten, fügen Sie das folgende imports
zu package.json
hinzu:
{ "name": "my-awesome-project", "imports": { "#*": "./src/*" } }
Um den konfigurierten Alias zu verwenden, können Importe wie folgt geschrieben werden:
import { apiClient } from '#shared/api'; import { ProductView } from '#entities/product/components/ProductView'; import { addProductToCart } from '#features/add-to-cart/actions';
Ab der Einrichtungsphase stehen wir vor der ersten Einschränkung: Einträge im imports
müssen mit dem #
-Symbol beginnen. Dadurch wird sichergestellt, dass sie von Paketspezifizierern wie @
unterschieden werden.
Ich halte diese Einschränkung für nützlich, da sie es Entwicklern ermöglicht, schnell festzustellen, wann ein Pfadalias bei einem Import verwendet wird und wo Aliaskonfigurationen zu finden sind.
Um weitere Pfadaliase für häufig verwendete Module hinzuzufügen, kann das imports
wie folgt geändert werden:
{ "name": "my-awesome-project", "imports": { "#modules/*": "./path/to/modules/*", "#logger": "./src/shared/lib/logger.js", "#*": "./src/*" } }
Ideal wäre es, den Artikel mit dem Satz „Alles andere klappt sofort“ abzuschließen. Wenn Sie jedoch planen, das imports
zu verwenden, können in der Realität einige Schwierigkeiten auftreten.
Wenn Sie vorhaben, Pfadaliase mit CommonJS-Modulen zu verwenden, habe ich schlechte Nachrichten für Sie: Der folgende Code wird nicht funktionieren.
const { apiClient } = require('#shared/api'); const { ProductView } = require('#entities/product/components/ProductView'); const { addProductToCart } = require('#features/add-to-cart/actions');
Bei der Verwendung von Pfadaliasen in Node.js müssen Sie die Modulauflösungsregeln aus der ESM-Welt befolgen. Dies gilt sowohl für ES-Module als auch für CommonJS-Module und führt zu zwei neuen Anforderungen, die erfüllt werden müssen:
Es ist erforderlich, den vollständigen Pfad zu einer Datei einschließlich der Dateierweiterung anzugeben.
Es ist nicht zulässig, einen Pfad zu einem Verzeichnis anzugeben und zu erwarten, dass eine index.js
Datei importiert wird. Stattdessen muss der vollständige Pfad zu einer index.js
Datei angegeben werden.
Damit Node.js Module korrekt auflösen kann, sollten die Importe wie folgt korrigiert werden:
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');
Diese Einschränkungen können zu Problemen bei der Konfiguration des imports
in einem Projekt mit vielen CommonJS-Modulen führen. Wenn Sie jedoch bereits ES-Module verwenden, erfüllt Ihr Code alle Anforderungen.
Darüber hinaus können Sie diese Einschränkungen umgehen, wenn Sie Code mit einem Bundler erstellen. Wie das geht, besprechen wir weiter unten.
Um importierte Module für die Typprüfung ordnungsgemäß aufzulösen, muss TypeScript das imports
unterstützen. Diese Funktion wird ab Version 4.8.1 unterstützt , jedoch nur, wenn die oben aufgeführten Node.js-Einschränkungen erfüllt sind.
Um das imports
für die Modulauflösung zu verwenden, müssen einige Optionen in der Datei tsconfig.json
konfiguriert werden.
{ "compilerOptions": { /* Specify what module code is generated. */ "module": "esnext", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "nodenext" } }
Durch diese Konfiguration funktioniert das imports
genauso wie in Node.js. Das bedeutet, dass TypeScript eine Fehlermeldung generiert, wenn Sie vergessen, eine Dateierweiterung in einen Modulimport aufzunehmen.
// 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';
Ich wollte nicht alle Importe neu schreiben, da die meisten meiner Projekte einen Bundler zum Erstellen von Code verwenden und ich beim Importieren von Modulen niemals Dateierweiterungen hinzufüge. Um diese Einschränkung zu umgehen, habe ich eine Möglichkeit gefunden, das Projekt wie folgt zu konfigurieren:
{ "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" ] } }
Diese Konfiguration ermöglicht den üblichen Import von Modulen, ohne dass Erweiterungen angegeben werden müssen. Dies funktioniert sogar, wenn ein Importpfad auf ein Verzeichnis verweist.
// 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';
Wir haben noch ein Problem, das den Import über einen relativen Pfad betrifft. Dieses Problem hängt nicht mit Pfadaliasen zusammen. TypeScript gibt einen Fehler aus, weil wir die Modulauflösung für die Verwendung des nodenext
Modus konfiguriert haben.
Glücklicherweise wurde in der jüngsten Version von TypeScript 5.0 ein neuer Modulauflösungsmodus hinzugefügt, der die Angabe des vollständigen Pfads innerhalb von Importen überflüssig macht. Um diesen Modus zu aktivieren, müssen einige Optionen in der Datei tsconfig.json
konfiguriert werden.
{ "compilerOptions": { /* Specify what module code is generated. */ "module": "esnext", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "bundler" } }
Nach Abschluss der Einrichtung funktionieren Importe für relative Pfade wie gewohnt.
// 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';
Jetzt können wir Pfadaliase über das Feld imports
vollständig nutzen, ohne dass es zusätzliche Einschränkungen beim Schreiben von Importpfaden gibt.
Beim Erstellen von Quellcode mit dem tsc
Compiler ist möglicherweise eine zusätzliche Konfiguration erforderlich. Eine Einschränkung von TypeScript besteht darin, dass bei Verwendung des imports
kein Code im CommonJS-Modulformat erstellt werden kann.
Daher muss der Code im ESM-Format kompiliert werden und das type
muss zu package.json
hinzugefügt werden, um kompilierten Code in Node.js auszuführen.
{ "name": "my-awesome-project", "type": "module", "imports": { "#*": "./src/*" } }
Wenn Ihr Code in ein separates Verzeichnis kompiliert wird, z. B. build/
, wird das Modul möglicherweise von Node.js nicht gefunden, da der Pfadalias auf den ursprünglichen Speicherort verweisen würde, z. B. src/
. Um dieses Problem zu lösen, können bedingte Importpfade in der Datei package.json
verwendet werden.
Dadurch kann bereits erstellter Code aus dem Verzeichnis build/
anstelle des Verzeichnisses src/
importiert werden.
{ "name": "my-awesome-project", "type": "module", "imports": { "#*": { "default": "./src/*", "production": "./build/*" } } }
Um eine bestimmte Importbedingung zu verwenden, sollte Node.js mit dem Flag --conditions
gestartet werden.
node --conditions=production build/index.js
Code-Bundler verwenden normalerweise ihre eigene Modulauflösungsimplementierung und nicht die in Node.js integrierte. Daher ist es für sie wichtig, Unterstützung für den imports
zu implementieren.
Ich habe Pfadaliase mit Webpack, Rollup und Vite in meinen Projekten getestet und bin bereit, meine Erkenntnisse zu teilen.
Hier ist die Pfad-Alias-Konfiguration, die ich zum Testen der Bundler verwendet habe. Ich habe den gleichen Trick wie bei TypeScript verwendet, um zu vermeiden, dass in Importen der vollständige Pfad zu Dateien angegeben werden muss.
{ "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 unterstützt das imports
ab Version 5.0. Pfadaliase funktionieren ohne zusätzliche Konfiguration. Hier ist die Webpack-Konfiguration, die ich zum Erstellen eines Testprojekts mit TypeScript verwendet habe:
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;
Unterstützung für das imports
wurde in Vite Version 4.2.0 hinzugefügt . Allerdings wurde in Version 4.3.3 ein wichtiger Fehler behoben, daher wird empfohlen, mindestens diese Version zu verwenden. In Vite funktionieren Pfadaliase ohne die Notwendigkeit einer zusätzlichen Konfiguration sowohl im dev
als auch build
Modus.
Daher habe ich ein Testprojekt mit einer völlig leeren Konfiguration erstellt.
Obwohl Rollup in Vite verwendet wird, funktioniert das imports
nicht sofort. Um es zu aktivieren, müssen Sie das @rollup/plugin-node-resolve
Version 11.1.0 oder höher installieren. Hier ist eine Beispielkonfiguration:
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'], }), ], }, ];
Leider funktionieren Pfadaliase bei dieser Konfiguration nur innerhalb der Einschränkungen von Node.js. Das bedeutet, dass Sie den vollständigen Dateipfad einschließlich der Erweiterung angeben müssen. Durch die Angabe eines Arrays im imports
wird diese Einschränkung nicht umgangen, da Rollup nur den ersten Pfad im Array verwendet.
Ich glaube, dass es möglich ist, dieses Problem mithilfe von Rollup-Plugins zu lösen, habe dies jedoch nicht versucht, da ich Rollup hauptsächlich für kleine Bibliotheken verwende. In meinem Fall war es einfacher, die Importpfade im gesamten Projekt neu zu schreiben.
Testläufer sind eine weitere Gruppe von Entwicklungstools, die stark vom Modulauflösungsmechanismus abhängen. Sie verwenden oft ihre eigene Implementierung der Modulauflösung, ähnlich wie Code-Bundler. Daher besteht die Möglichkeit, dass das imports
nicht wie erwartet funktioniert.
Glücklicherweise funktionieren die von mir getesteten Tools gut. Ich habe Pfadaliase mit Jest v29.5.0 und Vite v0.30.1 getestet. In beiden Fällen funktionierten die Pfadaliase nahtlos ohne zusätzliche Einrichtung oder Einschränkungen. Jest unterstützt das imports
seit Version v29.4.0.
Der Grad der Unterstützung in Vitest hängt ausschließlich von der Version von Vite ab, die mindestens v4.2.0 sein muss.
Das imports
in beliebten Bibliotheken wird derzeit gut unterstützt. Was ist jedoch mit Code-Editoren? Ich habe die Codenavigation, insbesondere die Funktion „Gehe zu Definition“, in einem Projekt getestet, das Pfadaliase verwendet. Es stellt sich heraus, dass es bei der Unterstützung dieser Funktion in Code-Editoren einige Probleme gibt.
Wenn es um VS-Code geht, ist die Version von TypeScript entscheidend. Der TypeScript Language Server ist für die Analyse und Navigation durch JavaScript- und TypeScript-Code verantwortlich.
Abhängig von Ihren Einstellungen verwendet VS Code entweder die integrierte oder die in Ihrem Projekt installierte Version von TypeScript.
Ich habe die imports
in VS Code v1.77.3 in Kombination mit TypeScript v5.0.4 getestet.
VS Code hat die folgenden Probleme mit Pfadaliasen:
TypeScript verwendet das imports
erst, wenn die Modulauflösungseinstellung auf nodenext
oder bundler
festgelegt ist. Um es in VS Code verwenden zu können, müssen Sie daher die Modulauflösung in Ihrem Projekt angeben.
IntelliSense unterstützt derzeit nicht das Vorschlagen von Importpfaden mithilfe des imports
. Für dieses Problem gibt es ein offenes Problem .
Um beide Probleme zu umgehen, können Sie eine Pfadaliaskonfiguration in der Datei tsconfig.json
replizieren. Wenn Sie TypeScript nicht verwenden, können Sie dasselbe in jsconfig.json
tun.
// tsconfig.json OR jsconfig.json { "compilerOptions": { "baseUrl": "./", "paths": { "#*": ["./src/*"] } } } // package.json { "name": "my-awesome-project", "imports": { "#*": "./src/*" } }
Seit Version 2021.3 (ich habe es in 2022.3.4 getestet) unterstützt WebStorm das imports
. Diese Funktion funktioniert unabhängig von der TypeScript-Version, da WebStorm einen eigenen Code-Analysator verwendet. WebStorm weist jedoch eine Reihe gesonderter Probleme hinsichtlich der Unterstützung von Pfadaliasen auf:
Der Editor befolgt strikt die von Node.js auferlegten Einschränkungen bei der Verwendung von Pfadaliasnamen. Die Codenavigation funktioniert nicht, wenn die Dateierweiterung nicht explizit angegeben wird. Gleiches gilt für den Import von Verzeichnissen mit einer index.js
Datei.
WebStorm weist einen Fehler auf, der die Verwendung eines Arrays von Pfaden im imports
verhindert. In diesem Fall funktioniert die Codenavigation vollständig nicht mehr.
{ "name": "my-awesome-project", // OK "imports": { "#*": "./src/*" }, // This breaks code navigation "imports": { "#*": ["./src/*", "./src/*.ts", "./src/*.tsx"] } }
Glücklicherweise können wir denselben Trick verwenden, der alle Probleme in VS Code löst. Insbesondere können wir eine Pfad-Alias-Konfiguration in der Datei tsconfig.json
oder jsconfig.json
replizieren. Dadurch ist die Verwendung von Pfadaliasen ohne Einschränkungen möglich.
Basierend auf meinen Experimenten und Erfahrungen mit dem imports
in verschiedenen Projekten habe ich die besten Pfad-Alias-Konfigurationen für verschiedene Arten von Projekten ermittelt.
Diese Konfiguration ist für Projekte gedacht, bei denen der Quellcode in Node.js ausgeführt wird, ohne dass zusätzliche Build-Schritte erforderlich sind. Um es zu verwenden, befolgen Sie diese Schritte:
Konfigurieren Sie das imports
in einer package.json
Datei. In diesem Fall reicht eine sehr einfache Konfiguration aus.
Damit die Codenavigation in Code-Editoren funktioniert, müssen Pfadaliase in einer jsconfig.json
Datei konfiguriert werden.
// jsconfig.json { "compilerOptions": { "baseUrl": "./", "paths": { "#*": ["./src/*"] } } } // package.json { "name": "my-awesome-project", "imports": { "#*": "./src/*" } }
Diese Konfiguration sollte für Projekte verwendet werden, bei denen der Quellcode in TypeScript geschrieben und mit dem tsc
Compiler erstellt wird. In dieser Konfiguration ist es wichtig, Folgendes zu konfigurieren:
Das imports
in einer package.json
Datei. In diesem Fall ist es notwendig, bedingte Pfadaliase hinzuzufügen, um sicherzustellen, dass Node.js kompilierten Code korrekt auflöst.
Die Aktivierung des ESM-Paketformats in einer package.json
Datei ist erforderlich, da TypeScript bei Verwendung des imports
nur Code im ESM-Format kompilieren kann.
Legen Sie in einer tsconfig.json
Datei das ESM-Modulformat und moduleResolution
fest. Dadurch kann TypeScript bei Importen vergessene Dateierweiterungen vorschlagen. Wenn keine Dateierweiterung angegeben ist, wird der Code nach der Kompilierung nicht in Node.js ausgeführt.
Um die Codenavigation in Code-Editoren zu korrigieren, müssen Pfadaliase in einer tsconfig.json
Datei wiederholt werden.
// 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/*" } } }
Diese Konfiguration ist für Projekte gedacht, bei denen Quellcode gebündelt ist. TypeScript ist in diesem Fall nicht erforderlich. Wenn es nicht vorhanden ist, können alle Einstellungen in einer jsconfig.json
Datei festgelegt werden.
Das Hauptmerkmal dieser Konfiguration besteht darin, dass Sie die Einschränkungen von Node.js hinsichtlich der Angabe von Dateierweiterungen bei Importen umgehen können.
Es ist wichtig, Folgendes zu konfigurieren:
Konfigurieren Sie das imports
in einer package.json
Datei. In diesem Fall müssen Sie jedem Alias ein Array von Pfaden hinzufügen. Dadurch kann ein Bundler das importierte Modul finden, ohne dass die Dateierweiterung angegeben werden muss.
Um die Codenavigation in Code-Editoren zu korrigieren, müssen Sie Pfadaliase in einer tsconfig.json
oder jsconfig.json
-Datei wiederholen.
// 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" ] } }
Das Konfigurieren von Pfadaliasen über das imports
hat im Vergleich zum Konfigurieren über Bibliotheken von Drittanbietern sowohl Vor- als auch Nachteile. Obwohl dieser Ansatz von gängigen Entwicklungstools unterstützt wird (Stand April 2023), weist er auch Einschränkungen auf.
Diese Methode bietet folgende Vorteile:
package.json
).
Es gibt jedoch vorübergehende Nachteile, die mit der Weiterentwicklung der Entwicklungstools beseitigt werden:
imports
. Um diese Probleme zu vermeiden, können Sie die Datei jsconfig.json
verwenden. Dies führt jedoch zu einer Duplizierung der Pfad-Alias-Konfiguration in zwei Dateien.
imports
. Rollup erfordert beispielsweise die Installation zusätzlicher Plugins.
imports
in Node.js werden neue Einschränkungen für Importpfade hinzugefügt. Diese Einschränkungen sind die gleichen wie für ES-Module, sie können jedoch den Einstieg in die Verwendung des imports
erschweren.
Lohnt es sich also, das imports
zum Konfigurieren von Pfadaliasen zu verwenden? Ich glaube, dass es sich für neue Projekte lohnt, diese Methode anstelle von Bibliotheken von Drittanbietern zu verwenden.
Das imports
hat gute Chancen, in den kommenden Jahren für viele Entwickler zu einer Standardmethode zur Konfiguration von Pfadaliasen zu werden, da es im Vergleich zu herkömmlichen Konfigurationsmethoden erhebliche Vorteile bietet.
Wenn Sie jedoch bereits über ein Projekt mit konfigurierten Pfadaliasen verfügen, bringt der Wechsel zum imports
keine wesentlichen Vorteile.
Ich hoffe, Sie haben aus diesem Artikel etwas Neues gelernt. Vielen Dank fürs Lesen!
Auch hier veröffentlicht