Что такое __dirname в JavaScript

Разработчикам на нативном JS история про различия систем модулей CommonJS и ECMAScript знакома на собственном опыте. Сейчас идёт активное внедрение ECMAScript на уровень языка, а в Node.js новых версий «из коробки» она уже работает нативно. ECMAScript-модули принесли за собой некоторые другие явления:
- Необходимость указывать
"type": "module"
в package.json; - Временный костыль для запуска
jest
с ключами--experimental-vm-modules
и, опционально,--no-warnings
; - По умолчанию, отсутствуют глобальные константы
__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"] }] # разрешает подчёркивание в именах только для двух констант
Чтобы столкнуться с данной задачей прямо сейчас, достаточно начать прохождение второго проекта на Хекслете.
Sergei Melodyn
5 лет назад