Правила для модульной системы

Модульная система JavaScript определяет способ разбиения кода на изолированные единицы, их экспорта и последующего использования в других файлах. ESLint рассматривает модули как статическую структуру, поддающуюся анализу, и предоставляет набор правил, обеспечивающих предсказуемость импортов, отсутствие циклических зависимостей, корректность путей и согласованность архитектуры.

ESLint работает на уровне синтаксического дерева (AST), поэтому анализ импортов опирается на статические конструкции import и export. Это создаёт ряд принципиальных ограничений:

  • динамические require() частично выпадают из полного анализа
  • вычисляемые пути импорта не могут быть проверены на этапе линтинга
  • алиасы и резолверы требуют дополнительной конфигурации

Модульная проверка в ESLint строится вокруг предположения, что структура зависимостей должна быть прозрачной и предсказуемой без выполнения кода.

Разделение ES Modules и CommonJS

В современных проектах сосуществуют две модульные системы:

ES Modules

Используют синтаксис import и export:

import fs from 'fs';
export const value = 42;

Особенности:

  • статическая структура зависимостей
  • поддержка tree-shaking
  • строгая область видимости модулей

CommonJS

Использует require и module.exports:

const fs = require('fs');
module.exports = { value: 42 };

Особенности:

  • динамическая природа загрузки
  • возможность условных импортов
  • менее предсказуемый граф зависимостей

ESLint-правила для модулей учитывают различия этих систем, но большинство современных правил ориентировано на ES Modules как целевой стандарт.

Контроль корректности импортов

import/no-unresolved

Одно из ключевых правил модульной системы, проверяющее возможность разрешения импортируемых путей.

Основные задачи:

  • проверка существования файлов
  • контроль npm-зависимостей
  • учёт настроек alias и resolver plugins

Пример нарушения:

import utils from './utills/helpers';

Ошибка возникает при отсутствии файла или опечатке в пути.

Настройка резолвера особенно важна при использовании TypeScript, Webpack или Babel:

{
  "settings": {
    "import/resolver": {
      "node": {
        "extensions": [".js", ".ts"]
      }
    }
  }
}

Контроль зависимостей проекта

import/no-extraneous-dependencies

Правило контролирует соответствие импортов объявленным зависимостям в package.json.

Проверяемые случаи:

  • использование библиотек, не указанных в dependencies
  • импорт devDependencies в production-коде
  • некорректное использование peerDependencies

Пример:

import lodash from 'lodash';

Если lodash не указан в зависимостях, ESLint фиксирует нарушение.

Правило критично для монорепозиториев и CI-сборок, где избыточные зависимости приводят к нестабильности окружения.

Управление структурой импортов

import/order

Одна из наиболее используемых групп правил, регулирующая порядок импортов.

Ключевые группы:

  • встроенные модули Node.js
  • внешние зависимости
  • внутренние модули проекта
  • стили и побочные импорты

Пример логически упорядоченного кода:

import fs from 'fs';
import React from 'react';
import utils from '@/utils/helpers';
import './styles.css';

Дополнительно правило может:

  • требовать пустые строки между группами
  • сортировать импорты по алфавиту
  • разделять type-only импорты

Конфигурация:

{
  "rules": {
    "import/order": [
      "error",
      {
        "groups": ["builtin", "external", "internal"],
        "newlines-between": "always"
      }
    ]
  }
}

Контроль циклических зависимостей

import/no-cycle

Циклические зависимости нарушают линейность графа модулей:

// a.js
import { b } from './b';

// b.js
import { a } from './a';

Такие структуры приводят к:

  • частично инициализированным модулям
  • непредсказуемому поведению во время выполнения
  • трудностям при рефакторинге

ESLint анализирует граф импортов и выявляет циклы на уровне файлов или модулей. В сложных проектах правило может учитывать глубину цикла:

{
  "rules": {
    "import/no-cycle": ["error", { "maxDepth": 1 }]
  }
}

Дублирование импортов и избыточность

import/no-duplicates

Запрещает повторные импорты из одного источника:

import { map } from 'lodash';
import { filter } from 'lodash';

Корректная форма:

import { map, filter } from 'lodash';

Это правило улучшает читаемость и упрощает анализ зависимостей.

import/no-self-import

Запрещает импорт самого себя:

import utils from './utils';

внутри utils.js.

Такие ошибки часто возникают при рефакторинге и переименовании файлов.

Контроль путей и структуры проекта

import/no-useless-path-segments

Удаляет избыточные сегменты путей:

import utils from './utils/index.js';

предлагается заменить на:

import utils from './utils';

Правило уменьшает шум в графе импортов и повышает читаемость структуры каталогов.

import/extensions

Контролирует использование расширений файлов:

import helper from './helper.js';

или

import helper from './helper';

В зависимости от конфигурации проекта правило может:

  • требовать явного указания расширений
  • запрещать их использование для JS/TS
  • различать production и development окружения

Пример конфигурации:

{
  "rules": {
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "ts": "never"
      }
    ]
  }
}

Порядок выполнения импортов

import/first

Гарантирует, что все импорты находятся в начале файла:

console.log('init');
import fs from 'fs';

Такой код нарушает правило, поскольку импорт должен быть статическим и предсказуемым.

Правильный вариант:

import fs from 'fs';

console.log('init');

Это правило важно для корректной работы статического анализа.

import/newline-after-import

Регулирует визуальное разделение импортов и основного кода:

import fs from 'fs';

function run() {}

Отсутствие разделительной строки ухудшает читаемость и затрудняет восприятие структуры файла.

Управление архитектурными ограничениями

no-restricted-imports

Позволяет явно запрещать импорт определённых модулей:

{
  "rules": {
    "no-restricted-imports": [
      "error",
      {
        "paths": ["moment"]
      }
    ]
  }
}

Пример применения:

  • запрет тяжёлых библиотек
  • ограничение доступа к внутренним слоям архитектуры
  • контроль использования legacy-модулей

Также возможно ограничение подмодулей:

{
  "paths": [
    {
      "name": "lodash",
      "importNames": ["cloneDeep"]
    }
  ]
}

Управление экспортами

import/prefer-default-export

Регулирует стиль экспорта в модулях с единственной сущностью:

export function parse() {}

вместо:

export default function parse() {}

или наоборот — в зависимости от выбранной архитектуры.

Правило влияет на:

  • консистентность API модулей
  • предсказуемость импортов
  • структуру публичных интерфейсов

Работа с алиасами и резолвингом

В крупных проектах часто используются алиасы:

import Button from '@/ui/Button';

ESLint не понимает такие пути без дополнительной настройки. Используются резолверы:

  • eslint-plugin-import
  • eslint-import-resolver-node
  • eslint-import-resolver-alias
  • TypeScript resolver

Пример настройки:

{
  "settings": {
    "import/resolver": {
      "alias": {
        "map": [["@", "./src"]],
        "extensions": [".js", ".ts"]
      }
    }
  }
}

Без корректного резолвера правила вроде import/no-unresolved будут давать ложные срабатывания.

Модульная согласованность в больших кодовых базах

Совокупность правил модульной системы формирует архитектурный слой линтинга, который отвечает за:

  • предсказуемость зависимостей
  • отсутствие скрытых связей между модулями
  • контроль направленности импортов (например, запрет обратных зависимостей между слоями)
  • единообразие структуры файлов

В крупных приложениях модульные правила ESLint фактически становятся частью архитектурной спецификации, фиксируя допустимые формы взаимодействия между частями системы.