pnpm против разросшегося node_modules: как вернуть место на диске

Кратко: экономия диска с pnpm достигается за счёт общего хранилища пакетов, жёстких ссылок и строгого lockfile — это режет дубли и ускоряет инсталляции. Подробный вводный обзор — Оптимизация дискового пространства: как pnpm помогает сократить размер node_modules в 2026 году, а ниже — разбор механики, настроек и подводных камней с примерами для монорепо, CI и контейнеров.

Там, где node_modules превращался в бесконечный чердак с одинаковыми коробками, pnpm вводит порядок кладовщика: каждый уникальный пакет хранится один раз, а рабочие проекты берут его через ссылки. Диск перестаёт таять на глазах, кэш обретает смысл, а пересборки не выглядят как капитальный ремонт.

Смена менеджера пакетов редко бывает романтичной, но именно здесь замена инструмента меняет экономику: меньше гигабайтов, ниже сетевые издержки, стабильнее сборки. И за этой сухой бытовой выгодой — чёткий инженерный замысел, где вычислительная дисциплина соседствует с радикально бережным отношением к байтам.

Почему node_modules разрастается и как pnpm меняет правила

Корень проблемы — дублирование зависимостей и хаотичное дублирование версий; решение pnpm — общий контент-адресуемый store и ссылки вместо копий. Размер падает, а целостность графа зависимостей становится предсказуемой.

Традиционный подход к установке пакетов сводится к копированию каждой зависимости прямо в папку проекта. Каждая новая версия — новый набор файлов, каждая вариация графа — очередной клон. В больших экосистемах, особенно при множестве сервисов, это приводит к каскаду однотипных копий, которые отличный кандидат на дедупликацию, но менеджеры прошлого поколения не преследовали эту цель радикально. pnpm строит установку иначе: формирует единый контент-адресуемый store на машине и кладёт в него каждый уникальный артефакт ровно один раз. В каталоге проекта оказывается не гора файлов, а лёгкая прослойка ссылок, указывающих на общий оригинал. Эта архитектура режет размер каталога проекта кратно, сохраняя при этом привычную семантику импорта модулей и изоляцию зависимостей на уровне графа.

Парадоксально, но строгость даёт свободу. Жёсткие ссылки и «виртуальный» store приучают систему к неизменяемости артефактов, а это снижает вероятность «тихой» порчи кэша. Lockfile отражает точную, а не приблизительную конфигурацию. В итоге пространство перестаёт быть жертвой компромиссов, а время установки всё чаще упирается не в сеть, а в файловую систему и CPU при постинсталляционных скриптах.

Что именно копируют npm и yarn, и что оставляет pnpm

npm и классический yarn копируют файлы пакетов в проект, pnpm — оставляет в проекте ссылки на файлы из общего хранилища. Экономится место и время на распаковку, а общедоступный кэш используется всеми проектами на машине.

В старых схемах каждый пакет — это набор файлов, который повторяется в каждом проекте, даже если контент совпадает байт-в-байт. Такой подход быстро разгоняет каталог node_modules до десятков гигабайт, особенно если команда ведёт монорепозиторий или парк микросервисов. У pnpm файлы не копируются повторно; вместо этого в проект кладутся жёсткие ссылки (или джанкшены на Windows), указывающие на единственный «источник правды» в store. В результате два проекта с одинаковым набором зависимостей не увеличат суммарный размер на диске вдвое: добавится только тонкая надстройка ссылок и метаданных.

Что происходит на диске: жёсткие ссылки, виртуальный store и где рождается экономия

Экономия рождается в общем store: каждый файл устанавливается один раз и раздаётся проектам через жёсткие ссылки. На совместимых файловых системах это даёт почти нулевую цену дубликатов, сохраняя привычные пути в node_modules.

