Как исправить ошибку 'Cannot use import statement outside a module'
Разбор причин ошибки cannot use import statement outside a module в Node.js, браузерах и Jest с правильной диагностикой конфликта систем модулей.
Вы пишете корректный оператор import, запускаете код и сразу же получаете ошибку:
SyntaxError: Cannot use import statement outside a module
Эта ошибка, связанная с ES-модулями JavaScript, является одним из наиболее распространённых источников путаницы для разработчиков, работающих с Node.js, браузерами и тестовыми окружениями. Решение не всегда одинаково — и применение неправильного подхода только усугубляет ситуацию. Вот как диагностировать вашу ситуацию и правильно её решить.
Ключевые моменты
- Эта ошибка возникает из-за несоответствия модульных систем, а не синтаксической ошибки — ваша среда выполнения ожидала CommonJS, но обнаружила синтаксис ES-модулей
import. - В Node.js установите
"type": "module"вpackage.jsonили используйте расширение файла.mjs, чтобы включить ESM. - В браузерах добавьте
type="module"к тегу<script>, чтобы движок обрабатывал файл как ES-модуль. - В Jest настройте Babel для преобразования ESM в CommonJS и скорректируйте
transformIgnorePatternsдля сторонних пакетов, использующих только ESM. - Всегда сначала диагностируйте своё окружение — Node.js, браузер или test runner — прежде чем применять исправление.
Почему возникает эта ошибка
Ошибка означает, что среда выполнения обнаружила синтаксис ES-модулей (import), но ожидала что-то другое — обычно CommonJS (require). Это несоответствие модульных систем, а не синтаксическая ошибка в вашем коде.
В JavaScript существует две модульные системы:
| Система | Синтаксис | По умолчанию в |
|---|---|---|
| CommonJS (CJS) | require() | Node.js (устаревший) |
| ES Modules (ESM) | import | Браузеры, современный Node.js |
Среда выполнения решает, какую систему использовать, на основе конфигурации — а не самого кода. Поэтому один и тот же оператор import работает в одном проекте и не работает в другом.
Как исправить ошибку Import Statement Outside Module в Node.js
Node.js обрабатывает файлы .js как CommonJS, если только в package.json не установлено "type": "module". Чтобы использовать import, необходимо явно включить ESM.
Вариант 1: Установите "type": "module" в package.json
{
"name": "my-app",
"version": "1.0.0",
"type": "module"
}
Это указывает Node.js обрабатывать все файлы .js в проекте как ES-модули. См. официальную документацию Node.js по ES-модулям для получения подробной информации о том, как Node определяет тип модуля.
Вариант 2: Используйте расширение файла .mjs
Переименуйте файл из app.js в app.mjs. Node.js всегда обрабатывает файлы .mjs как ESM, независимо от package.json.
Вариант 3: Используйте .cjs для файлов CommonJS
Если ваш проект использует "type": "module", но одному файлу нужен require(), присвойте ему расширение .cjs.
Важно: Не добавляйте просто
"type": "module"без проверки остальных файлов. Любой файл, использующийrequire(),module.exportsили__dirname, перестанет работать при использовании ESM.
Исправление ошибки в JavaScript для браузера
В браузерах статический import работает только внутри модульного скрипта. Если ваш тег <script> не объявляет type="module", браузер обрабатывает его как классический скрипт и отклоняет синтаксис import.
<!-- Это вызовет ошибку -->
<script src="app.js"></script>
<!-- Это работает -->
<script type="module" src="app.js"></script>
Это поведение является частью стандартной спецификации JavaScript-модулей, реализованной современными браузерами.
Обратите внимание, что модульные скрипты изолированы — переменные, объявленные внутри одного модуля, недоступны глобально. Поэтому после добавления type="module" вы можете увидеть ReferenceError для переменной, которая, как вы ожидали, должна быть глобальной. Решение — явно экспортировать и импортировать значения, а не полагаться на глобальную область видимости.
Discover how at OpenReplay.com.
Исправление ошибки в Jest и других тестовых окружениях
Jest работает в Node.js и традиционно использует CommonJS по умолчанию, даже если ваш исходный код использует ESM. Это распространённый источник ошибки “cannot use import statement outside a module” в тестовых наборах.
Один из подходов — настроить Babel для преобразования ESM в CommonJS во время тестирования:
// babel.config.js
module.exports = {
presets: ['@babel/preset-env'],
}
В качестве альтернативы Jest можно настроить на запуск тестов в нативном режиме ESM, если ваш проект уже использует ES-модули. См. документацию Jest по ECMAScript Modules для получения подробной информации.
Если сторонний пакет (например, swiper, lodash-es или подобные библиотеки, использующие только ESM) вызывает ошибку, необходимо указать Jest преобразовывать его, а не игнорировать:
// jest.config.js
module.exports = {
transformIgnorePatterns: [
'/node_modules/(?!(swiper|ssr-window|dom7)/)',
],
}
Ключевой момент: transformIgnorePatterns использует негативный просмотр вперёд. Вы говорите: “игнорировать всё в node_modules, кроме этих пакетов”. Перечисление их в отдельных элементах массива не сработает — они должны быть объединены в одном регулярном выражении с помощью |.
Конфигурация TypeScript
Если вы используете TypeScript, проверьте ваш tsconfig.json. Поле module контролирует, какой синтаксис модулей TypeScript генерирует в скомпилированном выводе:
{
"compilerOptions": {
"module": "ESNext",
"target": "ESNext"
}
}
Установка module в CommonJS при написании операторов import является валидным TypeScript — компилятор преобразует import в require() в выводе. Однако установка module в ESNext при запуске вывода в окружении CommonJS Node.js (без "type": "module" в package.json) вызовет ошибку во время выполнения. Убедитесь, что настройка модуля в вашем tsconfig.json соответствует тому, что фактически ожидает ваша среда выполнения.
Диагностируйте, прежде чем исправлять
Одна и та же ошибка появляется в разных окружениях по разным причинам. Прежде чем применять какое-либо исправление, спросите себя:
- Где возникает ошибка — в Node.js, браузере или test runner?
- Что указано в поле
"type"вашегоpackage.json? - Вы используете сборщик или запускаете файлы напрямую?
Ответы на эти три вопроса каждый раз укажут вам на правильное решение.
Часто задаваемые вопросы
Можно ли смешивать CommonJS и ES-модули в одном проекте Node.js?
Да, но вам нужно использовать расширения файлов, чтобы различать их. Файлы с расширением .mjs всегда обрабатываются как ES-модули, а файлы с расширением .cjs всегда обрабатываются как CommonJS, независимо от поля type в package.json. Это позволяет использовать обе системы одновременно в одном проекте.
Влияет ли добавление type module в package.json на мои зависимости в node_modules?
Нет. Каждый пакет в node_modules имеет свой собственный package.json, поэтому ваше поле type применяется только к файлам вашего проекта. Зависимости определяют свою собственную модульную систему независимо. Ошибка обычно возникает из ваших собственных файлов или при импорте зависимости, использующей только ESM, в контексте CommonJS без надлежащего преобразования.
Почему мой код работает со сборщиком вроде Webpack, но не работает без него?
Сборщики, такие как Webpack и Vite, обрабатывают операторы import на этапе сборки и создают единый выходной файл. Они разрешают синтаксис модулей до того, как среда выполнения его увидит. Когда вы запускаете файлы напрямую в Node.js или браузере без сборщика, сама среда выполнения должна понимать формат модуля, и именно здесь несоответствия вызывают эту ошибку.
Как использовать __dirname и __filename в ES-модуле?
Эти глобальные переменные CommonJS недоступны в ES-модулях. Вместо этого вы можете воссоздать их, используя import.meta.url. Например, используйте new URL(import.meta.url).pathname для получения пути к файлу или комбинируйте его с модулем path и fileURLToPath из модуля url, чтобы воспроизвести поведение __dirname.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k