Современные проекты нередко состоят из нескольких взаимосвязанных пакетов. Например, в одном репозитории могут одновременно находиться:
Подход, при котором несколько пакетов располагаются внутри одного репозитория, называется монорепозиторием (monorepo).
Для управления такими структурами менеджеры пакетов предоставляют механизм Workspaces. Он позволяет:
Parcel полностью поддерживает проекты, построенные на Workspaces, независимо от того, используется Yarn, npm или pnpm.
Типичная структура проекта выглядит следующим образом:
my-monorepo/
├── package.json
├── packages/
│ ├── ui/
│ │ ├── package.json
│ │ └── src/
│ ├── utils/
│ │ ├── package.json
│ │ └── src/
│ └── app/
│ ├── package.json
│ └── src/
└── node_modules/
Корневой каталог содержит общий package.json, а каждый
пакет обладает собственным файлом package.json.
Начиная с npm 7 механизм Workspaces входит в состав стандартного менеджера пакетов.
Корневой файл:
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*"
]
}
Пакет библиотеки:
{
"name": "@company/utils",
"version": "1.0.0"
}
Пакет приложения:
{
"name": "@company/app",
"version": "1.0.0",
"dependencies": {
"@company/utils": "1.0.0"
}
}
После выполнения:
npm install
npm автоматически создаёт локальную связь между пакетами внутри монорепозитория.
Yarn стал одним из первых популярных инструментов, реализовавших поддержку монорепозиториев.
Корневой файл:
{
"private": true,
"workspaces": [
"packages/*"
]
}
Установка зависимостей выполняется обычной командой:
yarn install
Yarn анализирует все рабочие пространства и формирует единое дерево зависимостей.
В современных версиях Yarn поддерживаются:
Parcel корректно работает как с классическим режимом
node_modules, так и с современными возможностями Yarn.
pnpm использует собственную высокоэффективную модель хранения пакетов.
Корневой файл:
packages:
- "packages/*"
Файл сохраняется как:
pnpm-workspace.yaml
Дополнительно присутствует стандартный корневой
package.json:
{
"name": "my-monorepo",
"private": true
}
Установка зависимостей:
pnpm install
Главные особенности pnpm:
Parcel полностью совместим с проектами на pnpm.
Рассмотрим структуру:
packages/
├── app/
└── ui/
Пакет ui:
{
"name": "@company/ui",
"source": "src/index.js"
}
Пакет app:
{
"dependencies": {
"@company/ui": "workspace:*"
}
}
Использование:
import { Button } from "@company/ui";
Parcel обнаруживает локальную зависимость и включает её в граф сборки так же, как обычный пакет.
Дополнительная публикация библиотеки не требуется.
Parcel строит единый граф зависимостей для всего проекта.
Например:
app
├── ui
│ └── utils
└── api
└── utils
Parcel определяет:
Благодаря этому исключается дублирование кода.
Во многих монорепозиториях применяется специальный протокол:
{
"dependencies": {
"@company/ui": "workspace:*"
}
}
Также возможны варианты:
{
"dependencies": {
"@company/ui": "workspace:^"
}
}
или
{
"dependencies": {
"@company/ui": "workspace:~"
}
}
Такой синтаксис явно сообщает менеджеру пакетов, что зависимость должна разрешаться через локальное рабочее пространство.
Parcel получает уже корректно разрешённый путь к пакету и не требует дополнительной настройки.
Одно из преимуществ Workspaces заключается в том, что изменения библиотек моментально доступны приложениям.
Например:
packages/
├── ui/
└── app/
После изменения:
// ui/src/Button.js
export function Button() {
return "Updated";
}
Parcel фиксирует изменение файла внутри пакета ui и
запускает перестроение зависимых модулей.
При использовании режима разработки:
parcel serve src/index.html
обновление происходит автоматически через механизм HMR.
Часто несколько пакетов используют одну библиотеку.
Пример:
app
├── react
└── ui
└── react
Parcel анализирует дерево зависимостей и определяет, что используется одна и та же версия React.
Это позволяет:
Большинство менеджеров пакетов стараются поднимать общие зависимости вверх по дереву каталогов.
Например:
node_modules/
├── react
├── lodash
└── typescript
Вместо:
packages/
├── app/node_modules/react
└── ui/node_modules/react
Такой механизм называется hoisting.
Parcel не зависит от конкретной структуры размещения пакетов и использует стандартный механизм разрешения модулей Node.js.
В pnpm зависимости размещаются иначе.
Физическая структура может выглядеть значительно сложнее:
node_modules/
└── .pnpm/
Однако Parcel работает через официальные алгоритмы разрешения модулей и корректно находит пакеты независимо от внутреннего устройства каталога.
Поэтому специальные настройки обычно не требуются.
Распространённая практика — выделение отдельного пакета для конфигурации.
Пример:
packages/
├── eslint-config/
├── ui/
└── app/
Пакет:
{
"name": "@company/eslint-config"
}
Использование:
{
"extends": [
"@company/eslint-config"
]
}
Тот же подход применяется для:
Parcel легко взаимодействует с подобной организацией проекта.
Типичная структура:
packages/
├── types/
├── ui/
└── app/
Пакет типов:
{
"name": "@company/types"
}
Использование:
import type { User } from "@company/types";
Parcel корректно отслеживает зависимости между TypeScript-пакетами и перестраивает проект при изменениях.
Иногда используются алиасы:
{
"alias": {
"@ui": "./src/ui"
}
}
Импорт:
import Button from "@ui/Button";
Алиасы могут сосуществовать с Workspaces.
Например:
import { Button } from "@company/ui";
import Header from "@ui/Header";
Parcel разрешает оба типа импортов через собственный резолвер.
Крупные проекты могут содержать:
Parcel использует интеллектуальное кэширование:
В сочетании с Workspaces это позволяет существенно сократить время повторных сборок.
Не все пакеты предназначены только для внутреннего использования.
Например:
packages/
├── ui
├── utils
└── app
Библиотеки могут публиковаться в npm:
npm publish
или:
pnpm publish
Parcel при этом используется исключительно как инструмент сборки.
Пакеты внутри монорепозитория продолжают использовать локальные ссылки во время разработки и опубликованные версии после релиза.
Структура:
packages/
├── ui/
├── icons/
├── utils/
└── app/
Зависимости:
app
├── ui
│ ├── icons
│ └── utils
└── utils
Процесс работы выглядит следующим образом:
Благодаря поддержке Yarn Workspaces, npm Workspaces и pnpm Workspaces Parcel одинаково эффективно работает как в небольших проектах из нескольких пакетов, так и в крупных корпоративных монорепозиториях с десятками внутренних библиотек и приложений.