pnpm создаёт контент-адресуемое хранилище — своего рода библиотеку, где каждая книга идентифицируется по содержимому, а не по месту на полке. Когда проект «просит» зависимость, он получает не копию книги, а карточку-указатель: жёсткую ссылку на тот же блок данных. Для файловых систем поддерживающих hard link, такие ссылки не занимают дополнительного места, а записывают лишь ещё один индекс к тем же данным на диске. В node_modules остаётся организованный фасад: привычные структуры директорий, корректные резолвы зависимостей, аккуратная изоляция peerDependencies. Такой фасад скрывает от разработчиков библиотечный механизм, но именно он возвращает гигабайты и уменьшает давление на систему ввода-вывода.

Где-то между элегантностью идеи и её приземлением живут детали. На Windows pnpm применяет джанкшены, на macOS и Linux — жёсткие ссылки и симлинки. На файловых системах с ограничениями по жёстким ссылкам используется деградация к копированию, но и там выигрыши заметны благодаря общему кэшу и строгой работе с lockfile. В контейнерах экономия усиливается: слои образов меньше, кэш CI-пайплайна стабильнее, пересборки предсказуемы. В конечном счёте, к механизму прирастают практики — и именно их сочетание делает экономию устойчивой.

Механизм Как экономит место Совместимость Ограничения
Жёсткие ссылки (hard links) Один набор данных — множество путей Linux, macOS (ext4, APFS, др.) Нельзя ссылаться на разные ФС; счётчики ссылок
Симлинки/джанкшены Тонкая прослойка вместо копий Windows (NTFS), кросс-платформенно Поведение в инструментах, чувствительных к путям
Контент-адресуемый store Один раз скачано — переиспользуется повсюду Все платформы Требует дисциплины в lockfile и кэше

Виртуальный store и «видимость» зависимостей

pnpm держит фактические пакеты вне node_modules, а внутрь выводит аккуратные ссылки и хелперы. Это сохраняет корректный алгоритм резолва и не даёт неявным зависимостям «просачиваться» наверх.

Такая архитектура дисциплинирует зависимости: модули не могут полагаться на соседей, если не объявили их явно. Бонусом становится предсказуемость сборки: если пакет не заявлен — он недоступен, даже если «живет рядом» в общем store. Разработчики получают ясный сигнал из сборки, а не молчаливую удачу на одной машине и падение на другой. Экономия диска здесь идёт рука об руку с прозрачностью графа.

Реальные выигрыши: монорепозитории, CI и контейнеры

Самые большие выгоды pnpm приносит там, где много проектов и повторяющихся зависимостей: монорепо, CI-пайплайны, контейнерные сборки. Экономится место, время и сетевой трафик.

В монорепозитории десятки пакетов часто делят между собой одни и те же библиотеки. В мире копий это лестница в небо: каждый пакет тащит свой багаж. pnpm прекращает эту экскурсию — общий store хранит багаж для всех, а рабочие каталоги лишь помечают, что именно им нужно. В CI, где чистые агенты изрядно тратят минуты на дублирующие загрузки, «тёплый» store вместе с pnpm fetch и детерминированным lockfile сокращает холодный старт и стабилизирует кэш. В контейнерах чуть иная магия: правильный порядок слоёв и вынесение store в отдельный слой позволяет слою с приложением быть тонким и переиспользуемым. Так и пространство освобождается, и сборки становятся повторяемыми, а не походят на попытку угадать погоду по кофейной гуще.

Сценарий Размер с npm/yarn Размер с pnpm Экономия Комментарий
Монорепо, 40 пакетов ~12–16 ГБ ~4–6 ГБ 60–70% Общий store и единый lockfile
CI с кэшем зависимостей ~2–3 ГБ на билд ~0.5–1 ГБ на билд 50–70% pnpm fetch + кэш слоёв
Docker-образ сервиса +600–900 МБ +250–450 МБ 40–60% Выделенный слой с store

Скорость установки и прогрев кэша

Установка с pnpm быстрее там, где кэш прогрет: распаковки меньше, сеть молчит, а диску достаётся работа по созданию ссылок. На «холодном» старте преимущество проявляется за счёт строгого lockfile и параллелизма.

