Ошибка TypeScript TS1384 из тех, что выглядят загадочно при первой встрече, но всегда имеют точную и устранимую причину. Она возникает, когда компилятор находит модификатор export там, где тот не может законно находиться, а именно внутри блока расширения модуля. Это руководство объясняет, что именно означает TS1384, разбирает все сценарии, которые его вызывают, и даёт правильное исправление для каждого.
Что такое TS1384?
Ошибка TypeScript TS1384 содержит сообщение: «модификатор "export" нельзя применить к расширению модуля». Она возникает, когда компилятор встречает ключевое слово `export` внутри блока `declare module` или `declare global` — двух конструкций, которые TypeScript использует для расширения модулей. Блоки расширения предназначены для дополнения типов существующих модулей, а не для объявления новых публичных символов, поэтому `export` внутри них структурно недопустим.
Расширение модуля в одном предложении
Расширение модуля — это механизм TypeScript, позволяющий добавлять новые члены к типам существующего модуля: например, добавить собственное свойство в `Express.Request`, расширить `Window` сторонним глобальным объектом или добавить методы в опции компонента фреймворка. Синтаксис похож на блок `declare module 'имя-модуля' {}` и должен находиться в файле-модуле (файле хотя бы с одной инструкцией `import` или `export` верхнего уровня).
- TS1384 — ошибка компилятора: файл не пройдёт проверку типов, пока её не исправят
- Она не влияет на выполнение: ошибка касается исключительно объявлений типов
- Она детерминирована: один и тот же код всегда её вызывает, промежуточных вариантов нет
- Причин мало: контекст скрипта против модуля, isolatedModules и неверная структура .d.ts покрывают 95 % случаев
Note
Когда возникает TS1384
Ошибка всегда связана с `export` там, где TypeScript его не разрешает. Есть три разных шаблона, которые её вызывают; определить, какой относится к вашей кодовой базе, — значит выбрать правильное исправление.
Шаблон 1 — export внутри блока declare module
Самый прямой триггер: вы пишете инструкцию `export` внутри расширения `declare module`. Обычно цель — добавить что-то в публичный API модуля, но блоки расширения так не работают: они могут только дополнять объявления типов, уже существующие в целевом модуле.
// ✗ TS1384 - export внутри расширения модуля
declare module 'some-library' {
export interface NewInterface { // <-- здесь возникает TS1384
id: string;
}
}
// ✓ Правильно - интерфейс добавлен без export
declare module 'some-library' {
interface ExistingInterface {
newProperty: string; // дополняет существующий тип
}
}Шаблон 2 — файл считается скриптом, а не модулем
Это самая частая причина TS1384 в реальных проектах. Если в файле нет инструкций `import` или `export` верхнего уровня, TypeScript считает его скриптом с глобальной областью. Блок `declare global {}` в файле-скрипте бессмысленен (в скриптах всё и так глобально), поэтому TypeScript отклоняет любой `export` внутри него с TS1384. Чтобы контекст расширения имел смысл, файл должен быть модулем.
Шаблон 3 — неверная структура файла .d.ts
Файлы объявлений (`.d.ts`), которые смешивают объявления внешних модулей с обычными инструкциями `export` в неверном порядке, могут вызывать TS1384. Файл `.d.ts`, начинающийся с объявлений `export` верхнего уровня и затем содержащий блок `declare module`, считается модулем — это правильно. Но `.d.ts`, который оборачивает всё в единственный блок `declare module` и затем пытается использовать `export` внутри него, путает контекст расширения с контекстом определения модуля.
Warning
Как исправить TS1384: расширение модуля
Правильное исправление зависит от того, чего вы на самом деле хотите добиться. К TS1384 ведут две разные цели, и они требуют разных подходов. Пройдите эти четыре шага, чтобы аккуратно устранить ошибку.
Определите, какой шаблон вызывает TS1384
Внимательно прочитайте полное сообщение компилятора. Обратите внимание на расширение файла (.ts или .d.ts), номер строки и на то, находится ли `export` в блоке `declare module`, блоке `declare global` или где-то ещё. Окружающий код подскажет, какой из трёх шаблонов выше применим. Если путь в ошибке начинается с `node_modules/`, сразу переходите к исправлению с `skipLibCheck`: остальные шаблоны там не действуют.
Добавьте export {}, чтобы превратить файл в модуль
Если в вашем файле есть блок `declare module` или `declare global`, но нет импортов и экспортов верхнего уровня, добавьте `export {}` в начало. Одна эта строка переводит файл из контекста скрипта в контекст модуля — обязательную среду для синтаксиса расширения. Пустой экспорт ничего не добавляет в скомпилированный результат: это исключительно сигнал контекста для TypeScript.
// Добавьте эту строку, чтобы файл стал модулем
export {};
declare global {
interface Window {
myAnalytics: AnalyticsInstance;
}
}Перенесите declare global в существующий модуль
Альтернатива добавлению `export {}` — поместить блок `declare global` в файл, где уже есть настоящие импорты или экспорты: точку входа библиотеки, функциональный модуль или файл общих утилит. Такой подход оставляет расширения типов рядом с кодом, который они дополняют, и это бывает проще поддерживать, чем отдельный файл глобальных объявлений.
Уберите export из блока расширения
Если вы действительно хотите добавить новый экспортируемый тип в публичный API существующего модуля, блок расширения — неподходящее место. Полностью вынесите объявление за пределы блока `declare module`. Чтобы дополнить существующий интерфейс, используйте то же имя интерфейса без `export` внутри блока: TypeScript объединит их автоматически через слияние объявлений.
Форматтер и валидатор JSON
Проверяйте tsconfig.json и package.json на ошибки синтаксиса мгновенно в браузере — компилятор TypeScript не нужен.
TS1384 и isolatedModules
Опция компилятора `isolatedModules` включена по умолчанию в Vite, Next.js, Create React App с Babel и в любом проекте на esbuild или SWC. Она требует, чтобы каждый файл преобразовывался независимо, без информации о типах из других файлов, а это добавляет ограничения, усиливающие ряд ошибок TypeScript, включая TS1384.
Что ограничивает isolatedModules
| Конструкция | Без isolatedModules | С isolatedModules |
|---|---|---|
| `const enum` | ✓ Разрешён везде | ✗ Только в файлах .d.ts |
| `export type` | ✓ Необязателен | ✓ Обязателен для реэкспорта типов |
| `import type` | ✓ Необязателен | ✓ Обязателен для импорта типов |
| Внешние объявления | ✓ В любом файле .ts | ✓ Лучше в файлах .d.ts |
| Расширение модуля | ✓ В файлах-модулях .ts | ✓ Предпочтительно в .d.ts |
| Реэкспорт пространств имён | ✓ Разрешён | ✗ Ограничен |
Исправление для isolatedModules
Когда `isolatedModules` включён и TS1384 появляется на расширении в обычном файле `.ts`, самый чистый вариант — перенести расширение в файл `.d.ts`. Файлы объявлений никогда не преобразуются esbuild или Babel: их читает только компилятор TypeScript. Это полностью снимает ограничение isolatedModules для такого расширения.
// Этот шаблон корректно работает с isolatedModules
export {};
declare module 'express' {
interface Request {
userId?: string;
tenantId?: string;
}
}Tip
TS1384 в файлах .d.ts
Файлы объявлений добавляют собственные нюансы в TS1384. Правила того, что допустимо в файле `.d.ts`, немного отличаются от обычных `.ts`, и шаблоны, порождающие TS1384 в файлах объявлений, часто менее очевидны.
Определение внешнего модуля или расширение?
Файл `.d.ts` может содержать две принципиально разные вещи, которые выглядят похоже, но ведут себя по-разному. Определение внешнего модуля (`declare module 'имя' {}` в `.d.ts` в контексте скрипта, без импортов и экспортов) описывает все типы модуля с нуля — так типизируют библиотеки JavaScript без типов. Расширение модуля (`declare module 'имя' {}` в `.d.ts` в контексте модуля с `export {}`) дополняет типы существующего модуля. Различие важно, потому что `export` допустим в определении внешнего модуля, но даёт TS1384 в расширении модуля.
Как выбрать правильный шаблон .d.ts
Если ваш файл `.d.ts` описывает типы нетипизированной библиотеки (например, типизирует старый плагин jQuery), оставьте его в контексте скрипта без `export {}` в начале. Используйте `export` внутри блока `declare module` свободно. Если файл `.d.ts` дополняет уже типизированную библиотеку (например, добавляет свойство в `Express.Request`), добавьте `export {}` в начало и уберите любой `export` из блока `declare module`.
Note
skipLibCheck как крайняя мера
Когда TS1384 возникает на пути внутри `node_modules` и вы не контролируете пакет, добавьте `"skipLibCheck": true` в tsconfig.json. Это указывает TypeScript пропустить проверку типов всех файлов `.d.ts`, включая лежащие в `node_modules`. Это законная и широко используемая опция конфигурации, а не хак. Плата за неё — полная потеря проверки типов библиотечных объявлений, поэтому действительно сломанные типы в зависимостях останутся незамеченными. Применяйте `skipLibCheck`, только когда альтернатива — заблокировать сборку.
TS1384 во фреймворках и сборщиках
Каждый фреймворк и инструмент сборки настраивает TypeScript по-своему, поэтому TS1384 может возникать по чуть разным причинам в зависимости от вашего стека. Вот как самые распространённые окружения порождают и устраняют эту ошибку.
Next.js
Next.js автоматически включает `isolatedModules` через стандартный tsconfig и использует SWC для преобразования. Стандартный шаблон расширения типов в Next.js — отдельный каталог `types/` в корне проекта с файлами `.d.ts`, начинающимися с `export {}`. Next.js также создаёт файл `next-env.d.ts`: никогда не редактируйте его вручную, потому что он пересоздаётся при каждой сборке и изменения потеряются. Размещайте расширения в отдельном файле.
Vite
Проекты Vite используют esbuild для преобразования и включают `isolatedModules: true` в стандартном tsconfig. Vite также создаёт файл `vite-env.d.ts` для своих глобальных объектов. Добавляйте расширения модулей в отдельный каталог `src/types/`. Приём с `export {}` устраняет TS1384 во всех стандартных конфигурациях Vite, а хранение расширений в файлах `.d.ts` — самый надёжный подход, когда в цепочке есть преобразование esbuild.
Простые API на Node.js / Express
В проектах на Express часто расширяют `Express.Request`, чтобы добавить сессию пользователя или контекст аутентификации. Канонический шаблон — файл `src/types/express/index.d.ts` с `export {}` в начале и расширением `declare module 'express-serve-static-core'` ниже. Без `isolatedModules` это работает и в обычном `.ts`-файле, но использование `.d.ts` в любом случае чище по конвенции. Всегда проверяйте точное имя модуля в определениях типов Express: цель расширения — `express-serve-static-core`, а не `express`.
Файл должен быть модулем, прежде чем он сможет расширить другой модуль. Одно `export {}` превращает скрипт в модуль — и открывает всю систему расширений.
Как избежать TS1384 в будущем
TS1384 легко внести случайно, особенно когда новые участники команды добавляют расширения типов или когда вы переносите проект на новый сборщик. Эти практики не дают ошибке вернуться после исправления.
Введите конвенцию для каталога типов
Создайте каталог `src/types/` (или `types/`) и держите там все расширения модулей в виде файлов `.d.ts`. Каждый файл должен содержать одно расширение и начинаться с `export {}`. Опишите эту конвенцию в `CONTRIBUTING.md` проекта, чтобы новые участники знали, где размещать объявления типов. Единое место также упрощает аудит расширений при обновлении зависимостей.
Используйте tsc --noEmit в CI
Запуск `tsc --noEmit` как шага CI ловит TS1384 (и любую другую ошибку TypeScript) до того, как код попадёт в main. Многие проекты пропускают проверку типов в CI, потому что сборщик её не требует: esbuild и SWC удаляют типы, не проверяя их. Добавьте `tsc --noEmit` отдельным шагом задания, чтобы ошибки типов, включая TS1384, выявлялись в каждом пул-реквесте.
Сравнивайте изменения tsconfig через просмотр различий
Изменения в tsconfig.json — добавление `isolatedModules`, смена `moduleResolution` или обновление `lib` — могут внести TS1384 в файлы, которые раньше компилировались чисто. Когда приходит изменение tsconfig, используйте просмотр различий, чтобы сравнить старую и новую конфигурацию рядом. Так сразу видно, какие опции изменились, и можно проследить новые ошибки TS1384 до конкретной настройки, которая их вызвала.
Tip
Просмотр различий
Сравнивайте две версии tsconfig.json, package.json или любого текстового файла рядом, чтобы точно увидеть, что изменилось — в браузере и без загрузки файлов.
Key takeaways
- TS1384 возникает, когда модификатор `export` оказывается внутри блока расширения `declare module` или `declare global`; исправление почти всегда укладывается в одну строку.
- Самая частая причина: в файле нет импортов и экспортов, поэтому TypeScript считает его скриптом. Добавьте `export {}` в начало, чтобы превратить его в модуль.
- При `isolatedModules: true` (проекты на Vite, Next.js, esbuild) переносите расширения в файлы `.d.ts`: они исключены из преобразования и полностью обходят ограничение.
- Если TS1384 указывает на путь внутри `node_modules`, исправление — `skipLibCheck: true` в tsconfig.json: файлы объявлений зависимостей править нельзя.
- Определения внешних модулей (типизация библиотек без типов) и расширения модулей (дополнение типизированных библиотек) похожи, но ведут себя по-разному: `export` внутри блока допустим только в определениях, но не в расширениях.
- Добавьте `tsc --noEmit` в конвейер CI, чтобы ловить TS1384 в каждом пул-реквесте, даже если ваш сборщик (esbuild, SWC) не проверяет типы во время сборки.
- Проверяйте синтаксис tsconfig.json через форматтер и валидатор JSON после изменений: ошибка синтаксиса JSON молча мешает TypeScript прочитать вашу конфигурацию.