После обновления зависимости сервис Node.js перестал запускаться, а в журнале появилась ERR_REQUIRE_ESM. Сначала нужно установить, какой файл загружает пакет и какой Node.js действительно запускает этот процесс. Слепая замена require на import или откат версии пакета может перенести ошибку в другой сценарий.
Три безопасных первых действия:
- Зафиксируйте код ошибки, время и первый относящийся к приложению файл в стеке вызовов. Для передачи уберите из примера данные клиентов, секреты и внутренние адреса.
- Сравните версии Node.js и зависимости до и после релиза, сохраните файл фиксации зависимостей — например,
package-lock.json. - Воспроизведите запуск на изолированной копии с той же сборкой, без реальных платежей, рассылок и записи в рабочую базу.
Если из-за сбоя остановился приём заявок или обмен с CRM, заранее определите допустимый способ восстановления последней рабочей сборки. Не проверяйте очередную гипотезу прямо на рабочем сервисе.
Почему название ошибки ещё не определяет исправление
В Node.js есть две системы модулей: CommonJS с require() и ECMAScript modules, или ESM, с import и export. Ошибка указывает на границу их загрузки; причина зависит от версии среды и цепочки вызовов.
Поддержка синхронного require(ESM) включена по умолчанию начиная с Node.js 22.12.0 в ветке 22 и 20.19.0 в ветке 20. Это исторические границы поведения, а не рекомендация устанавливать именно эти выпуски. В актуальной документации ERR_REQUIRE_ESM отмечена как устаревшая ошибка.
Есть важное ограничение: если модуль или его импортируемые зависимости используют await на верхнем уровне, синхронный require() не подходит. В поддерживающей его среде ожидается ERR_REQUIRE_ASYNC_MODULE. При таком сценарии рассматривают асинхронную загрузку и порядок запуска приложения.
Поэтому запись «у нас Node.js 22» недостаточна. Уточните полную версию работающего процесса: терминал разработчика, сборка, сервер и фоновый обработчик могут использовать разные исполняемые файлы. Основа этих различий — официальная документация Node.js, разделы Errors и Modules: CommonJS modules.
Составьте карту границы модулей
Попросите разработчика заполнить короткую карту для файла, указанного в ошибке. Она должна описывать фактическую сборку, которая упала, а не только исходный TypeScript.
- Кто вызывает: файл и строка с
require(), статическимimportили динамическимimport(); приложение это, тестовый инструмент или вложенная зависимость. - Что загружается: точная версия пакета и выбранная точка входа. Поле
exportsвpackage.jsonможет направлятьrequireиimportв разные файлы; учитывайте порядок и условия этой карты. - Как определяется формат:
.mjsобозначает ESM,.cjs— CommonJS. Для.jsпроверьте ближайшийpackage.jsonи его полеtype. Отсутствие поля не позволяет во всех версиях считать файл CommonJS: существует определение по синтаксису. - Что делает сборка: осталось ли
import()в итоговом JavaScript или инструмент преобразовал его вrequire(). - Что запускает файл: полная версия Node.js, команда запуска и относящиеся к загрузке модулей флаги. Не копируйте весь набор переменных окружения в отчёт: там могут быть секреты.
Эту карту удобно сравнить для успешного и неуспешного запуска. Например, одна и та же версия пакета при разных точках входа — другой сценарий, чем замена самого пакета. Правила type и условных exports описаны в документации Node.js Modules: Packages.
Отделите поведение Node.js от поведения проекта
Ниже учебный пример, который не обращается к сети и не использует данные проекта. Он проверяет загрузчик Node.js, но не доказывает исправность реальной зависимости.
В отдельной папке на тестовой копии создайте probe-leaf.mjs:
export const marker = 'esm-ok';
Рядом создайте probe.cjs:
console.log('Node:', process.version);
try {
console.log('require:', require('./probe-leaf.mjs').marker);
} catch (error) {
console.log('require:', error.code || error.name);
}
import('./probe-leaf.mjs')
.then(module => console.log('import:', module.marker))
.catch(error => {
console.error('import:', error.code || error.name);
process.exitCode = 1;
});
Запустите его тем же исполняемым файлом Node.js, которым запускается проблемный процесс:
node probe.cjs
В среде с включённым require(ESM) для этого синхронного модуля обе строки загрузки должны содержать esm-ok. Если через require получается ошибка, а через import — результат, проверьте версию и флаги. Если пример работает, а приложение падает, переходите к его реальной точке входа, сборщику и зависимости.
Далее разработчик сокращает сбой приложения до одного импорта проблемного пакета на изолированной копии. В отличие от учебного модуля, реальный пакет может выполнять действия уже при загрузке: ему нельзя давать рабочие доступы. Динамический import() возвращает Promise — результат асинхронной операции; нельзя передавать его вместо уже загруженного объекта.
Выберите правку по подтверждённой причине
Пакет стал ESM, а вызывающий код остался CommonJS. Проверьте документированный способ подключения новой версии. Иногда достаточно локального асинхронного адаптера: дождаться загрузки и только затем принимать запросы. При этом нужно сверить экспортируемые имена и default, а не механически заменить одну строку.
Локально работает, на сервере падает. Сравните среду именно того процесса, который дал ошибку, и повторите запуск собранного приложения. Обновление Node.js допустимо после проверки требований проекта и поддерживаемой ветки, с планом возврата; установка самого нового выпуска сама по себе не является исправлением.
**После сборки появляется require().** Проверьте настройки формата выходного кода и совместимость инструментов. Успешный запуск исходников не подтверждает работоспособность файла, который уезжает на сервер.
Изменилась точка входа или вложенная зависимость. Найдите вызывающий пакет по стеку и сопоставьте его требования с фактически установленной версией. Не обходите exports импортом случайного внутреннего файла: это связывает приложение с недокументированной структурой пакета.
Не добавляйте
"type": "module"ко всему проекту ради одной ошибки и не редактируйте пакет вnode_modulesвручную. Такая правка может изменить запуск остальных файлов или исчезнуть при следующей установке.
Временный возврат к предыдущей сборке имеет смысл только как согласованная мера восстановления с известными ограничениями. Слепой откат зависимости без проверки совместимости и безопасности не заменяет диагностику.
Как принять исправление
Критерий «ошибка исчезла из журнала» слишком слабый: нужная ветка кода могла просто не выполниться. До выпуска согласуйте конкретный сценарий, который перестал работать.
- На исходной тестовой сборке сбой воспроизводится, на исправленной тот же сценарий проходит.
- Сборка повторяется с зафиксированными зависимостями; запуск проверен в целевой версии Node.js.
- Пройдены холодный старт, повторный запуск и действие, которое впервые загружает пакет.
- Проверены отдельные фоновые обработчики и соседние сценарии, использующие тот же модуль.
- После выпуска наблюдают ошибки запуска и результат критичной операции; определены ответственный и условия возврата.
В учебном сценарии с обменом с CRM приёмка включает ожидаемое изменение тестовой записи и отсутствие повторного побочного действия.
Когда достаточно своей команды, а когда нужна помощь
Если разработчик воспроизвёл сбой, объяснил выбор формата и подтвердил исправление на копии, отдельный подрядчик может не понадобиться. Владельцу проекта достаточно принять результат по согласованному сценарию.
Помощь полезна, когда ошибка проходит через несколько пакетов, сборку и фоновые процессы, а безопасного способа проверки нет. В рамках работ с Node.js-сервисами OpenStart разбирает код и зависимости, проверяет окружение и помогает определить первый этап исправления.
Для первичного разбора достаточно симптома, времени появления, полной версии Node.js, версии зависимости и обезличенного фрагмента ошибки. Пароли, ключи и полные журналы через публичную форму передавать не нужно. По этим вводным команда уточнит критичный сценарий и объём диагностики; итоговая оценка зависит от устройства проекта.