Распаковка архивов — затратная операция. Когда она проводится один раз, а последующие инсталляции лишь создают ссылки, профит становится заметным даже на SSD. Параллельное скачивание, «сухая» команда pnpm fetch и режим --prefer-offline сдвигают бутылочное горлышко от сети к локальным операциям, а они предсказуемее и дешевле. В результате «прогретые» разработческие машины и CI-агенты ощутимо экономят минуты, которые прежде таяли в логах установщика.

Настройки pnpm, которые дают максимум экономии

Максимальная экономия достигается комбинацией общего store, строгого lockfile и осознанных флагов установки. Важны: frozen-lockfile, prefer-offline, pnpm fetch, грамотный кэш в CI и монорепо-конфигурация.

Архитектура pnpm уже экономит, но настройки превращают выгоду в гарантию. Жёсткое соблюдение lockfile отсекает «ползучие» апдейты, которые раздувают кэш. Режимы офлайна и предварительной закачки смещают сетевые риски в предсказуемую фазу, а workspace-конфигурация в монорепо собирает граф зависимостей в единое полотно. Раздельные каталоги для store и для проекта позволяют тонко управлять контейнерными слоями и артефактами кэша. Часть флагов стоит включать по умолчанию, часть — под конкретную стадию пайплайна.

  • pnpm fetch — заранее скачивает артефакты в store без установки; полезно в CI и Docker-слоях.
  • pnpm install --frozen-lockfile — жёсткая фиксация графа; исключает неожиданные дубли и дрейф.
  • --prefer-offline — берёт из кэша при любом удобном случае; сеть трогает реже.
  • store-dir в .npmrc — явное место store для кэширования между сборками.
  • pnpm prune — удаляет мёртвые артефакты; держит store в форме.
  • node-linker и package-import-method — тонкая настройка типа ссылок под ФС/CI.
Флаг/настройка Эффект Когда включать Побочный эффект
--frozen-lockfile Детерминированные сборки CI, релизы Требует актуального lockfile
--prefer-offline Меньше сетевых обращений Локальная разработка, CI с кэшем Редкий риск устаревшего кэша
pnpm fetch Тонкий слой зависимостей в Docker Контейнеры, многоэтапные сборки Нужна корректная стадия кеша
store-dir=... Шеринг кэша между сборками CI-агенты, хост-машины Нужны права/квоты на каталог

Монорепо и workspaces: один lockfile — меньше копий

Единый lockfile для рабочих пространств предотвращает дрейф версий и повторную установку почти одинаковых деревьев. Экономия места и времени становится системной, а не случайной.

Workspaces позволяют собирать зависимый зоопарк пакетов в стройную композицию. Когда каждый пакет живёт своим lockfile, небольшие расхождения версий умножают артефакты. Единый lockfile и согласованные версии снимают эту нагрузку. В такой схеме pnpm не только делит файлы через store, но и предотвращает саму причину разрастания — несогласованность графа. Каждое обновление проходит через ревью, а у кэша появляется шанс на долгую спокойную жизнь.

Подводные камни и как их обходить без лишних копий

Переход на pnpm иногда вскрывает тонкие места: нестандартные postinstall-скрипты, завязки на физические пути, хрупкие peerDependencies. Решение — поправить скрипты, зафиксировать пира и следить за «side effects».

Многие инструменты «в поле» исходят из предположения, что файлы лежат там, где их положили ранее другие менеджеры. В мире ссылок такие предположения ломаются. Скрипты, читающие из относительных путей вне своего пакета, требуют корректировок. Некоторые сборочные цепочки берут зависимости «наугад», рассчитывая на транзитивную доступность — с pnpm подобная магия исчезает, и это, по сути, благо. Ещё одна особенность — кэш «side effects»: результаты сборок нативных модулей, которые стоит хранить и переиспользовать, но нужно держать в чистоте при смене платформ и ABI.

  • Убрать обращения к путям вне пакета; использовать require.resolve и API инструментов.
  • Явно объявлять peerDependencies и их версии; избегать плавающих значений.
  • Проверить postinstall-скрипты на работу в read-only окружениях и в ссылочной структуре.
  • Включить и обслуживать кэш side effects для нативных модулей на CI.
  • Отладить поведение на Windows: джанкшены и права доступа могут повлиять на линтеры.

