ReferenceError: require is not defined означает, что среда выполнила выражение require(...), но в текущей области видимости нет имени require. До поиска пакета или файла дело ещё не дошло. Сначала определите, кто выполнил проблемный файл: браузер, Node.js в режиме ESM, сборщик либо тестовый раннер.
require не входит в язык JavaScript и не служит браузерным глобальным объектом. В Node.js это локальная привязка, которую загрузчик CommonJS передаёт модулю вместе с module, exports, __filename и __dirname. У ECMAScript-модуля такой оболочки нет, поэтому тот же вызов завершается ReferenceError.
Формат модуля определяется исполнителем файла
Найдите среду по стеку, а не по папке проекта
Откройте верхний кадр стека, который относится к вашему коду. URL вида https://…/app.js указывает на браузер. Путь к исходному .mjs или .js рядом с package.json обычно ведёт к Node.js. Путь внутри конфигурации, плагина, SSR-обработчика или тестового setup-файла означает, что код мог выполнить Node.js, даже если весь продукт называют фронтендом.
Затем найдите сам вызов require. Важно отличить четыре случая:
- вызов написан в вашем исходнике;
- его содержит зависимость;
- его оставил скомпилированный файл в
dist; - браузер загрузил файл из
public, CDN или тега<script>, минуя сборщик.
Если имя require существует, но загрузчик не находит цель, ошибка будет другой: MODULE_NOT_FOUND или ERR_MODULE_NOT_FOUND. Их причины разобраны в статье «Cannot find module в Node.js». Если смешались window, document и серверный код, сначала проверьте среду по статье «Браузерные глобальные объекты не определены».
В браузере подключите нативный модуль или соберите зависимость
Обычный браузерный <script> не получает require. Для собственного кода используйте ECMAScript modules: объявите точку входа модулем и импортируйте файл по URL.
<script type="module" src="/js/app.js"></script>
// /js/app.js
import { formatPrice } from './format-price.js'
console.log(formatPrice(1200))
В браузере относительный спецификатор разрешается как URL. Указывайте ./, ../ или абсолютный URL и обычно полное расширение файла. Имя пакета вроде lodash браузер сам по node_modules не ищет: нужен import map либо сборщик, который преобразует граф зависимостей в браузерные ресурсы.
Не копируйте произвольный пакет из node_modules в public. Пакет может обращаться к node:fs, process, нативному дополнению или другим API Node.js. Выберите документированный браузерный entry point пакета или браузерную альтернативу. Если пакет предназначен только для Node.js, замена require на import не сделает его браузерным.
Динамический импорт подходит, когда модуль нужен по условию или после действия пользователя:
export async function openEditor() {
const { mountEditor } = await import('./editor.js')
return mountEditor(document.querySelector('[data-editor]'))
}
import() возвращает Promise; это не синхронная подмена require. Для обычной зависимости в начале модуля статический import короче и понятнее.
В Node.js ESM замените require на import
Node.js выполняет файл как ESM, если он имеет расширение .mjs либо расширение .js под ближайшим package.json с "type": "module". В таком файле используйте статический import:
import { readFile } from 'node:fs/promises'
import config from './config.js'
const source = await readFile(config.input, 'utf8')
ESM умеет импортировать большинство CommonJS-пакетов. Значение module.exports доступно как default export, поэтому для старого CommonJS-модуля надёжная форма выглядит так:
import legacyParser from './legacy-parser.cjs'
const result = legacyParser.parse('source')
Именованные экспорты CommonJS Node.js определяет статическим анализом, и они доступны не для каждого пакета. Если import { parse } не работает, сверьтесь с документацией пакета и проверьте default export, а не возвращайте require автоматически.
Когда имя модуля вычисляется во время выполнения или загрузка нужна только в одной ветке, используйте import():
export async function loadFormatter(locale) {
const module = await import(`./formatters/${locale}.js`)
return module.default
}
Здесь вызывающий код тоже должен дождаться Promise. Если путь известен заранее и модуль нужен всегда, оставьте статический импорт.
createRequire нужен только на границе с CommonJS
Иногда ESM-файл обязан вызвать настоящий CommonJS-загрузчик: например, старый плагин публикуется только как CommonJS или API требует require.resolve. Node.js позволяет создать локальный require с базой относительно текущего модуля:
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const legacyPlugin = require('./legacy-plugin.cjs')
Это средство совместимости Node.js, а не универсальное исправление. node:module недоступен в обычном браузере. Не применяйте createRequire ко всем импортам ESM-файла: нативный import лучше показывает зависимости и не смешивает два загрузчика без причины.
Если файл должен остаться CommonJS, обозначьте это явно
Для старого скрипта, конфигурации или интеграции иногда дешевле сохранить CommonJS. Переименуйте только этот файл в .cjs и оставьте его синтаксис согласованным:
// build-config.cjs
const legacyPlugin = require('legacy-plugin')
module.exports = {
plugins: [legacyPlugin()],
}
Не меняйте "type" всего пакета ради одного файла. Это переключит интерпретацию всех подходящих .js внутри границы пакета и может сломать соседние import, module.exports, __dirname или инструменты.
Правила Node.js удобно свести к таблице:
| Файл | Как Node.js его трактует |
|---|---|
entry.mjs | ESM независимо от поля type |
entry.cjs | CommonJS независимо от поля type |
entry.js рядом с "type": "module" | ESM |
entry.js рядом с "type": "commonjs" | CommonJS |
Для .js действует ближайший родительский package.json, а не обязательно корневой файл репозитория. Вложенный пакет может открыть новую границу. Если type отсутствует, поведение неоднозначного .js зависит от синтаксиса и версии Node.js; явное поле type, .mjs или .cjs убирает эту неопределённость.
В собранном фронтенде проверьте, прошёл ли файл через сборщик
Сборщик может преобразовать модульный граф, но это не делает require глобальным во всех браузерных скриптах. Файл из public, внешний CDN-скрипт, строка для eval или модуль, подключённый отдельным тегом, часто выполняется без такого преобразования.
Если ошибка пришла из исходника приложения, замените статический require на import и убедитесь, что зависимость имеет браузерную сборку. Если ошибка внутри готового пакета, проверьте его browser/ESM entry point и условия exports. Если стек указывает на сгенерированный bundle, найдите исходный модуль по source map или строке рядом с ошибкой; не редактируйте bundle вручную.
Особенно подозрительны динамические вызовы:
const adapter = require('./adapters/' + name)
Сборщик не обязан уметь перечислить все возможные цели такой строки. Сделайте набор импортов явным или используйте документированный механизм динамического импорта своего сборщика. Не добавляйте глобальную функцию require: она скроет исходную ошибку, но не реализует разрешение пакетов, кеш и семантику CommonJS.
У конфигураций и тестов своя граница модулей
Конфигурацию сборщика, тестового раннера или линтера обычно выполняет Node.js. Формат клиентского приложения на неё автоматически не распространяется. Например, приложение может собираться как ESM, а tool.config.cjs оставаться CommonJS; обратная комбинация тоже возможна, если инструмент её поддерживает.
Проверяйте именно файл из стека:
- Его расширение:
.mjs,.cjsили.js. - Ближайший
package.jsonи полеtype. - Документацию инструмента: импортирует ли он конфигурацию как ESM, загружает ли как CommonJS или сначала преобразует.
- Формат setup-файла и зависимости, в которой находится
require.
Выбор DOM-среды теста не создаёт CommonJS-привязку. Эмулятор браузера отвечает за window и document, а формат модуля определяет загрузчик. Поэтому переключение теста на DOM ради require is not defined обычно чинит не ту границу.
Что проверить перед правкой
Короткая диагностика предотвращает случайное переключение всего проекта:
- Найдите первый принадлежащий проекту кадр стека и фактический файл с
require. - Определите исполнителя: браузер, Node.js, сборщик, SSR или тестовый раннер.
- Для Node.js установите формат точного файла по расширению и ближайшему
package.json. - Для браузера выясните, прошёл ли файл через сборщик и поддерживает ли пакет браузерную среду.
- Выберите одну границу:
import, асинхронныйimport(), локальный.cjsилиcreateRequire()для необходимой совместимости.
Не устанавливайте пакет с именем require, не присваивайте window.require = ... и не меняйте type наугад. Ошибка сообщает не об отсутствующей библиотеке, а о несовпадении формата модуля или среды выполнения. Исправление должно сделать эту границу явной.