Dependency scanning

Dependency scanning во фреймворке Fresh — это механизм статического анализа зависимостей, который используется для автоматического определения клиентских и серверных модулей, а также для точного формирования графа импортов. В отличие от классических SPA-фреймворков, Fresh изначально проектировался с упором на минимальный JavaScript на клиенте и строгую изоляцию серверного и клиентского кода. Dependency scanning является ключевым элементом этой архитектуры.

Fresh работает поверх Deno, используя его модульную систему и стандартные ES-модули. Сканирование зависимостей выполняется во время сборки и в режиме разработки, без необходимости явной конфигурации со стороны разработчика.


Архитектурные принципы, лежащие в основе сканирования зависимостей

Fresh придерживается нескольких фундаментальных принципов:

  • Zero-build подход — отсутствие традиционного шага сборки для серверного кода
  • Островная архитектура (islands architecture) — клиентский JavaScript загружается только для интерактивных компонентов
  • Явное разделение окружений — код либо серверный, либо клиентский, без неявных гибридных состояний

Dependency scanning обеспечивает реализацию этих принципов за счёт анализа импортов, сигнатур компонентов и контекста их использования.


Источники данных для dependency scanning

Сканирование зависимостей во Fresh опирается на несколько типов входных данных:

Файловая структура проекта

Fresh использует строгую конвенцию каталогов:

  • routes/ — серверные маршруты
  • islands/ — интерактивные клиентские компоненты
  • components/ — серверные или универсальные компоненты
  • static/ — статические ресурсы

Уже на этом уровне dependency scanner делает первичную классификацию модулей.

AST-анализ исходного кода

Для каждого TypeScript/JavaScript-файла строится абстрактное синтаксическое дерево. Анализируются:

  • import и export
  • динамические импорты
  • JSX-выражения
  • сигнатуры функций и компонентов

AST-анализ позволяет определить фактические зависимости, а не просто текстовые импорты.


Разделение серверных и клиентских зависимостей

Ключевая задача dependency scanning — определить, какие зависимости должны попасть в клиентский бандл, а какие остаются на сервере.

Серверные зависимости

К серверным относятся:

  • модули, импортируемые из routes/
  • утилиты без JSX
  • работа с базами данных
  • файловая система
  • Deno API

Эти зависимости никогда не включаются в клиентский JavaScript.

Клиентские зависимости

Клиентские зависимости формируются исключительно из island-компонентов:

  • файлы из каталога islands/
  • их прямые и транзитивные импорты
  • зависимости, используемые внутри JSX интерактивных компонентов

Dependency scanner строит отдельный граф зависимостей для каждого island.


Механизм обнаружения islands

Island-компоненты являются точкой входа для клиентского кода. Dependency scanning выполняет следующие шаги:

  1. Поиск JSX-компонентов, импортируемых в маршрутах
  2. Проверка, находится ли компонент в каталоге islands/
  3. Фиксация компонента как client entry point
  4. Рекурсивное сканирование всех его импортов

Каждый island изолируется: его зависимости не смешиваются с другими islands, даже если используются одинаковые модули.


Транзитивные зависимости и дедупликация

Fresh не объединяет client bundles автоматически. Dependency scanning намеренно сохраняет изоляцию, но при этом выполняет анализ на уровне модулей:

  • повторяющиеся импорты выявляются
  • одинаковые версии модулей могут быть переиспользованы браузером за счёт HTTP-кеширования
  • отсутствие глобального клиентского бандла снижает риск каскадных зависимостей

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


Обработка динамических импортов

Dependency scanning во Fresh поддерживает динамические импорты (import()), но с ограничениями:

  • динамический импорт внутри island считается клиентским
  • динамический импорт в серверном коде остаётся серверным
  • анализируются только статически разрешимые пути

Это позволяет избежать непредсказуемого поведения и сохранять детерминированность dependency graph.


JSX как маркер среды выполнения

Во Fresh наличие JSX не делает модуль автоматически клиентским. Dependency scanning учитывает контекст:

  • JSX в routes/ — серверный рендеринг
  • JSX в components/ — серверный, если не используется в island
  • JSX в islands/ — клиентский

Таким образом, среда выполнения определяется не синтаксисом, а путём распространения зависимости.


Роль dependency scanning в HMR

В режиме разработки dependency scanning тесно интегрирован с hot module replacement:

  • при изменении файла пересчитывается только затронутый граф зависимостей
  • обновление islands происходит изолированно
  • серверные маршруты перезапускаются без пересборки клиента

Это достигается за счёт точного знания, какие модули связаны между собой.


Влияние на производительность и безопасность

Dependency scanning даёт несколько побочных эффектов:

  • Минимальный клиентский JS — загружается только необходимый код
  • Отсутствие утечек серверных зависимостей — Deno API и секреты не могут попасть в браузер
  • Предсказуемый runtime — каждый модуль имеет строго определённую среду выполнения

Fresh избегает эвристик и магии, опираясь исключительно на анализ зависимостей.


Ограничения и осознанные компромиссы

Dependency scanning во Fresh не пытается решить все возможные случаи:

  • не поддерживаются условные импорты, зависящие от runtime-значений
  • невозможна автоматическая оптимизация shared-клиентских зависимостей
  • требуется соблюдение файловых конвенций

Эти ограничения упрощают модель и делают поведение фреймворка прозрачным.


Внутреннее представление dependency graph

Внутри Fresh dependency graph представлен ориентированным ациклическим графом:

  • вершины — модули
  • рёбра — импорты
  • корни — routes и islands

Каждый граф аннотирован метаданными:

  • среда выполнения
  • тип модуля
  • путь до entry point

Это представление используется как для dev-сервера, так и для production-режима.


Dependency scanning как фундамент архитектуры Fresh

Во Fresh dependency scanning — не вспомогательная оптимизация, а базовый архитектурный механизм. Он определяет, как код исполняется, где он исполняется и в каком объёме доставляется пользователю. Благодаря строгому анализу зависимостей фреймворк достигает предсказуемости, простоты и высокой производительности без сложных конфигураций и скрытых этапов сборки.