Делаем 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/


Интересные статьи

Интересные статьи

Мои давние читатели наверняка заметили моё явное сонибойство. Но нет, сонибойство не в плане игровых консолей, а их замечательных мобилках из прошлого. Вспомнить только крутые девайсы из W и K серий. ...
Шар, венчающий самое высокое здание Севастополя, — не что иное как круглый обтекатель станции спутниковой связи «Волна-М». Ранее в этом здании располагалась штаб-квартира конструкторского бюро НПО «Му...
Я не ждал Cyberpunk 2077 десять лет, я даже не смотрел презентации на Е3 и трейлеров, а переносы меня не огорчали — мой компьютер все равно не удовлетворял системным требованиям. Так уж произошло, ч...
Создание ботов - довольно заезжанная тема, но все уроки, статьи и различного рода документация дают информацию только о том, как построить одноуровневого бота без возможности создания древа из различн...
Привет, Хабр. В предыдущей части я рассматривал создание несложной распознавалки текста, основанной на нейронной сети. Сегодня мы применим аналогичный подход, и напишем автоматический переводч...