Как настроить псевдонимы путей в проектах внешнего интерфейса нативным способом
5 мая 2023 г.О псевдонимах пути
Проекты часто превращаются в сложные вложенные структуры каталогов. В результате пути импорта могут стать длиннее и запутаннее, что может негативно сказаться на внешнем виде кода и затруднить понимание источника импортируемого кода.
Использование псевдонимов пути может решить проблему, разрешив определение импорта, относящегося к предопределенным каталогам. Такой подход не только решает проблемы с пониманием путей импорта, но и упрощает процесс перемещения кода при рефакторинге.
// 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, таких как alias-hq и tsconfig-paths. Однако, просматривая документацию Node.js, я обнаружил способ настроить псевдонимы путей, не полагаясь на сторонние библиотеки.
Более того, этот подход позволяет использовать псевдонимы без этапа сборки.
В этой статье мы обсудим Импорт подпутей Node.js и как с его помощью настроить псевдонимы пути. Мы также изучим их поддержку в экосистеме внешнего интерфейса.
Поле импорта
Начиная с Node.js версии 12.19.0, разработчики могут использовать импорт подпути для объявления псевдонимов пути внутри npm-пакет. Это можно сделать с помощью поля imports
в файле package.json
. Пакет не обязательно публиковать на npm.
Достаточно создать файл package.json
в любом каталоге. Следовательно, этот метод подходит и для частных проектов.
:::подсказка
Вот интересный факт: Node.js представил поддержку поля imports
еще в 2020 году через RFC под названием «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');
Эти ограничения могут привести к проблемам при настройке поля imports
в проекте с большим количеством модулей CommonJS. Однако, если вы уже используете модули 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';
У нас осталась одна проблема, связанная с импортом с использованием относительного пути. Эта проблема не связана с псевдонимами путей. 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
При сборке исходного кода с помощью компилятора tsc
может потребоваться дополнительная настройка. Одним из ограничений TypeScript является то, что код нельзя построить в формате модуля CommonJS при использовании поля imports
.
Следовательно, код должен быть скомпилирован в формате ESM, а поле type
должно быть добавлено в package.json
для запуска скомпилированного кода в Node.js.
{
"name": "my-awesome-project",
"type": "module",
"imports": {
"#*": "./src/*"
}
}
Если ваш код скомпилирован в отдельный каталог, например build/
, Node.js может не найти модуль, поскольку псевдоним пути будет указывать на исходное местоположение, например src/ код>. Чтобы решить эту проблему, можно использовать условные пути импорта в файле
package.json
.
Это позволяет импортировать уже созданный код из каталога build/
вместо каталога src/
.
{
"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 поддерживает начало поля imports
начиная с версии 5.0. Псевдонимы пути работают без дополнительной настройки. Вот конфигурация Webpack, которую я использовал для создания тестового проекта с TypeScript:
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 псевдонимы путей работают без необходимости дополнительной настройки как в режимах dev
, так и в режимах build
.
Поэтому я собрал тестовый проект с абсолютно пустой конфигурацией.
Сводка
Несмотря на то, что Rollup используется внутри 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, но я не пробовал этого делать, потому что в основном использую Rollup для небольших библиотек. В моем случае было проще переписать пути импорта по всему проекту.
Поддержка импорта вложенных путей в средствах запуска тестов
Средства запуска тестов — это еще одна группа инструментов разработки, которые сильно зависят от механизма разрешения модулей. Они часто используют собственную реализацию разрешения модулей, аналогичную сборщикам кода. В результате есть вероятность, что поле imports
может работать не так, как ожидалось.
К счастью, инструменты, которые я тестировал, работают хорошо. Я тестировал псевдонимы путей с помощью Jest v29.5.0 и Vite v0.30.1. В обоих случаях псевдонимы пути работали без каких-либо дополнительных настроек или ограничений. Jest поддерживает поле imports
, начиная с версии v29.4.0. .
Уровень поддержки в Vitest зависит исключительно от версии Vite, которая должна быть не ниже 4.2.0.
Поддержка импорта вложенных путей в редакторах кода
Поле imports
в популярных библиотеках в настоящее время хорошо поддерживается. Однако как насчет редакторов кода? Я протестировал навигацию по коду, в частности функцию «Перейти к определению», в проекте, в котором используются псевдонимы путей. Оказывается, поддержка этой функции в редакторах кода имеет некоторые проблемы.
Код VS
Когда дело доходит до VS Code, версия TypeScript имеет решающее значение. Языковой сервер TypeScript отвечает за анализ и навигацию по коду JavaScript и TypeScript.
В зависимости от ваших настроек VS Code будет использовать либо встроенную версию TypeScript, либо версию, установленную в вашем проекте.
Я протестировал поддержку поля imports
в VS Code v1.77.3 в сочетании с TypeScript v5.0.4.
VS Code имеет следующие проблемы с псевдонимами путей:
- TypeScript не использует поле
imports
, пока для параметра разрешения модуля не установлено значениеnodenext
илиbundler
. Поэтому, чтобы использовать его в 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
. Эта функция работает независимо от версии TypeScript, поскольку WebStorm использует собственный анализатор кода. Однако у WebStorm есть отдельный набор проблем, связанных с поддержкой псевдонимов путей:
- Редактор строго соблюдает ограничения, налагаемые 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 без дополнительных шагов сборки. Чтобы использовать его, выполните следующие действия:
- Настройте поле
imports
в файлеpackage.json
. В этом случае достаточно самой простой конфигурации.
2. Чтобы навигация по коду работала в редакторах кода, необходимо настроить псевдонимы путей в файле jsconfig.json
.
// jsconfig.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"#*": ["./src/*"]
}
}
}
// package.json
{
"name": "my-awesome-project",
"imports": {
"#*": "./src/*"
}
}
Создание кода с использованием TypeScript
Эту конфигурацию следует использовать для проектов, исходный код которых написан на TypeScript и собран с помощью компилятора tsc
. В этой конфигурации важно настроить следующее:
- Поле
imports
в файлеpackage.json
. В этом случае необходимо добавить псевдонимы условного пути, чтобы убедиться, что Node.js правильно разрешает скомпилированный код.
2. Включение формата пакета ESM в файле package.json
необходимо, поскольку TypeScript может компилировать код в формате ESM только при использовании поля imports
.
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, касающиеся указания расширений файлов при импорте.
Важно настроить следующее:
- Настройте поле
imports
в файлеpackage.json
. В этом случае вам нужно добавить массив путей к каждому псевдониму. Это позволит сборщику найти импортированный модуль, не требуя указания расширения файла.
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 г.), он также имеет ограничения.
Этот метод предлагает следующие преимущества:
- Возможность использовать псевдонимы путей без необходимости компилировать или транспилировать код "на лету".
* Большинство популярных средств разработки поддерживают псевдонимы путей без какой-либо дополнительной настройки. Это было подтверждено в Webpack, Vite, Jest и Vitest.
* Этот подход способствует настройке псевдонимов путей в одном предсказуемом месте (файл package.json
).
* Настройка псевдонимов пути не требует установки сторонних библиотек.
Однако существуют временные недостатки, которые будут устранены по мере развития средств разработки:
* Даже популярные редакторы кода имеют проблемы с поддержкой поля imports
. Чтобы избежать этих проблем, вы можете использовать файл jsconfig.json
. Однако это приводит к дублированию конфигурации псевдонима пути в двух файлах.
* Некоторые средства разработки могут не работать с полем imports
из коробки. Например, для Rollup требуется установка дополнительных подключаемых модулей.
* Использование поля imports
в Node.js добавляет новые ограничения на пути импорта. Эти ограничения такие же, как и для модулей ES, но они могут затруднить начало использования поля imports
.
* Ограничения Node.js могут привести к различиям в реализации между Node.js и другими инструментами разработки. Например, сборщики кода могут игнорировать ограничения Node.js. Эти различия могут иногда усложнять настройку, особенно при настройке TypeScript.
Итак, стоит ли использовать поле imports
для настройки псевдонимов пути? Я считаю, что для новых проектов да, этот метод стоит использовать вместо сторонних библиотек.
Поле imports
имеет хорошие шансы стать стандартным способом настройки псевдонимов путей для многих разработчиков в ближайшие годы, поскольку оно предлагает значительные преимущества по сравнению с традиционными методами настройки.
Однако если у вас уже есть проект с настроенными псевдонимами путей, переключение на поле imports
не принесет значительных преимуществ.
Надеюсь, вы узнали что-то новое из этой статьи. Спасибо за прочтение!
Полезные ссылки
- RFC для реализации экспорта и импорта
- Набор тестов для лучшего понимания возможностей поля импорта
- Документация по полю импорта в Node.js
- Ограничения Node.js на пути импорта в модулях ES
Также опубликовано здесь< /эм>
Оригинал