Namer: управление именами выходных файлов

В системе сборки Parcel формирование имён итоговых файлов относится к отдельному этапу пайплайна трансформаций. За это отвечает слой Namer, который определяет, как именно будут называться сгенерированные ассеты после бандлинга, минификации и оптимизаций.

Parcel строит граф зависимостей, обрабатывает модули через трансформеры, а затем на этапе вывода (output) применяет namer-плагины, формируя финальные пути файлов. Это критически важный механизм для кеширования, организации структуры сборки и интеграции с CDN.


Роль Namer в процессе сборки

После завершения всех трансформаций Parcel получает набор ассетов: JavaScript-бандлы, CSS, изображения, шрифты и прочие ресурсы. На этом этапе каждый файл уже имеет содержимое, но ещё не имеет окончательного имени.

Задача Namer:

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

Parcel позволяет подключать несколько namer-стратегий, которые применяются по очереди.


Базовое поведение именования

По умолчанию Parcel использует встроенную стратегию, которая ориентируется на:

  • имя входного файла;
  • относительный путь;
  • хеш содержимого.

Типичный результат:

dist/index.8f3a1c.js
dist/styles.91bc44.css
dist/logo.2d91aa.png

Главный принцип — content hashing. Если содержимое файла меняется, меняется и его имя, что позволяет безопасно использовать агрессивное кеширование.


Плейсхолдеры именования

Parcel поддерживает систему шаблонов, которые используются внутри namer-логики. Основные токены:

[name]

Имя исходного файла без расширения.

src/app.js → app.js

[ext]

Расширение файла.

app.js → app.js
style.css → style.css

[hash]

Хеш содержимого файла.

Используется для кеш-бастинга:

app.[hash].js → app.8f3a1c.js

[contenthash]

Аналог [hash], но строго зависит от содержимого файла, игнорируя метаданные.

Используется для стабильного кеширования.


[dir]

Сохраняет структуру каталогов относительно входной точки.

src/pages/home.js → pages/home.js

[parent]

Имя родительской директории.

Полезно при группировке модулей.


Настройка именования через конфигурацию

Parcel позволяет переопределять стратегию именования через .parcelrc.

Пример подключения стандартного namer:

{
  "namers": ["@parcel/namer-default"]
}

Можно комбинировать несколько namer-стратегий:

{
  "namers": [
    "@parcel/namer-hash",
    "@parcel/namer-default"
  ]
}

Parcel применяет их последовательно: первая стратегия, которая возвращает результат, побеждает.


Поведение при конфликте имён

Если два файла после обработки получают одинаковое имя, Parcel автоматически:

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

Это предотвращает перезапись файлов в dist.


Хеширование и стратегии кеширования

Namer тесно связан с системой кеширования Parcel.

Сценарий изменения файла

  1. Исходный файл:
app.js → app.1a2b3c.js
  1. Изменение кода:
console.log("update");
  1. Новый результат:
app.9f8e7d.js

При этом старый файл остаётся в сборке до очистки, что позволяет CDN продолжать обслуживать старые версии.


Пользовательский Namer

Parcel позволяет создавать собственные namer-плагины.

Базовая структура плагина

export default function namer(asset) {
  return `${asset.name}.${asset.hash}.${asset.type}`;
}

Регистрация в .parcelrc

{
  "namers": ["./my-namer.js"]
}

Объект Asset в Namer

Каждый asset передаётся в namer с набором метаданных:

  • filePath — путь к исходному файлу;
  • type — тип ресурса (js, css, png);
  • bundleGraph — граф зависимостей;
  • hash — вычисленный хеш;
  • bundleBehavior — поведение бандла;
  • env — контекст окружения.

Эти данные позволяют строить сложные стратегии именования.


Примеры кастомных стратегий

1. Разделение по типам ресурсов

export default function namer(asset) {
  if (asset.type === "js") {
    return `scripts/${asset.name}.${asset.hash}.js`;
  }

  if (asset.type === "css") {
    return `styles/${asset.name}.${asset.hash}.css`;
  }

  return `assets/${asset.filePath.split('/').pop()}`;
}

2. Группировка по модулям

export default function namer(asset) {
  const module = asset.filePath.split('/')[1];
  return `${module}/${asset.name}.${asset.hash}.${asset.type}`;
}

3. CDN-ориентированная структура

export default function namer(asset) {
  return `cdn/${asset.hash}.${asset.type}`;
}

Такая стратегия полностью игнорирует исходную структуру проекта и оптимизирует доставку через CDN.


Влияние Namer на кеширование браузера

Namer напрямую определяет эффективность HTTP-кеширования.

Без хеша

app.js

Любое изменение требует сброса кеша вручную.

С хешем

app.3f91c2.js

Браузер воспринимает файл как новый ресурс, если изменилось содержимое.


Взаимодействие с Bundler и Optimizer

Namer работает после:

  1. Tree-shaking
  2. Minification
  3. Code splitting
  4. Asset optimization

Это означает, что хеш отражает финальное состояние файла, а не исходный код.


Особенности работы с code splitting

При динамическом импорте:

import("./module.js");

Parcel создаёт отдельный chunk, для которого также применяется namer.

Пример результата:

module.async.7c1a9b.js

Каждый async-бандл получает собственную стратегию именования.


Ошибки и ограничения

Некорректный путь

Если namer возвращает недопустимый путь:

return "";

Parcel генерирует fallback-имя.


Потеря уникальности

Если стратегия не учитывает хеш:

return `${asset.name}.js`;

возможны конфликты файлов при одинаковых именах в разных директориях.


Рекомендации по проектированию namer-стратегий

  • всегда учитывать хеш для production-сборок;
  • избегать глобального плоского пространства имён без необходимости;
  • разделять dev и prod стратегии;
  • учитывать CDN-архитектуру;
  • не дублировать структуру проекта без причины;
  • контролировать читаемость путей при дебаге.

Поведение в development-режиме

В режиме разработки Parcel может:

  • уменьшать использование хешей;
  • сохранять более читаемые имена;
  • ускорять пересборку за счёт упрощённого именования.

Пример:

app.js (dev)
app.8f3a1c.js (prod)

Совместимость с другими плагинами

Namer взаимодействует с:

  • Resolver (определение модулей);
  • Transformer (изменение кода);
  • Packager (упаковка бандлов);
  • Optimizer (минификация).

Любое изменение в этих этапах может повлиять на итоговое имя через изменение хеша.


Расширенные сценарии

Мультирегиональные сборки

export default function namer(asset) {
  return `eu/${asset.hash}.${asset.type}`;
}

Версионирование ресурсов

export default function namer(asset) {
  return `v2/${asset.name}.${asset.hash}.${asset.type}`;
}

Условное именование по окружению

export default function namer(asset) {
  if (process.env.NODE_ENV === "production") {
    return `${asset.name}.${asset.hash}.${asset.type}`;
  }

  return `${asset.name}.${asset.type}`;
}