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>