12k
All articles

Как исправить ошибку 'Cannot use import statement outside a module'

Разбор причин ошибки cannot use import statement outside a module в Node.js, браузерах и Jest с правильной диагностикой конфликта систем модулей.

OpenReplay Team
OpenReplay Team
Как исправить ошибку 'Cannot use import statement outside a module'

Вы пишете корректный оператор 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 для переменной, которая, как вы ожидали, должна быть глобальной. Решение — явно экспортировать и импортировать значения, а не полагаться на глобальную область видимости.

Исправление ошибки в 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.

Open-source session replay

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

We use cookies to improve your experience. By using our site, you accept cookies.