Концепция targets в Parcel

Одной из ключевых особенностей Parcel является возможность создавать несколько вариантов сборки одного и того же проекта. Для решения этой задачи используется концепция targets — целей сборки, определяющих характеристики итогового набора файлов.

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

Один исходный код может одновременно собираться для различных сценариев:

  • современного браузера;
  • устаревших браузеров;
  • Node.js;
  • библиотеки npm;
  • серверного рендеринга;
  • мобильных WebView;
  • тестового окружения.

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


Что представляет собой target

Target — это конфигурационный объект, описывающий конкретный результат сборки.

Простейшая схема выглядит следующим образом:

Исходный код
       │
       ▼
    Parcel
       │
 ┌─────┼─────┐
 ▼     ▼     ▼
Web  Node  Library

Каждая цель сборки может иметь собственные настройки:

Параметр Назначение
context Среда выполнения
distDir Каталог вывода
engines Поддерживаемые платформы
outputFormat Формат модулей
optimize Оптимизация
sourceMap Карты исходников
isLibrary Режим библиотеки

Встроенные targets из package.json

Parcel автоматически анализирует некоторые поля файла package.json и на их основе создает цели сборки.

Например:

{
  "main": "dist/main.js",
  "module": "dist/module.js",
  "browser": "dist/browser.js"
}

В этом случае Parcel формирует несколько целей:

Поле Target
main CommonJS-сборка
module ES Modules-сборка
browser браузерная версия

Команда:

parcel build src/index.js

может автоматически создать все перечисленные варианты.


Target main

Поле main обычно используется для публикации пакетов в npm.

Пример:

{
  "main": "dist/index.js"
}

Parcel воспринимает данный файл как основной экспорт библиотеки.

После сборки:

dist/
 └── index.js

Обычно для main генерируется формат CommonJS.

Пример использования собранного пакета:

const lib = require("my-library");

Target module

Поле module предназначено для современных сборщиков и браузеров, поддерживающих ES Modules.

{
  "module": "dist/index.mjs"
}

После сборки:

dist/
 └── index.mjs

Использование:

import lib from "my-library";

Преимущества:

  • поддержка tree shaking;
  • статический анализ зависимостей;
  • более эффективная оптимизация.

Target browser

Target browser описывает браузерную сборку.

{
  "browser": "dist/browser.js"
}

Parcel учитывает особенности веб-платформы:

  • заменяет Node-специфичные конструкции;
  • подключает браузерные полифиллы при необходимости;
  • оптимизирует загрузку ресурсов.

Пример:

process.env.NODE_ENV

может быть преобразован в константу на этапе сборки.


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

В большинстве реальных проектов используются собственные цели сборки.

Пример:

{
  "targets": {
    "modern": {},
    "legacy": {}
  }
}

Теперь доступны две независимые сборки.

Запуск:

parcel build src/index.js

создаст обе версии одновременно.


Настройка каталога вывода

Каждый target может использовать собственный каталог результатов.

{
  "targets": {
    "modern": {
      "distDir": "dist/modern"
    },
    "legacy": {
      "distDir": "dist/legacy"
    }
  }
}

Результат:

dist/
 ├── modern/
 │   └── index.js
 │
 └── legacy/
     └── index.js

Такой подход особенно удобен при публикации разных версий приложения.


Параметр context

Свойство context определяет среду выполнения.

Возможные значения:

{
  "context": "browser"
}
{
  "context": "node"
}
{
  "context": "web-worker"
}
{
  "context": "service-worker"
}

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

Browser

{
  "context": "browser"
}

Предназначено для веб-приложений.

Особенности:

  • работа с DOM;
  • поддержка CSS;
  • обработка изображений;
  • браузерные оптимизации.

Node

{
  "context": "node"
}

Предназначено для серверного кода.

Особенности:

  • отсутствуют DOM API;
  • сохраняются Node-модули;
  • используются возможности платформы Node.js.

Пример:

const fs = require("fs");

Такой код не требует браузерных полифиллов.


Web Worker

{
  "context": "web-worker"
}

Используется для фоновых потоков браузера.

Пример:

self.onmess age = event => {
  console.log(event.data);
};

Parcel учитывает ограничения среды Worker.


Service Worker

{
  "context": "service-worker"
}

Применяется для:

  • офлайн-режима;
  • кэширования;
  • фоновой синхронизации;
  • push-уведомлений.

Настройка engines

Свойство engines определяет список поддерживаемых платформ.

Пример:

{
  "targets": {
    "modern": {
      "engines": {
        "browsers": [
          "last 2 Chrome versions"
        ]
      }
    }
  }
}

Parcel использует эту информацию для:

  • транспиляции JavaScript;
  • подбора полифиллов;
  • оптимизации синтаксиса.

Browserslist и targets

Parcel интегрируется с Browserslist.

Пример:

{
  "targets": {
    "legacy": {
      "engines": {
        "browsers": [
          "> 0.5%",
          "not dead"
        ]
      }
    }
  }
}

