Делаем import/require ясными и красивыми

—

от автора

Довольно часто в проектах встречается использование относительных import/require. Если это маленький проект, и подключается модуль из текущей папки, то это приемлемо, но при разрастании проекта и глубины вложенности папочной структуры без слез смотреть на это нельзя:

import { User } from '../../user/model'; import { Article } from '../../article/model';  import { Cache } from '../../../../cache'; import { MongoDB } from '../../../../mongodb'; 

Основные минусы относительных путей:

  • Они плохо читаются и загрязняют код. Человеку требуются когнитивные усилия, чтобы интерпретировать эти ../../ в реальный путь. Гораздо проще читаются пути от корня к файлам.

  • Редакторы кода не всегда корректно исправляют относительные пути при перемещении файла. А если редактор не смог добавить autoimport сущности из модуля при написании кода, то одна боль писать руками относительный import в более или менее развесистом проекте. Копирование import/require из другого файла тоже плохо работает, ибо если новый файл лежит на другом уровне вложенности, то придется во всех скопированных import/require добавлять/удалять ../../.

  • При любом перемещении файла надо поменять не только пути в import/require в других файлах, которые его подключают, но и в самом перемещаемом файле меняются import/require (надо будет добавить/убрать лишние перемещения по каталогам). Это создает дополнительный «шум» в системе контроля версии, хотя по сути ничего не поменялось. С абсолютным путем этой проблемы бы не было.

В некоторых языках, как в Python, возможность писать импорты от корня проекта есть из коробки, но в JavaScript этого нет. К счастью, это довольно не сложно добавить.

Есть несколько способов решения проблемы:

Cимлинк

Создать симлинк в корне вашей системы, который ведет к вашему проекту, и использовать его для написания require/import.

// sudo ln -s /Users/mitya/project prj  // Вместо const User = require('../../model/User'); // Можно будет использовать вот такую запись const User = require('/prj/src/model/User'); 

Минус в том, что с симлинками могут быть «сюрпризы». Далеко не во всех операционках это легко. В современной MacOS есть определенные нюансы (надо использовать synthetic.conf). Кроме того, из-за странной работы с симлинками от корня на MacOS, компилятор typescript при компиляции проекта по симлинку от корня генерит что-то странное. Как в Windows, не знаю, но наверняка есть проблемы или ограничения. Судя по всему проблем не будет только на Linux. К минусам так же можно отнести то, что чисто гипотетически название симлинка может конфликтовать (например, ваш домашний и рабочий проект использует симлинк prj) и невозможность разложить копии проекта в разные папки и запустить без правки путей к модулям (тут виртуализация и контейнеризация поможет).

NODE_PATH

Другой вариант это добавить пути к вашим папкам в переменную окружения NODE_PATH. По умолчанию, она содержит пути для того, чтобы node js могла подключить модули из node_modules, но мы можем использовать ее в своих целях:

// Вместо const User = require('../../model/User'); // Можно использовать вот такую запись const User = require('src/model/User');  // при старте указываем пути, в которых node должна искать модули // NODE_PATH=./ node src/app/main.js  // Для typescript необходимо добавить baseUrl, который должен совпадать с NODE_PATH {   "compilerOptions": {     "baseUrl": "."   } }  

Минус это то, что не все редакторы хорошо работают c NODE_PATH в плане перехода к файлу из объявления import/require. Но довольно давно появился способ получше.

Path aliases

Path aliases — это функционал для использования алиасов для путей внутри require/import.

// Вместо const User = require('../../model/User');  // Можно использовать вот такую запись const User = require('@/model/User'); 

Для того чтобы такая запись работала, нам потребуется модуль module-alias:

npm i module-alias -S 

Для того чтобы module-alias мог сопоставить алиасы в реальный путь в файловой системе необходимо добавить следующую запись в package.json:

"_moduleAliases": {     "@": "src"   } 

Затем в самом вверху файла, который является точкой сборки приложения, добавить import/require самого модуля:

// src/app/main.js require('module-alias/register'); // import 'module-alias/register'; 

Другой вариант подключения это загружать модуль через командую строку:

node -r ./node_modules/module-alias/register src/app/main.js 

Для того чтобы использовать path aliases в typescript необходимо добавить в tsconfig.json директиву path, в которой указать алиасы до исходников:

{   "compilerOptions": {     "paths": {       "@/*": ["./src/*"]     }   } } 

А папку для алиасов поменять в package.json на папку с итоговой сборкой, в нашем случае dist:

  "_moduleAliases": {     "@": "dist"   }, 

Теперь наши импорты с aлиасами должны работать:

node -r ./node_modules/module-alias/register dist/app/main.js 

Если вы пишете бэкенд и для запуска проекта используете ts-node, то вам потребуется модуль ts-config-path для работы path aliases:

npm i tsconfig-paths -S; npx ts-node -r tsconfig-paths/register src/app/main.ts; 

Я же для запуска бэкенда на typescript использую tsx, он из коробки понимает path aliases:

npm i tsx -D; npx tsx src/app/main.ts; 

Алиасов можно создавать сколько душе угодно:

{   "compilerOptions": {     "paths": {         "app/*": ["./src/app/*"],         "config/*": ["./src/config/*"],         "shared/*": ["./src/shared/*"],         "cache/*": ["./src/cache/*"],         "tests/*": ["./src/tests/*"]     }, } 

Подробнее про path alias можно почитать здесь и здесь. Используя path вместе с baseUrl можно организовать довольно интересные схемы подключения модулей из разных папок.

Теперь мы можем писать красивые и понятные import/require:

import { User } from '@component/user/model'; import { Article } from '@component/article/model';  import { Cache } from '@cache/cache'; import { MongoDB } from '@db/mongodb'; 


ссылка на оригинал статьи https://habr.com/ru/articles/758514/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *