Стек: React, TypeScript, ESLint 9, IDE: VS Code.
Зачем организовывать импорты
Импорты в приложении – самодокументируемая шапка файла. Взглянув на неё, можно быстро сделать вывод о содержимом: какие библиотеки, компоненты, типы и стили - если речь о React-компоненте - используются в файле, и создать общее впечатление о модуле.
Хорошо, когда эта «шапка» имеет единый порядок: мы быстро можем увидеть, например, какие типы использует файл, много ли компонентов он содержит и т.п. Короче говоря – это ускоряет и точечный, и общий анализ файла. Ну и просто приятно, когда код содержится в порядке и подчиняется единым правилам.
Цели, поставленные для организации импортов
Разделить импорты на группы: библиотеки / внешние / внутренние / типы TypeScript / стили и прочее.
Типы TypeScript тоже должны быть разделены на группы, соответствующие основным группам импортов. Также необходимо использовать в файле только top-level type imports (
import type { ... }), inline type imports не использовать (мне кажется, так удобнее).Чтобы разделители (новые строки) добавлялись автоматически между определёнными группами, а не между всеми подряд.
Делать это вручную или просить команду всегда выделять импорты отдельно – можно, но сложно. Всё равно будут ошибки, что послужит поводом для комментария на ревью.
Попытка использовать только eslint-plugin-import
Первый плагин, который я попробовала – популярный eslint-plugin-import. У него есть важное правило import/consistent-type-specifier-style, которое позволяет отделить все inline-импорты типов в top-level. (Если существуют другие способы выделения типов в top-level imports – было бы интересно узнать.)
С помощью этого плагина я хотела настроить и порядок импортов, но не хватило кастомизации, а именно – выделения стилей в отдельную группу (нужно создавать кастомную с регулярными выражениями), расстановки новых строк только между определенными группами, а не между всеми. Это мелочи, которые можно было решить ручной доработкой, но хотелось добиться полной автоматической правки файла при сохранении.
Добавление плагина eslint-plugin-perfectionist
В итоге я настроила порядок импортов с помощью другого популярного плагина – eslint-plugin-perfectionist. У него больше групп по умолчанию и более гибкая настройка расположения новых строк между группами. Единственный недостаток: нет встроенного правила для автоматической группировки импортов типов в соответствии с порядком основных импортов – расстановку импортов типов нужно прописывать явно в массиве групп.
Получившийся конфиг
В итоге я использовала комбинацию двух вышеуказанных плагинов, и это позволило добиться желаемого результата - автоматической настройки без ручных доработок. И некоторого снижения количества комментариев в код-ревью)
Обратите внимание на customGroups. По задумке импорты React должны идти первыми и в основных импортах, и в типах TypeScript. Группа type-react идёт первой в массиве customGroups, поэтому при сортировке типы React отделяются первыми в отдельную группу. Если поменять порядок групп в customGroups, типы React попадут наверх вместе с обычными импортами React, а это нам не нужно.
import importPlugin from 'eslint-plugin-import'; import perfectionisPlugin from 'eslint-plugin-perfectionist'; export default { files: ['**/*.{js,ts,tsx}'], plugins: { perfectionist: perfectionisPlugin, import: importPlugin }, rules: { 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], // разрешает только top-level type import 'import/newline-after-import': ['error', { count: 1 }], // пустая строка после импортов 'perfectionist/sort-imports': [ 'error', { type: 'alphabetical', // сортировка элементов по алфавиту order: 'asc', // порядок от a к z ignoreCase: true, // игнорирование регистра при сортировке groups: [ 'side-effect', // side-effect импорты { newlinesBetween: 1 }, 'builtin', // node встроенные модули (fs, path) { newlinesBetween: 1 }, 'react', { newlinesBetween: 0 }, 'external', // npm пакеты { newlinesBetween: 1 }, 'internal', // внутренние импорты { newlinesBetween: 'ignore' }, 'parent', // ../ импорты { newlinesBetween: 'ignore' }, 'sibling', // ./ импорты { newlinesBetween: 'ignore' }, 'index', // ./index импорты { newlinesBetween: 1 }, 'import', // любые импорты { newlinesBetween: 1 }, // все импорты типов 'type-react', { newlinesBetween: 0 }, 'type-external', { newlinesBetween: 1 }, 'type-internal', { newlinesBetween: 'ignore' }, 'type-parent', { newlinesBetween: 'ignore' }, 'type-sibling', { newlinesBetween: 'ignore' }, 'type-index', { newlinesBetween: 0 }, 'type-import', { newlinesBetween: 1 }, 'side-effect-style', // side-effect импорты стилей { newlinesBetween: 0 }, 'style', // импорты стилей ], customGroups: [ { groupName: 'type-react', elementNamePattern: ['^react$', '^react-.*'], selector: 'type', }, { groupName: 'react', elementNamePattern: ['^react$', '^react-.*'], }, ], newlinesBetween: 1, // пустая строка между группами internalPattern: ['^app/.+'], // петтерны внутренних путей }, ], }, };
Результат применения
примеры до


примеры после


Если у вас есть опыт организации импортов в больших проектах или вы знаете другие способы автоматической сортировки – буду рада обсудить в комментариях!
subzey
Чувствую, что нахватаю минусов, но… не очень понимаю, зачем люди сортируют импорты.
От этого же нет практической пользы: машинам наплевать на порядок, а люди всё равно эту простыню импортов не читают и проматывают сразу на реализацию.
Я видел мерж реквесты, которые откладываются на день из-за неправильного переноса строки между импортами. Я видел мерж конфликты от того, что кто-то решил отсортировать импорты по алфавиту. Человеческие усилия и время впустую, и чего ради?
ooko
Чтоб ИИ быстрее заменил людей
onmotion
Порядок некоторых импортов, например таких, как scss важен (вы можете получить другой результат рендера).
Так же, без правила сортировки, каждый мейнтенер будет коммитить кучу конфликтов или просто мусорных изменений в порядке импортов, согласно его видения или видения его IDE
subzey
Стили конечно особняком стоят, соглашусь. Но тут есть нюанс: Бандлер постарается сохранить порядок импортов в выхлопе, но совершенно не факт, что у него это получится если структура чанков станет сложнее плоского списка: при наследовании энтрей и аснхронном импорте. Хорошо, что у нас теперь есть
@layer, и мы можем вручную устанавливать специфичность, а не полагаться на порядок импорта!А IDE — ну так у IDE есть оператор. Вводить автомат чтобы он огородился от действий других автоматов — Скайнет уже победил?)
mops2
Ну, то, что люди не читают импорты, не всегда верно. Думаю, в 90% это, конечно, так, особенно в мире фронта, где компонентный подход с потенциально маленькими компонентами. Но когда тебе нужно разобраться в импортах, вручную что-то поправить ну или по другим любым причинам, я думаю, проще смотреть на отсортированные и сгруппированные импорты, чем когда в каждом файле свой порядок.
Ну и плюс импорты — это тоже часть кодстайла на проекте.
Что касается последнего абзаца, то откладывание МРа из-за переноса строк между импортами уже выглядит как неправильно настроенные линтеры и форматеры. По поводу конфликтов они все равно будут, просто теперь ты можешь не тратить на это время, а вмержить и прогнать линтером. Как раз меньше человеческих усилий и больше автоматизации.
Могу сказать за себя, я не сортирую импорты руками и не трачу на это время, но я пробегаюсь глазами по импортам, и, когда они отсортированы, в этом проще разобраться, а остальное делает линтер.