Предзагрузка TS-плагина: как подключить и устранить ошибки

Ошибка 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 — предварительная компиляция: надёжно для продакшена, без магии на этапе запуска.
📊 Каким способом вы запускаете TypeScript-плагины?
ts-node/register
tsx
Предварительная компиляция tsc
Встроенные средства сборщика

Предзагрузка плагинов в сборщиках и фреймворках

Многие инструменты уже умеют загружать TS-конфиги и плагины самостоятельно. Vite, например, обрабатывает vite.config.ts через esbuild без дополнительной настройки. Но если вы пишете собственный плагин, который динамически подгружает TS-модули, предзагрузку придётся организовать вручную.

Для webpack конфиг на TypeScript требует зарегистрированного загрузчика: CLI попытается использовать ts-node, если он установлен, либо предложит установить его. Проверьте, что пакет присутствует в devDependencies, а в tsconfig.json корректно указаны module и target под вашу версию Node.

Если ваша система плагинов загружает модули динамически через import(), убедитесь, что регистрация загрузчика происходит в точке входа — до первого динамического импорта. Иначе обработчик просто не успеет подключиться.

Пошаговая настройка предзагрузки

Ниже — универсальный порядок действий, не привязанный к конкретному фреймворку. Он подходит для собственных плагинных систем и скриптов.

☑️ Настройка предзагрузки TS-плагина

Выполнено: 0 / 5

Установите загрузчик:

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.