Обзор архитектуры: как Parcel обрабатывает граф зависимостей

Общая модель архитектуры

Parcel построен вокруг концепции инкрементального графа зависимостей, где каждый модуль проекта рассматривается как узел, а связи import / require формируют рёбра графа. В отличие от классических бандлеров, где сборка часто представляет собой линейный процесс, здесь основной акцент сделан на динамическом построении, кешировании и переиспользовании графа.

Архитектура делится на несколько ключевых подсистем:

  • построение графа зависимостей (Dependency Graph)
  • система резолвинга модулей (Resolver)
  • трансформации кода (Transformer Pipeline)
  • система кеширования (Cache Layer)
  • планировщик задач (Scheduler)
  • система упаковки (Packager)

Каждая из этих частей работает как отдельный слой, но все они связаны через единый объект графа, который является центральной структурой всей системы.


Граф зависимостей как ядро системы

Основная сущность, с которой работает Parcel, — это граф зависимостей (Dependency Graph).

Он состоит из:

  • Nodes (узлы) — модули (JS, CSS, изображения, шрифты и т.д.)
  • Edges (рёбра) — связи между модулями через импорты
  • Assets (активы) — внутреннее представление модулей после резолва и трансформаций

Каждый узел графа содержит:

  • исходный код модуля
  • метаданные (тип, путь, зависимости)
  • список трансформированных вариантов
  • информацию о кешировании
  • ссылки на дочерние зависимости

Граф строится лениво: Parcel не пытается сразу обработать весь проект, а начинает с entry-point и постепенно расширяет граф по мере обнаружения импортов.


Построение графа: от entry point к полной структуре

Процесс начинается с точки входа (entry file). Далее выполняется рекурсивный обход:

  1. Парсинг entry-файла
  2. Поиск импортов
  3. Резолв путей импортов
  4. Создание узлов графа
  5. Рекурсивная обработка зависимостей

На каждом шаге Parcel работает не с текстом, а с AST (Abstract Syntax Tree), что позволяет точно определять зависимости без использования строкового анализа.

Важный момент

Parcel не просто строит дерево зависимостей — он строит граф, поскольку:

  • один модуль может использоваться в нескольких местах
  • возможны циклические зависимости
  • разные типы активов могут ссылаться друг на друга (JS → CSS → images)

Резолвинг модулей: как определяется источник зависимости

Система резолвинга отвечает за преобразование строковых импортов в реальные файлы.

Пример:

import Button from "./components/Button";

Parcel выполняет несколько шагов:

  • определение базового пути текущего модуля
  • проверка расширений (.js, .ts, .jsx, .json)
  • поиск index файлов в директориях
  • применение alias-конфигураций
  • поддержка node_modules резолвинга

Резолвер в Parcel является расширяемым и может учитывать:

  • конфигурацию проекта
  • плагины
  • тип ассетов (например, CSS Modules vs обычный CSS)

Результат резолвинга — это уникальный идентификатор ассета, который затем используется в графе.


Преобразование модулей: Transformer Pipeline

После того как модуль добавлен в граф, он проходит через систему трансформаций.

Transformer Pipeline работает как цепочка этапов:

  1. чтение исходного файла
  2. определение типа (JS, CSS, PNG и т.д.)
  3. применение соответствующих трансформеров
  4. генерация промежуточного представления

Примеры трансформаций:

  • TypeScript → JavaScript
  • JSX → JS
  • SCSS → CSS
  • оптимизация изображений
  • транспиляция современных возможностей JS

Каждый трансформер возвращает:

  • новый код
  • метаданные
  • список новых зависимостей (если они появились)

Это критически важно: трансформации могут изменять структуру графа, добавляя новые узлы.


Динамическое расширение графа

Граф в Parcel не является статическим. Он может изменяться в процессе трансформаций.

Сценарий:

  • файл A.js импортирует B.css
  • CSS-трансформер обнаруживает url(image.png)
  • создаётся новый узел image.png
  • граф расширяется в процессе сборки

Таким образом, граф зависит не только от JS-импортов, но и от внутреннего анализа всех типов ресурсов.


Кеширование: ключевой механизм производительности

Parcel активно использует кеширование на уровне узлов графа.

Кеш включает:

  • исходный код файла
  • результат трансформаций
  • зависимости
  • хеш содержимого

Если файл не изменился, Parcel:

  • не пересобирает его AST
  • не запускает трансформеры
  • не пересчитывает зависимости

Это делает возможным:

  • почти мгновенные пересборки
  • HMR (Hot Module Replacement)
  • инкрементальные обновления

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


Система инвалидации графа

При изменении файла Parcel не пересобирает всё дерево, а выполняет локальную инвалидацию:

  1. изменённый узел помечается как dirty
  2. пересчитываются только его зависимости
  3. затрагиваются только соседние узлы графа

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


Параллельная обработка и планировщик задач

Parcel использует многопоточную модель выполнения:

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

Scheduler распределяет задачи по типу:

  • CPU-bound (трансформации)
  • IO-bound (чтение файлов)
  • кеш-операции

Граф при этом выступает как источник задач: каждый узел может генерировать новые задания.


Связь графа с упаковкой (bundling)

После построения полного графа начинается этап упаковки.

Graph → Bundles transformation включает:

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

Parcel использует граф для принятия решений:

  • какие модули объединять
  • какие разделять на отдельные чанки
  • какие загружать лениво

Code Splitting на основе графа зависимостей

Разбиение кода происходит автоматически на основе структуры графа.

Основные стратегии:

  • entry-based splitting
  • dynamic import splitting (import())
  • shared dependency extraction

Если модуль встречается в нескольких ветках графа, он выносится в общий chunk.


Влияние циклических зависимостей

Граф зависимостей в реальных проектах часто содержит циклы:

A → B → C → A

Parcel обрабатывает их через:

  • ленивую инициализацию узлов
  • частично построенные AST
  • отложенное разрешение экспортов

Циклы не ломают граф, а становятся частью его структуры.


Граф ассетов: расширение классической модели

Parcel работает не только с JS, но и с универсальными asset nodes:

  • JavaScript modules
  • CSS stylesheets
  • images
  • fonts
  • WASM modules
  • HTML files

Каждый тип ассета является узлом одного графа, что делает систему унифицированной мультимодульной структурой.


Роль метаданных в графе

Каждый узел графа содержит метаданные:

  • hash содержимого
  • список трансформеров
  • тип ассета
  • зависимости
  • зависимости второго уровня

Эти данные используются для:

  • кеширования
  • инвалидации
  • оптимизации сборки
  • анализа изменений

Итоговая структура взаимодействия слоёв

Архитектура Parcel вокруг графа выглядит как конвейер:

  • Resolver формирует узлы
  • Transformer добавляет зависимости
  • Graph агрегирует структуру
  • Scheduler распределяет обработку
  • Cache ускоряет повторные сборки
  • Packager преобразует граф в бандлы

Все процессы замкнуты на одну структуру — dependency graph, который является центральной моделью всей системы сборки.