Когда симлинки мешают сборке и что делать

Редкие инструменты ошибочно трактуют симлинки как внешние источники и «выпадают» из оптимизаций. Решение — обновить инструментарий или поменять стратегию линковки в конфигурации pnpm.

Старые обфускаторы, устаревшие бандлеры, жёстко прописанные пути — всё это ещё встречается. При проблемах разумно протестировать альтернативные значения node-linker или переключить метод импорта пакетов. В большинстве современных цепочек сборки готовность к симлинкам уже норма, а проблемы решаются точечными обновлениями. Экономия места не стоит того, чтобы держаться за наследие, которое тратит минуты и гигабайты впустую.

Измеряем результат: методика, метрики, правдивые цифры

Корректная оценка выгоды строится на замерах «до/после»: размер node_modules и store, время установки на холодном/тёплом старте, размер контейнерных слоёв. Нужны воспроизводимые условия и фиксированный lockfile.

Любая оптимизация требует чисел. Простой сценарий: зафиксировать lockfile, очистить кэши и провести холодную установку, затем тёплую — с прогретым store. Замерить размеры каталогов node_modules, .pnpm-store и итоговых артефактов сборки. Для контейнеров — отдельно оценить слои после шага pnpm fetch и после установки. В CI добавить метки времени, чтобы понимать, где теряются минуты: в сети, распаковке или постинстале. Цифры показывают реальную экономию и подсказывают, где ещё есть воздух для улучшений.

  1. Зафиксировать lockfile и версии Node, отключить автообновления.
  2. Очистить кэш и удалить node_modules, сохранить снимок размеров диска.
  3. Провести холодную установку, замерить время и размер.
  4. Повторить установку с прогретым store, зафиксировать показатели.
  5. Собрать Docker-образ в два этапа: с pnpm fetch и без; сравнить слои.
  6. Собрать отчёт: проценты экономии, стабильность времени, отклонения.
Метрика Инструмент Базовый ориентир На что смотреть
Размер node_modules du -sh, проводник Снижение в 2–4 раза Стабильность между сборками
Размер store du -sh ~/.pnpm-store Растёт умеренно Удаление мёртвых пакетов (pnpm prune)
Время cold start CI таймеры Сокращение на 20–40% Сети vs распаковка
Вес Docker-слоёв docker history -200–400 МБ Порядок команд в Dockerfile

Как фиксировать успех и не потерять кэш

Успех виден там, где кэш живёт дольше одной сборки: lockfile стабилен, store доступен агентам, а пайплайн сохраняет артефакты между задачами. Тогда цифры перестают прыгать и становятся прогнозом.

Практика показывает, что даже хорошие оптимизации исчезают, если каждый билд начинается «с нуля». Сохранение store между джобами, разнос шагов fetch и install по разным слоям, аккуратные ключи кэширования, учитывающие и lockfile, и системные параметры — это дисциплина, которая монетизирует преимущества pnpm. Пара недостающих строк в конфигурации способна вернуть серые логи и распухшие каталоги. Стоит один раз выстроить пайплайн, чтобы дальше наблюдать лишь плановое старение зависимостей, а не хаос.

FAQ — ответы на вопросы, которые задают чаще всего

Сколько места на диске реально экономит pnpm на нескольких проектах?

На общей машине экономия достигает 50–70% благодаря общему store и жёстким ссылкам вместо копий. Чем больше совпадающих зависимостей, тем выше выигрыш.