Parcel автоматически определяет:

  • поддержку стрелочных функций;
  • поддержку классов;
  • поддержку optional chaining;
  • поддержку nullish coalescing;
  • поддержку других современных возможностей языка.

Создание modern и legacy сборок

Очень распространённый сценарий.

{
  "targets": {
    "modern": {
      "distDir": "dist/modern",
      "engines": {
        "browsers": [
          "last 2 Chrome versions"
        ]
      }
    },

    "legacy": {
      "distDir": "dist/legacy",
      "engines": {
        "browsers": [
          "> 0.5%",
          "IE 11"
        ]
      }
    }
  }
}

Результат:

dist/
 ├── modern/
 └── legacy/

Современная версия содержит меньше транспиляции и работает быстрее.

Legacy-версия включает дополнительные преобразования для старых браузеров.


outputFormat

Свойство outputFormat задаёт формат модулей.

Поддерживаются:

  • commonjs;
  • esmodule;
  • global.

CommonJS

{
  "outputFormat": "commonjs"
}

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

module.exports = myFunction;

Чаще всего используется для Node.js.


ES Module

{
  "outputFormat": "esmodule"
}

Пример:

export default myFunction;

Используется современными инструментами сборки.


Global

{
  "outputFormat": "global"
}

Экспорт выполняется через глобальный объект.

Например:

window.MyLibrary = MyLibrary;

Такой вариант удобен для подключения через тег script.


Режим библиотеки

Для npm-пакетов существует специальный параметр:

{
  "isLibrary": true
}

Когда target является библиотекой:

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

Пример:

{
  "targets": {
    "library": {
      "isLibrary": true
    }
  }
}

Минификация отдельных targets

Каждая цель может иметь собственные настройки оптимизации.

{
  "targets": {
    "production": {
      "optimize": true
    },

    "debug": {
      "optimize": false
    }
  }
}

Результат:

  • production — минимизированный код;
  • debug — читаемый код.

Source Maps для разных целей

Карты исходников можно включать или отключать независимо.

{
  "targets": {
    "dev": {
      "sourceMap": true
    },

    "prod": {
      "sourceMap": false
    }
  }
}

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


Запуск конкретного target

При наличии нескольких целей можно собирать только одну из них.

Пример:

parcel build src/index.js --target modern

Либо:

parcel build src/index.js --target legacy

Это сокращает время сборки и удобно в CI/CD-процессах.


Несколько targets для одной библиотеки

Часто пакет публикуется сразу в нескольких форматах.

Конфигурация:

{
  "targets": {
    "cjs": {
      "distDir": "dist/cjs",
      "outputFormat": "commonjs",
      "isLibrary": true
    },

    "esm": {
      "distDir": "dist/esm",
      "outputFormat": "esmodule",
      "isLibrary": true
    }
  }
}

Структура:

dist/
 ├── cjs/
 │   └── index.js
 │
 └── esm/
     └── index.js

Преимущества:

  • совместимость со старым кодом;
  • поддержка современных сборщиков;
  • возможность tree shaking.

Targets и переменные окружения

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

Пример:

if (process.env.BUILD_TARGET === "modern") {
  console.log("Modern build");
}

В процессе сборки Parcel способен исключать неиспользуемые ветки кода благодаря механизму dead code elimination.


Targets и оптимизация зависимостей

Parcel анализирует каждый target отдельно.

Предположим, имеется код:

import feature from "./feature.js";

Для современного target:

feature?.run();

может остаться без изменений.

Для старого браузера тот же код будет транспилирован в более совместимую форму.

Таким образом один и тот же исходник приводит к разным результатам сборки.


Практический пример полной конфигурации

{
  "targets": {
    "modern": {
      "distDir": "dist/modern",
      "context": "browser",
      "outputFormat": "esmodule",
      "optimize": true,
      "sourceMap": false,
      "engines": {
        "browsers": [
          "last 2 Chrome versions"
        ]
      }
    },

    "legacy": {
      "distDir": "dist/legacy",
      "context": "browser",
      "outputFormat": "global",
      "optimize": true,
      "sourceMap": false,
      "engines": {
        "browsers": [
          "> 0.5%",
          "IE 11"
        ]
      }
    },

    "node": {
      "distDir": "dist/node",
      "context": "node",
      "outputFormat": "commonjs",
      "optimize": false,
      "sourceMap": true
    }
  }
}

После выполнения сборки:

dist/
 ├── modern/
 ├── legacy/
 └── node/

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

Архитектурная роль targets

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

Каждый target формирует собственный конвейер обработки:

  1. анализ окружения;
  2. определение поддерживаемых возможностей платформы;
  3. транспиляция JavaScript;
  4. обработка ресурсов;
  5. оптимизация;
  6. генерация итоговых файлов.

Благодаря этому один проект может одновременно выпускать:

  • ESM-сборку для современных инструментов;
  • CommonJS-сборку для Node.js;
  • браузерную версию для CDN;
  • legacy-вариант для старых браузеров;
  • отдельную серверную сборку для SSR.

Именно эта модель делает targets одним из важнейших механизмов Parcel при разработке универсальных приложений и библиотек.