lenec ru

← все посты

Astro: Cannot use import statement outside a module — что не так с конфигом

11K

Эта ошибка вылетает чаще всего там, где её не ждёшь: при запуске astro.config.mjs, при попытке подключить какой-нибудь сторонний скрипт в <script> или при вызове Node-команды над файлом, где ты искренне думал, что это ESM.

Сообщение в консоли выглядит обычно так:

SyntaxError: Cannot use import statement outside a module

В моём опыте у этой ошибки три типичных причины. Разберу по очереди — и по каждой покажу, что именно чинить.

Причина 1: Node не считает файл ESM-модулем

Node работает в двух режимах — CommonJS (по умолчанию для .js) и ESM. ESM включается одним из трёх способов:

  • расширение .mjs;
  • "type": "module" в package.json;
  • загрузчик/раннер, который сам форсит ESM (например, tsx).

Astro изначально полагается на ESM. Если ты в проекте написал свой служебный скрипт scripts/seed.js с import внутри, и в package.json нет "type": "module", Node ругнётся именно так.

Самая быстрая починка — переименовать файл в .mjs:

mv scripts/seed.js scripts/seed.mjs
node scripts/seed.mjs

Если хочется оставить .js, добавляй в package.json:

{
  "name": "my-astro-site",
  "type": "module",
  "scripts": {
    "dev": "astro dev"
  }
}

После этого все .js в проекте автоматически считаются ESM. Если у тебя в репозитории есть старые скрипты на CommonJS, их придётся переименовать в .cjs или переписать.

Причина 2: TS-файл прокидывают в Node без раннера

У меня был кейс: коллега запускал node scripts/migrate.ts и получал ровно эту ошибку. Node не понимает TypeScript, ему всё равно, что в файле import { something } from '...'. Он видит синтаксис ESM, но файл по умолчанию обрабатывается как CommonJS, а сам TS-синтаксис в стандартном Node ещё и не выполняется.

Лечится через tsx:

pnpm add -D tsx
pnpm tsx scripts/migrate.ts

Или через --import с tsx:

node --import tsx scripts/migrate.ts

В Astro 5 это особенно заметно, когда пишешь миграции для Drizzle или сидеры. Я обычно держу отдельный scripts/db и запускаю всё через tsx, чтобы не возиться ни с tsc, ни с ts-node.

Причина 3: код для браузера отправили в Node

Менее очевидный сценарий, но я на него тоже наступал. Бывает, что кто-то по ошибке запускает клиентский JS-файл напрямую в Node — например, для отладки. В файле сверху import { Foo } from './foo.js', а Node видит обычный .js без type: module и валится.

Решение зависит от задачи:

  • если файл реально клиентский — не запускай его в Node, его задача — попасть в бандл и работать в браузере;
  • если хочется погонять логику локально — вынеси её в отдельный .mjs-скрипт и зови оттуда.

Где это часто всплывает в Astro

Кастомные интеграции и плагины

Когда пишешь свою интеграцию для Astro, файл интеграции — это обычно ESM. Если ты по привычке вынес её в integrations/my-thing.js и забыл про type: module, при сборке прилетит та же ошибка. Лечение — то же.

Скрипты в <script> на странице Astro

В Astro по умолчанию <script> бандлится и работает как модуль. Но если ты добавил is:inline, он вставляется как есть, и браузер по умолчанию считает такой скрипт классическим — не модулем. Тогда import внутри уронит код прямо в браузере с тем же текстом.

<script is:inline>
  import { foo } from '/some.js'; // упадёт
</script>

Для inline-скриптов либо не используй import, либо явно укажи type="module":

<script type="module" is:inline>
  import { foo } from '/some.js';
</script>

Быстрый чек-лист, по которому я прохожу, когда вижу эту ошибку

  • Какое расширение у файла? Если .js — есть ли "type": "module" в package.json?
  • Это TS-файл, который кто-то запустил в Node напрямую без tsx?
  • Это inline-скрипт в Astro без type="module"?
  • Это вообще не серверный файл, а кусок клиентского кода, который ушёл в Node по недоразумению?

Девять из десяти раз ответ находится на этих четырёх вопросах. Если нет — смотри стек-трейс целиком, там обычно видно конкретный .js-файл, на котором споткнулся парсер, и дальше уже идёшь по списку причин выше.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.