Замеры в командах с десятками сервисов показывают, что каждый новый проект почти не увеличивает общий объём — добавляет только «обвязку» ссылок и новые артефакты, если появляются уникальные версии библиотек. Там, где раньше node_modules росли линейно с количеством проектов, с pnpm кривая сглаживается, а пространство возвращается владельцу машины без борьбы.

Будет ли pnpm быстрее npm/yarn на чистой установке с нуля?

На холодном старте преимущество умеренное и зависит от сети, но уже заметно за счёт параллельности и аккуратной распаковки. На тёплом старте выигрыши значительны.

Холодная установка всегда ограничена скоростью скачивания и распаковки архивов. Здесь pnpm играет в ту же игру, но делает меньше лишнего. Как только кэш прогревается, затраты переползают из «скачать и распаковать» в «сделать ссылки», и это кратно быстрее. Поэтому устойчивый эффект виден не на первом прогоне, а на последующих.

Совместим ли pnpm со всеми популярными инструментами сборки?

С большинством — да: современные бандлеры и тестовые раннеры корректно работают с симлинками и жёсткими ссылками. Проблемы встречаются у наследованных тулов.

При сбоях помогает обновление до последних версий, а также настройка node-linker и package-import-method. В редких случаях проще заменить проблемный инструмент, чем поддерживать поведение, рассчитанное на мир копий и прямых путей.

Что делать с нативными модулями и их «side effects» при использовании pnpm?

Включить кэш сайд-эффектов и отслеживать платформу/ABI при сборке. Тогда бинарные артефакты не будут пересобираться без повода и не распухнут в кэше.

Правильная конфигурация CI записывает кеш ключами, учитывающими версию Node, архитектуру и параметры компоновки. Это позволяет переиспользовать бинарники там, где это безопасно, и освежать их, когда среда меняется, избегая ложной экономии и повторных часовых сборок.

Как устроить оптимальный Dockerfile для pnpm, чтобы уменьшить размер образа?

Вынести pnpm fetch в ранний слой, кэшировать store, а установку и сборку делать позже. Тогда слой с зависимостями переиспользуется чаще и весит меньше.

Хороший паттерн: скопировать только lockfile и манифесты, выполнить pnpm fetch, затем добавить исходники и запустить pnpm install --offline. Порядок команд должен минимизировать перегенерацию слоя с кэшем при изменениях кода, чтобы тяжёлые артефакты не «съезжали» при каждом коммите.

Можно ли хранить общий store pnpm на отдельном разделе или в сети?

Да, store настраивается параметром store-dir, и его можно вынести в отдельный каталог, раздел или сетевую шару. Главное — совместимость ссылок и скорость.

Если файловая система или сетевое хранилище ограничивают жёсткие ссылки, pnpm упадёт в режим копирования или симлинков. Тогда выигрыши сохранятся не полностью, но общий кэш по-прежнему сэкономит трафик и время установки.

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

Экономия диска давно перестала быть «мелочью для энтузиастов». С pnpm она превращается в стратегию: порядок вместо хаоса, общий store вместо копий, детерминированность вместо случайности. Когда пространство и время в сборках складываются в предсказуемую линию, растёт не только скорость, но и надёжность разработки.

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

How To: быстрый переход на pnpm с гарантированной экономией

Ниже — сжатая последовательность действий, которая помогает перенести проект на pnpm без сюрпризов и с предсказуемой выгодой в месте и времени.

  1. Включить pnpm через Corepack и зафиксировать версию в репозитории.
  2. Сгенерировать lockfile pnpm, проверить граф зависимостей и peerDependencies.
  3. Настроить store-dir и pnpm fetch для локалки, CI и Docker.
  4. Перевести пайплайн на --frozen-lockfile, сохранить и прогревать кэш.
  5. Перебрать postinstall-скрипты, убрать привязки к физическим путям.
  6. Замерить «до/после»: размер node_modules, store, слоёв образа и время установки.
  7. Включить регулярный pnpm prune и ревью обновлений зависимостей.