Монорепозиторий (Monorepo) представляет собой единое хранилище исходного кода, содержащее несколько взаимосвязанных пакетов, приложений и библиотек. Такой подход широко используется для организации крупных проектов, где множество модулей развиваются синхронно и имеют общую инфраструктуру.
Типичная структура монорепозитория выглядит следующим образом:
project/
├── package.json
├── packages/
│ ├── ui/
│ │ ├── package.json
│ │ └── src/
│ ├── utils/
│ │ ├── package.json
│ │ └── src/
│ └── core/
│ ├── package.json
│ └── src/
└── apps/
├── admin/
└── client/
В подобных проектах Parcel решает сразу несколько задач:
Главным преимуществом Parcel является возможность работать с монорепозиторием практически без сложной конфигурации.
Современные монорепозитории обычно используют механизм Workspaces.
Пример настройки в корневом package.json:
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
]
}
Поддержка рабочих пространств существует в:
Parcel корректно распознаёт структуру зависимостей, создаваемую менеджером пакетов.
Например:
{
"name": "@company/ui",
"version": "1.0.0"
}
и
{
"name": "@company/admin",
"dependencies": {
"@company/ui": "^1.0.0"
}
}
Даже если пакет физически расположен внутри того же репозитория, Parcel воспринимает его как обычную зависимость и включает в граф сборки.
Часто монорепозиторий содержит набор внутренних библиотек.
Пример библиотеки:
packages/ui
├── package.json
└── src
└── index.js
Файл:
export function Button() {
console.log("Button");
}
Настройка библиотеки:
{
"name": "@company/ui",
"source": "src/index.js",
"main": "dist/main.js",
"module": "dist/module.js"
}
Запуск сборки:
parcel build
Parcel анализирует поле source и автоматически создаёт
выходные файлы, указанные в целях (targets).
Результат:
dist/
├── main.js
└── module.js
Таким образом каждая библиотека может собираться независимо от остальных частей монорепозитория.
В монорепозиториях часто возникает необходимость выпускать несколько вариантов одной библиотеки.
Пример:
{
"targets": {
"main": {
"context": "node",
"outputFormat": "commonjs"
},
"module": {
"context": "browser",
"outputFormat": "esmodule"
}
}
}
После сборки будут созданы:
dist/
├── main.js
└── module.js
Такой подход позволяет одновременно поддерживать:
Особенно полезно это для библиотек общего назначения внутри монорепозитория.
Рассмотрим структуру:
packages/
├── core
└── ui
Пакет ui использует core.
import { formatDate } from "@company/core";
export function renderDate(date) {
return formatDate(date);
}
Parcel строит единый граф зависимостей:
ui
└── core
Во время сборки:
Разработчику не требуется вручную настраивать алиасы или дополнительные резолверы.
При использовании Workspaces менеджер пакетов создаёт символические ссылки между пакетами.
Например:
node_modules/
└── @company/
└── ui -> ../. ./packages/ui
Parcel корректно обрабатывает такие связи.
Импорт:
import { Button } from "@company/ui";
будет разрешён в исходный код внутреннего пакета.
Это особенно удобно при активной разработке, поскольку изменения мгновенно отражаются во всех приложениях, использующих библиотеку.
Допустим существует приложение:
apps/admin
Файл:
import { Button } from "@company/ui";
Button();
Сборка:
parcel build src/index.js
Parcel автоматически включает код библиотеки в итоговый граф.
Схематично процесс выглядит так:
Admin App
↓
@company/ui
↓
@company/core
Все зависимости анализируются как единая система.
Современные библиотеки часто используют поле
exports.
Пример:
{
"exports": {
".": "./src/index.js",
"./hooks": "./src/hooks.js"
}
}
Теперь доступны только явно объявленные точки входа.
Разрешены:
import { Button } from "@company/ui";
import { useTheme } from "@company/ui/hooks";
Недоступен:
import data from "@company/ui/src/internal/data";
Parcel учитывает правила exports при построении графа
зависимостей.
Это помогает поддерживать стабильные публичные API внутри монорепозитория.
Нередко несколько пакетов используют одни и те же ресурсы:
packages/
├── assets
├── ui
└── marketing
Например:
import logo from "@company/assets/logo.svg";
Parcel автоматически обрабатывает:
Дополнительная настройка в большинстве случаев не требуется.
Структура проекта:
packages/
├── core
├── ui
└── utils
Каждый пакет содержит TypeScript-код.
export function sum(a: number, b: number): number {
return a + b;
}
Parcel автоматически использует конфигурацию:
tsconfig.json
или
tsconfig.base.json
в корне монорепозитория.
Типичный вариант:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true
}
}
Все пакеты могут наследовать общие настройки типизации.
В больших монорепозиториях активно используются алиасы.
Пример:
{
"compilerOptions": {
"paths": {
"@core/*": [
"packages/core/src/*"
]
}
}
}
Импорт:
import { logger } from "@core/logger";
Parcel умеет считывать настройки из TypeScript-конфигурации и корректно разрешать подобные пути.
Это упрощает поддержку больших кодовых баз.
Одним из главных преимуществ Parcel является агрессивное кэширование.
При первой сборке создаётся каталог:
.parcel-cache
Кэш содержит результаты:
При изменении только одного пакета пересобираются исключительно затронутые части графа.
Например:
core изменён
↓
ui зависит от core
↓
admin зависит от ui
Parcel пересоберёт только необходимую цепочку зависимостей.
Это значительно ускоряет работу в крупных монорепозиториях.
Во время разработки обычно используется:
parcel watch src/index.html
Parcel отслеживает изменения сразу во всех пакетах, участвующих в графе.
Изменение файла:
packages/core/src/date.js
приведёт к автоматической пересборке всех зависимых частей проекта.
Механизм работает даже через несколько уровней вложенности зависимостей.
Монорепозиторий часто содержит несколько клиентских приложений:
apps/
├── admin
├── crm
└── dashboard
Каждое приложение может использовать одинаковые библиотеки:
import { Button } from "@company/ui";
Parcel анализирует общий граф и способен эффективно выполнять:
В результате уменьшается объём загружаемого кода.
Предположим библиотека экспортирует множество функций.
export function a() {}
export function b() {}
export function c() {}
export function d() {}
Приложение использует только одну:
import { a } from "@company/utils";
Во время production-сборки Parcel удалит неиспользуемые экспорты.
Итоговый бандл будет содержать только необходимый код.
На больших внутренних библиотеках это позволяет существенно снизить размер сборки.
Часто несколько пакетов используют одну версию React.
Пример:
{
"peerDependencies": {
"react": "^19.0.0"
}
}
Подобная схема позволяет избежать:
Parcel корректно учитывает peer-зависимости при построении графа.
Даже если код хранится в едином репозитории, библиотеки могут публиковаться независимо.
Пример пакета:
{
"name": "@company/core",
"version": "2.1.0",
"source": "src/index.js",
"main": "dist/main.js"
}
Сборка:
parcel build
Публикация:
npm publish
Каждый пакет получает собственный артефакт сборки, сохраняя при этом преимущества совместной разработки внутри монорепозитория.
Крупный проект может иметь следующую организацию:
project/
├── apps/
│ ├── web
│ ├── mobile-web
│ └── admin
│
├── packages/
│ ├── core
│ ├── ui
│ ├── api
│ ├── analytics
│ └── design-tokens
│
├── package.json
├── tsconfig.json
└── .parcelrc
Связи между пакетами:
web
├── ui
├── api
└── analytics
admin
├── ui
├── api
└── core
ui
├── design-tokens
└── core
Parcel строит единый граф зависимостей для всей системы, автоматически отслеживает изменения, выполняет оптимизацию, устраняет неиспользуемый код и обеспечивает эффективную сборку как отдельных библиотек, так и конечных приложений внутри монорепозитория любого масштаба.