Ошибка Cannot find module или сообщение о неизвестном расширении .ts при запуске плагина почти всегда означает одно: загрузчик TypeScript не был зарегистрирован до того, как Node.js начал выполнять код. Предзагрузка TS-плагина решается флагом --require или --import, который подключает транспилятор до старта приложения.
В этой статье разберём, как работает механизм предзагрузки, какие инструменты использовать (ts-node, tsx, esbuild), как настроить предзагрузку в популярных сборщиках и что проверить, если плагин по-прежнему не подхватывается. Инструкции применимы к Node.js актуальных версий; точные флаги зависят от версии рантайма, поэтому сверяйтесь с документацией вашего инструмента.
Что такое предзагрузка TS-плагина и зачем она нужна
Node.js по умолчанию понимает только JavaScript. Когда проект или его плагин написан на TypeScript, файлы .ts нужно либо заранее скомпилировать, либо транспилировать «на лету». Предзагрузка — это регистрация обработчика до запуска основного кода, чтобы любой require() или import TypeScript-файла выполнялся прозрачно.
Типичный сценарий: конфигурационный файл или плагин системы (например, webpack.config.ts, плагин для Vite или тестового раннера) написан на TS. Без предзагрузки рантайм упадёт с синтаксической ошибкой на первом же типе. С предзагрузкой файл транспилируется в памяти и выполняется как обычный JS.
Способы предзагрузки: ts-node, tsx и нативные средства
Самый известный инструмент — ts-node. Предзагрузка выполняется флагом -r (сокращение от --require):
node -r ts-node/register ./src/plugin.ts
Для проектов на ESM вместо --require используется loader-флаг, так как --require работает только с CommonJS. В новых версиях Node.js рекомендуется флаг --import:
node --import tsx ./src/plugin.ts
Альтернатива — tsx, обёртка над esbuild. Она заметно быстрее ts-node на старте, потому что не выполняет проверку типов, а только удаляет их из кода. Это важный нюанс: ни ts-node в режиме transpile-only, ни tsx не проверяют типы — для контроля типов запускайте tsc --noEmit отдельно.
- 🔧 ts-node/register — классический вариант для CommonJS-проектов, поддерживает проверку типов.
- ⚡ tsx — быстрый запуск без type-checking, подходит для разработки и скриптов.
- 📦 esbuild-register — лёгкий регистратор на базе esbuild для флага
-r. - 🧪 tsc + node — предварительная компиляция: надёжно для продакшена, без магии на этапе запуска.
Предзагрузка плагинов в сборщиках и фреймворках
Многие инструменты уже умеют загружать TS-конфиги и плагины самостоятельно. Vite, например, обрабатывает vite.config.ts через esbuild без дополнительной настройки. Но если вы пишете собственный плагин, который динамически подгружает TS-модули, предзагрузку придётся организовать вручную.
Для webpack конфиг на TypeScript требует зарегистрированного загрузчика: CLI попытается использовать ts-node, если он установлен, либо предложит установить его. Проверьте, что пакет присутствует в devDependencies, а в tsconfig.json корректно указаны module и target под вашу версию Node.
Если ваша система плагинов загружает модули динамически через import(), убедитесь, что регистрация загрузчика происходит в точке входа — до первого динамического импорта. Иначе обработчик просто не успеет подключиться.
Пошаговая настройка предзагрузки
Ниже — универсальный порядок действий, не привязанный к конкретному фреймворку. Он подходит для собственных плагинных систем и скриптов.
☑️ Настройка предзагрузки TS-плагина
Установите загрузчик:
npm install --save-dev tsx
Затем обновите скрипт запуска в package.json. Вам нужно, чтобы флаг стоял до пути к входному файлу:
"start": "node --import tsx ./src/index.ts"
После этого запустите проект и проверьте, что плагин подхватился: в логах не должно быть ошибок парсинга, а функциональность плагина должна быть доступна. Если проект использует CommonJS, замените --import tsx на -r ts-node/register — смешивать оба подхода в одной команде не нужно.
⚠️ Внимание: флаги--requireи--importработают по-разному.--requireпредназначен для CommonJS и не подхватывает ESM-модули;--importпоявился в более новых версиях Node.js. Проверьте версию рантайма командойnode --versionи сверьтесь с документацией выбранного загрузчика.
Сравнение инструментов предзагрузки
Выбор зависит от того, что важнее: скорость старта, проверка типов или совместимость. Ориентируйтесь на общие характеристики подходов, а не на точные цифры — производительность сильно зависит от размера проекта.
| Инструмент | Проверка типов | Скорость старта | Формат модулей |
|---|---|---|---|
| ts-node (полный) | Да | Медленнее | CJS и ESM |
| ts-node --transpile-only | Нет | Средняя | CJS и ESM |
| tsx | Нет | Быстрая | CJS и ESM |
| tsc + node | Да (на этапе сборки) | Мгновенный запуск JS | Зависит от tsconfig |
Для продакшена предпочтительнее предварительная компиляция: вы запускаете уже готовый JavaScript, исключая целый класс ошибок на этапе старта. Предзагрузка же идеальна для разработки, тестов и одноразовых скриптов.
Почему tsx быстрее ts-node
tsx использует компилятор esbuild, написанный на Go, который только удаляет типовые аннотации без их проверки. ts-node по умолчанию прогоняет код через компилятор TypeScript, что даёт проверку типов, но замедляет запуск, особенно на больших проектах.
Типичные ошибки и их устранение
Чаще всего встречается SyntaxError: Cannot use import statement outside a module или неизвестное расширение .ts. Возможная причина — несовпадение формата модулей: загрузчик зарегистрирован для CommonJS, а код использует ESM, или наоборот. Проверьте поле "type" в package.json и настройку module в tsconfig.json — они должны быть согласованы.
Вторая группа проблем — пути и алиасы. Если в коде используются алиасы вида @/utils, загрузчик должен знать о них: для ts-node это пакет tsconfig-paths, для tsx алиасы из paths подхватываются автоматически в большинстве конфигураций, но стоит проверить поведение на вашей версии.
- 🚫 Ошибка парсинга
.ts— загрузчик не зарегистрирован или флаг стоит после пути к файлу. - 🔀 Конфликт CJS/ESM — несогласованные
"type"иmodule. - 🗂️ Не резолвятся алиасы — не подключён
tsconfig-pathsили неверныйbaseUrl. - 🐢 Долгий старт — отключите проверку типов в dev-режиме или перейдите на tsx.
⚠️ Внимание: не отключайте проверку типов навсегда ради скорости. Режим transpile-only удобен для разработки, но перед коммитом или сборкой запускайте tsc --noEmit, иначе ошибки типов попадут в репозиторий незамеченными.
⚠️ Внимание: регистрация загрузчика внутри самого TS-файла бесполезна — к моменту его выполнения файл уже должен быть транспилирован. Регистрация всегда происходит снаружи: через флаг команды, отдельный JS-файл точки входа или настройки раннера.
Предзагрузка в тестовых раннерах и CI
Тестовые фреймворки обычно имеют собственный механизм транспиляции. Vitest обрабатывает TS из коробки, Jest требует ts-jest или @swc/jest в конфигурации transform. Если ваш плагин подключается в тестах динамически, убедитесь, что трансформер настроен и для файлов плагина, а не только для тестовых файлов.
В CI-конвейере логика та же: команда запуска должна включать флаг предзагрузки, а зависимости загрузчика — устанавливаться. Частая причина «у меня локально работает, а в CI падает» — загрузчик лежит в devDependencies, которые не ставятся при npm ci --omit=dev. Либо перенесите пакет в dependencies, либо запускайте собранный JS.
FAQ: частые вопросы о предзагрузке TS-плагинов
Чем предзагрузка отличается от обычной компиляции tsc?
Компиляция создаёт готовые JS-файлы заранее, а предзагрузка транспилирует код в памяти в момент запуска. Первый вариант надёжнее для продакшена, второй — удобнее для разработки, так как не требует шага сборки.
Почему node -r ts-node/register не работает с ESM?
Флаг -r загружает модули через механизм CommonJS require, который не перехватывает ESM-импорты. Для ESM используйте loader-флаги или --import tsx — конкретный синтаксис зависит от версии Node.js и загрузчика.
Нужно ли проверять типы, если используется tsx?
Да. tsx только удаляет типовые аннотации, не анализируя их. Ошибки типов останутся незамеченными, поэтому добавьте отдельный шаг tsc --noEmit в скрипты или CI.
Плагин загружается локально, но падает в Docker-контейнере. Что проверить?
Проверьте, что загрузчик установлен в контейнере (не отфильтрован флагом --omit=dev), что команда запуска в Dockerfile содержит флаг предзагрузки и что версия Node.js в образе совпадает с локальной.
Можно ли предзагружать TS-плагины без сторонних пакетов?
В новых версиях Node.js появляются экспериментальные возможности работы с TypeScript, но их поддержка и поведение зависят от конкретной версии рантайма. Сверьтесь с официальной документацией Node.js вашей версии, прежде чем отказываться от tsx или ts-node.