Отладка резолюции модулей

Резолюция модулей в Parcel основана на сочетании алгоритмов Node.js, анализе метаданных пакетов и собственных оптимизаций бандлера. В процессе сборки каждый импорт проходит несколько стадий: нормализацию пути, определение типа модуля, поиск физического файла, интерпретацию package.json и применение правил трансформации.

Ключевая особенность Parcel заключается в том, что резолюция выполняется не единожды, а повторяется в разных контекстах графа зависимостей. Это означает, что одна и та же строка импорта может интерпретироваться по-разному в зависимости от окружения, типа сборки (development/production) и активных трансформеров.


Этапы разрешения импортов

Нормализация спецификатора

Первый шаг — приведение строки импорта к каноническому виду:

  • удаление лишних сегментов (./, ../)
  • нормализация слэшей
  • обработка URL-подобных импортов (например, file:)
  • различение абсолютных и относительных путей

Особое значение имеет различие между:

  • относительными импортами: ./utils/math
  • пакетными импортами: react, lodash/debounce
  • alias-импортами: @app/utils

Parcel не интерпретирует алиасы напрямую — они разрешаются через конфигурацию и плагины резолвера.


Поиск файла в файловой системе

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

  1. Проверка наличия файла с указанным именем

  2. Попытка добавления расширений:

    • .js
    • .jsx
    • .ts
    • .tsx
    • .json
  3. Проверка директорий с index-файлами

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


Интерпретация package.json

Если импорт указывает на пакет, Parcel анализирует package.json:

  • main — классическая точка входа
  • module — ESM-версия
  • source — исходный вход для трансформаций
  • exports — современная схема экспорта
  • browser — браузерные подмены

Особое внимание уделяется полю exports, которое может полностью изменить маршрут резолюции:

{
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

В этом случае прямой доступ к внутренним файлам пакета блокируется, и Parcel обязан следовать декларативной карте экспортов.


Типичные проблемы резолюции

Несовпадение ESM и CommonJS

Одна из частых причин ошибок — смешивание модулей:

  • ESM ожидает import/export
  • CJS использует require/module.exports

Parcel пытается автоматически адаптировать формат, но при конфликте типов может возникать:

  • дублирование зависимостей
  • некорректная tree-shaking-оптимизация
  • ошибки Cannot find module

Конфликты расширений

При наличии нескольких файлов:

utils.js
utils.ts
utils/index.js

результат резолюции зависит от порядка приоритетов расширений. В Parcel он определяется конфигурацией и внутренним списком предпочтений. Ошибки возникают, когда:

  • файл неожиданно перекрывает директорию
  • индекс-файл конфликтует с одноимённым модулем

Monorepo-структуры часто используют символические ссылки. Parcel по умолчанию может:

  • либо резолвить symlink как реальный путь
  • либо сохранять виртуальную структуру

Это влияет на:

  • кэширование модулей
  • определение дубликатов
  • корректность HMR

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


Инструменты диагностики резолюции

Логирование процесса сборки

Parcel предоставляет детализированные логи резолюции. При увеличении уровня логирования становятся видны:

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

Полезные режимы:

  • --log-level verbose
  • --log-level info

В verbose-режиме отображается полный путь принятия решений резолвера.


Анализ графа зависимостей

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

  • дублирование модулей
  • циклические зависимости
  • неожиданные переходы между пакетами

В графе особенно важно отслеживать:

  • узлы с разными версиями одного пакета
  • разветвления через exports
  • скрытые зависимости через динамические импорты

Проверка кеша резолвера

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

  • файловых путей
  • результатов анализа package.json
  • трансформаций

Проблемы кеша проявляются как:

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

Диагностика включает:

  • очистку .parcel-cache
  • проверку стабильности hash-ключей модулей
  • анализ повторяемости ошибок после пересборки

Конфигурационные источники конфликтов

Aliases и tsconfig paths

Parcel поддерживает алиасы через:

  • package.json (поле alias)
  • tsconfig.json (compilerOptions.paths)

Конфликты возникают при:

  • пересечении alias-правил
  • несовпадении tsconfig и Parcel-конфига
  • неоднозначных вложенных путях

Пример проблемного случая:

{
  "paths": {
    "@/*": ["src/*"],
    "@utils/*": ["src/utils/*"]
  }
}

Если одновременно используется alias @/utils, резолвер может выбрать неоднозначный маршрут.


Conditional exports

Современные пакеты используют условные экспорты:

  • import
  • require
  • browser
  • default

Parcel выбирает ветку в зависимости от контекста сборки. Ошибки возникают, если:

  • отсутствует fallback
  • браузерная сборка не учитывает require
  • серверная сборка игнорирует browser

Динамические импорты и резолюция

import() добавляет дополнительный слой сложности. Parcel должен:

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

Проблемы диагностики:

  • модуль не попадает в бандл
  • chunk создаётся с неожиданным именем
  • зависимость исчезает при tree-shaking

Влияние трансформеров на резолюцию

Трансформеры (Babel, TypeScript, PostHTML) могут изменять:

  • структуру импортов
  • путь к модулю
  • тип экспортов

Особенно критично:

  • преобразование ESM → CJS
  • инлайнинг зависимостей
  • замена импортов через плагины

Резолюция в Parcel происходит до и после трансформации, что создаёт два слоя анализа:

  1. исходный граф
  2. трансформированный граф

Несоответствие между ними часто становится причиной трудноуловимых ошибок.


Сложные сценарии конфликтов

Дублирование зависимостей

Причины:

  • разные версии пакета в дереве зависимостей
  • различие путей резолюции
  • symlink-разветвления

Последствия:

  • нарушение singleton-логики (например, React context)
  • увеличение размера бандла
  • нестабильный HMR

Циклические зависимости

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

  • частично инициализированным модулям
  • неожиданным undefined экспортам
  • различному поведению в dev и prod

Неоднозначные экспорты

Пакеты с некорректным exports могут приводить к:

  • невозможности достичь внутренних модулей
  • резолюции на fallback, не соответствующий ожиданиям
  • различиям между Node и браузерной сборкой

Практическая модель анализа резолюции

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

  • вход: строка импорта
  • состояние: контекст сборки + конфигурация
  • переходы: правила package.json, alias, файловая система
  • выход: конкретный файл и его метаданные

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


Поведение при неоднозначных путях

Если Parcel сталкивается с несколькими кандидатами, применяется приоритет:

  1. явный путь файла
  2. exports
  3. module
  4. main
  5. файловая система с расширениями
  6. index-файлы

Нарушение этого порядка обычно сигнализирует о:

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