Что такое __dirname в JavaScript
2026-02-26 22:30 Diff

Разработчикам на нативном JS история про различия систем модулей CommonJS и ECMAScript знакома на собственном опыте. Сейчас идёт активное внедрение ECMAScript на уровень языка, а в Node.js новых версий «из коробки» она уже работает нативно. ECMAScript-модули принесли за собой некоторые другие явления:

  1. Необходимость указывать "type": "module" в package.json;
  2. Временный костыль для запуска jest с ключами --experimental-vm-modules и, опционально, --no-warnings;
  3. По умолчанию, отсутствуют глобальные константы __filename и __dirname;

Почему последний пункт важен? Дело в том, что работая с путями, можно построить две рабочих системы: надёжную и ненадёжную. Использование одной из этих констант делает решение по умолчанию надёжнее.

Содержание

Сначала ломаем

Имеется следующая структура каталогов:

Файл index.test.js содержит такой код:

Для начала тесты запускаются в корневом каталоге:

Как видно, __dirname отсутствует в глобальной области видимости, самое простое решение — убрать её.

Тесты всё ещё падают, но почему? Из текста ошибки так сразу и не скажешь, но теперь поиск файла фикстур происходит на один каталог выше, как будто из корня было набрано cat ../__fixtures__/expected_file.json:

Тесты пройдены, но надёжность системы нарушена, потому что этими действиями обеспечено недетерминированное поведение функции построения путей:

Тесты снова упали, потому что теперь path.join собирает путь относительно места запуска.

Так вот, глобальная константа __filename содержит абсолютный путь к файлу, в котором она используется, а __dirname, соответственно, к каталогу. Зная, что необходимый файл лежит относительно текущего всегда на N каталогов выше/ниже, используя данные константы, можно обеспечить детерминированное поведение при запуске кода из любого каталога.

Теперь строим

Официальная документация Node.js предлагает, может быть, не самое красивое решение, но рабочее:

Уже зная, как работают эти константы, остаётся вернуть первоначальное решение и опробовать его:

Отличная работа! Теперь пути чётко описывают свой контекст, а код корректно отрабатывает, независимо от места запуска.

Нужно также учесть

Отличие path.join и path.resolve

При выборе между path.join и path.resolve нужно ориентироваться на ожидаемое поведение:

— path.join('/a', '/b', 'c') // вернёт /a/b/c сборка происходит слева направо, абсолютный путь строится от первого аргумента с полным путём;

— path.resolve('/a', '/b', 'c') // вернёт /b/c абсолютный путь строится от последнего аргумента с абсолютным путём, можно считать что сборка происходит справа налево.

Тесты могут врать

Команда npm test под капотом игнорирует место запуска в рамках проекта и производит запуск от корня. То есть тесты всегда будут проходить вне зависимости от наличия __dirname. С учётом не самого информативного вывода тестов, при ошибке (если оно вообще когда-то вскроется) это позволяет получить гейзенбаг.

Настройки линтера

При использовании eslint с правилами airbnb возникнет две ошибки: Parsing error: Unexpected token import и Unexpected dangling '_' in '__filename'.(no-underscore-dangle). Для их решения в .eslintrc на верхнем уровне достаточно добавить эти настройки:

# Включает поддержку конструкции import.meta.url parserOptions: ecmaVersion: 2020 rules: no-underscore-dangle: [2, { "allow": ["__filename", "__dirname"] }] # разрешает подчёркивание в именах только для двух констант