Проблема традиционного DI в JavaScript и ограничения ESM
В JavaScript‑экосистеме внедрение зависимостей (Dependency Injection, DI) чаще всего реализуется через ручную регистрацию модулей, динамические импорты или сторонние контейнеры. При работе с ECMAScript Modules (ESM) эта модель сталкивается с двумя фундаментальными ограничениями: отсутствие возможности «постпозитивного» связывания (late binding) без промежуточного билда и необходимость явно указывать зависимости в импорт‑заявлениях. В результате, изоморфный код, который должен одинаково работать в браузере и на Node.js, требует либо транспиляции, либо сложных конфигураций сборщика.
Архитектура @teqfw/di до рефакторинга
Библиотека @teqfw/di, разрабатываемая с 2019 года, решала задачу DI без транспиляции и без ручного перечисления зависимостей. Она предоставляла два ключевых механизма:
- Контейнер – глобальный реестр, в который помещались фабрики и готовые экземпляры.
- Позднее связывание – возможность отложить создание зависимости до момента первого обращения, что позволяло избежать проблем с порядком загрузки модулей.
Однако исходный код опирался на CommonJS‑подобные паттерны (require‑вызовы, глобальные переменные) и требовал явного вызова register() для каждого модуля. При переходе на чистый ESM‑стек такие вызовы становились избыточными и конфликтовали с статическим анализом импортов.
Как AI‑инструменты меняют подход к рефакторингу кода
С появлением больших языковых моделей (LLM) стало возможным автоматизировать часть рутинных задач рефакторинга. Интегрированный Codex‑агент способен проанализировать исходный репозиторий, выявить паттерны, предложить альтернативные реализации и даже сгенерировать готовый код. В случае с @teqfw/di AI‑агент предложил полностью избавиться от ручных регистраций, используя декларативный список зависимостей, который интерпретируется непосредственно в рантайме ESM.
Переписывание библиотеки: основные шаги
-
Выделение метаданных зависимостей
Каждый сервис был снабжён статическим полемdependsOn, в котором перечислялись имена других сервисов. Это позволило построить граф зависимостей без обращения к контейнеру. -
Создание декларативного манифеста
Был введён файлdi-manifest.mjs, экспортирующий массив объектов{ token, factory, dependsOn }. Манифест формируется автоматически при сборке и может быть импортирован в любой точке приложения. -
Переход к статическому
import
Вместо динамическихrequireкаждый сервис теперь импортирует только свои прямые зависимости через обычныйimport. Это сохраняет преимущества статического анализа и tree‑shaking. -
Автоматическая инициализация контейнера
При первом обращении к контейнеру он читает манифест, рекурсивно разрешает зависимости и создаёт экземпляры. При этом порядок инициализации контролируется алгоритмом топологической сортировки, исключающим циклические ссылки. -
Поддержка изоморфности
Все модули теперь работают как в браузере, так и в Node.js без дополнительных полифилов. ESM‑механизм гарантирует, что импорт происходит только один раз, а контейнер остаётся единственным источником состояния.
Результаты: изоморфность без транспиляции и автоматическая регистрация
После рефакторинга библиотека полностью отказалась от ручных вызовов register(). Декларативный манифест позволяет добавить новый сервис, просто описав его в файле di-manifest.mjs. При этом:
- Нет необходимости в транспилере – код написан на чистом ES2022 и может быть запущен напрямую в современных браузерах и Node.js 18+.
- Tree‑shaking сохраняет размер бандла – неиспользуемые сервисы исключаются сборщиком благодаря статическим импортам.
- Автоматическое разрешение зависимостей устраняет ошибки порядка загрузки, часто встречающиеся в традиционных DI‑контейнерах.
- Поддержка изоморфного кода достигается без условных проверок
process.browserилиtypeof window.
Практические выводы для разработчиков
- Декларативные зависимости предпочтительнее имплицитных – они делают граф видимым для статических анализаторов и упрощают отладку.
- ESM‑модули позволяют избавиться от большинства runtime‑шаблонов – если проект поддерживает современный JavaScript, стоит отказаться от CommonJS‑мостов.
- AI‑ассистенты могут ускорить миграцию – генерация шаблонов манифеста и автоподстановка импортов экономят часы ручного труда.
- Топологическая сортировка обязательна – при автоматическом разрешении зависимостей необходимо гарантировать отсутствие циклов, иначе контейнер зациклится.
- Тестировать изоморфность следует в обеих средах – даже при чистом ESM небольшие различия в поведении глобального объекта могут проявиться.
Внедрение декларативных (не)зависимостей в ESM‑контексте демонстрирует, что современный JavaScript способен обеспечить уровень абстракции, привычный разработчикам Java и PHP, без ущерба для производительности и без необходимости в сложных сборочных пайплайнах. Переписанная версия @teqfw/di подтверждает, что правильная архитектура и поддержка AI‑инструментов позволяют быстро адаптировать старый код к новым стандартам, сохраняя при этом изоморфность и простоту использования.