HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Разработчикам на нативном JS история про различия систем модулей CommonJS и ECMAScript знакома на собственном опыте. Сейчас идёт активное внедрение ECMAScript на уровень языка, а в Node.js новых версий "из коробки" она уже работает нативно. ECMAScript-модули принесли за собой некоторые другие явления:</p>
1 <p>Разработчикам на нативном JS история про различия систем модулей CommonJS и ECMAScript знакома на собственном опыте. Сейчас идёт активное внедрение ECMAScript на уровень языка, а в Node.js новых версий "из коробки" она уже работает нативно. ECMAScript-модули принесли за собой некоторые другие явления:</p>
2 <ol><li>Необходимость указывать "type": "module" в<em>package.json</em>;</li>
2 <ol><li>Необходимость указывать "type": "module" в<em>package.json</em>;</li>
3 <li>Временный костыль для запуска jest с ключами --experimental-vm-modules и, опционально, --no-warnings;</li>
3 <li>Временный костыль для запуска jest с ключами --experimental-vm-modules и, опционально, --no-warnings;</li>
4 <li>По умолчанию, отсутствуют глобальные константы __filename и __dirname;</li>
4 <li>По умолчанию, отсутствуют глобальные константы __filename и __dirname;</li>
5 </ol><p>Почему последний пункт важен? Дело в том, что работая с путями, можно построить две<strong>рабочих</strong>системы: надёжную и ненадёжную. Использование одной из этих констант делает решение по умолчанию надёжнее.</p>
5 </ol><p>Почему последний пункт важен? Дело в том, что работая с путями, можно построить две<strong>рабочих</strong>системы: надёжную и ненадёжную. Использование одной из этих констант делает решение по умолчанию надёжнее.</p>
6 <h2>Содержание</h2>
6 <h2>Содержание</h2>
7 <ul><li><a>Сначала ломаем</a></li>
7 <ul><li><a>Сначала ломаем</a></li>
8 <li><a>Теперь строим</a></li>
8 <li><a>Теперь строим</a></li>
9 <li><a>Нужно также учесть</a></li>
9 <li><a>Нужно также учесть</a></li>
10 </ul><h2>Сначала ломаем</h2>
10 </ul><h2>Сначала ломаем</h2>
11 <p>Имеется следующая структура каталогов:</p>
11 <p>Имеется следующая структура каталогов:</p>
12 <p>Файл<em>index.test.js</em>содержит такой код:</p>
12 <p>Файл<em>index.test.js</em>содержит такой код:</p>
13 <p>Для начала тесты запускаются в корневом каталоге:</p>
13 <p>Для начала тесты запускаются в корневом каталоге:</p>
14 <p>Как видно, __dirname отсутствует в глобальной области видимости, самое простое решение - убрать её.</p>
14 <p>Как видно, __dirname отсутствует в глобальной области видимости, самое простое решение - убрать её.</p>
15 <p>Тесты всё ещё падают, но почему? Из текста ошибки так сразу и не скажешь, но теперь поиск файла фикстур происходит на один каталог выше, как будто из корня было набрано cat ../__fixtures__/expected_file.json:</p>
15 <p>Тесты всё ещё падают, но почему? Из текста ошибки так сразу и не скажешь, но теперь поиск файла фикстур происходит на один каталог выше, как будто из корня было набрано cat ../__fixtures__/expected_file.json:</p>
16 <p>Тесты пройдены, но надёжность системы нарушена, потому что этими действиями обеспечено недетерминированное поведение функции построения путей:</p>
16 <p>Тесты пройдены, но надёжность системы нарушена, потому что этими действиями обеспечено недетерминированное поведение функции построения путей:</p>
17 <p>Тесты снова упали, потому что теперь path.join собирает путь относительно места запуска.</p>
17 <p>Тесты снова упали, потому что теперь path.join собирает путь относительно места запуска.</p>
18 <p>Так вот, глобальная константа __filename содержит<strong>абсолютный</strong>путь к файлу, в котором она используется, а __dirname, соответственно, к каталогу. Зная, что необходимый файл лежит относительно текущего всегда на N каталогов выше/ниже, используя данные константы, можно обеспечить детерминированное поведение при запуске кода из любого каталога.</p>
18 <p>Так вот, глобальная константа __filename содержит<strong>абсолютный</strong>путь к файлу, в котором она используется, а __dirname, соответственно, к каталогу. Зная, что необходимый файл лежит относительно текущего всегда на N каталогов выше/ниже, используя данные константы, можно обеспечить детерминированное поведение при запуске кода из любого каталога.</p>
19 <h2>Теперь строим</h2>
19 <h2>Теперь строим</h2>
20 <p>Официальная документация Node.js<a>предлагает</a>, может быть, не самое красивое решение, но рабочее:</p>
20 <p>Официальная документация Node.js<a>предлагает</a>, может быть, не самое красивое решение, но рабочее:</p>
21 <p>Уже зная, как работают эти константы, остаётся вернуть первоначальное решение и опробовать его:</p>
21 <p>Уже зная, как работают эти константы, остаётся вернуть первоначальное решение и опробовать его:</p>
22 <p>Отличная работа! Теперь пути чётко описывают свой контекст, а код корректно отрабатывает, независимо от места запуска.</p>
22 <p>Отличная работа! Теперь пути чётко описывают свой контекст, а код корректно отрабатывает, независимо от места запуска.</p>
23 <h2>Нужно также учесть</h2>
23 <h2>Нужно также учесть</h2>
24 <h3>Отличие path.join и path.resolve</h3>
24 <h3>Отличие path.join и path.resolve</h3>
25 <p>При выборе между path.join и path.resolve нужно ориентироваться на ожидаемое поведение:</p>
25 <p>При выборе между path.join и path.resolve нужно ориентироваться на ожидаемое поведение:</p>
26 <p>- path.join('/a', '/b', 'c') // вернёт /a/b/c сборка происходит слева направо, абсолютный путь строится от первого аргумента с полным путём;</p>
26 <p>- path.join('/a', '/b', 'c') // вернёт /a/b/c сборка происходит слева направо, абсолютный путь строится от первого аргумента с полным путём;</p>
27 <p>- path.resolve('/a', '/b', 'c') // вернёт /b/c абсолютный путь строится от последнего аргумента с абсолютным путём, можно считать что сборка происходит справа налево.</p>
27 <p>- path.resolve('/a', '/b', 'c') // вернёт /b/c абсолютный путь строится от последнего аргумента с абсолютным путём, можно считать что сборка происходит справа налево.</p>
28 <h3>Тесты могут врать</h3>
28 <h3>Тесты могут врать</h3>
29 <p>Команда npm test под капотом игнорирует место запуска в рамках проекта и производит запуск от корня. То есть тесты всегда будут проходить вне зависимости от наличия __dirname. С учётом не самого информативного вывода тестов, при ошибке (если оно вообще когда-то вскроется) это позволяет получить<a>гейзенбаг</a>.</p>
29 <p>Команда npm test под капотом игнорирует место запуска в рамках проекта и производит запуск от корня. То есть тесты всегда будут проходить вне зависимости от наличия __dirname. С учётом не самого информативного вывода тестов, при ошибке (если оно вообще когда-то вскроется) это позволяет получить<a>гейзенбаг</a>.</p>
30 <h3>Настройки линтера</h3>
30 <h3>Настройки линтера</h3>
31 <p>При использовании eslint с правилами airbnb возникнет две ошибки: Parsing error: Unexpected token import и Unexpected dangling '_' in '__filename'.(no-underscore-dangle). Для их решения в .eslintrc на верхнем уровне достаточно добавить эти настройки:</p>
31 <p>При использовании eslint с правилами airbnb возникнет две ошибки: Parsing error: Unexpected token import и Unexpected dangling '_' in '__filename'.(no-underscore-dangle). Для их решения в .eslintrc на верхнем уровне достаточно добавить эти настройки:</p>
32 <p># Включает поддержку конструкции import.meta.url parserOptions: ecmaVersion: 2020 rules: no-underscore-dangle: [2, { "allow": ["__filename", "__dirname"] }] # разрешает подчёркивание в именах только для двух констант</p>
32 <p># Включает поддержку конструкции import.meta.url parserOptions: ecmaVersion: 2020 rules: no-underscore-dangle: [2, { "allow": ["__filename", "__dirname"] }] # разрешает подчёркивание в именах только для двух констант</p>