Чем pnpm отличается от npm и yarn?
Вопрос
В чём разница между npm, yarn и pnpm и почему pnpm экономит место на диске?
Короткий ответ
npm и yarn (в классическом режиме) кладут копию каждого пакета в плоский node_modules для каждого проекта, дублируя одни и те же версии на диске. pnpm хранит каждый пакет один раз в глобальном content-addressable хранилище и линкует его в проекты через симлинки/хардлинки, что экономит место и делает структуру node_modules строгой — без "фантомных" зависимостей.
Подробный ответ
npm и yarn (classic) используют плоский node_modules: зависимости и их поддерево поднимаются (hoisting) на верхний уровень, чтобы избежать дублирования и глубокой вложенности. Побочный эффект — фантомные зависимости: пакет A может импортировать пакет B, который реально ему не объявлен в package.json, а просто оказался рядом на верхнем уровне из-за того, что его поставил пакет C. Код работает, но ломается при любом изменении дерева зависимостей.
pnpm решает это иначе:
- Все версии всех пакетов физически хранятся один раз в глобальном content-addressable store (обычно
~/.pnpm-store), адресуемом по хэшу содержимого. - В
node_modulesкаждого проекта pnpm создаёт симлинки/хардлинки на этот store, а не копирует файлы. - Структура
node_modules/.pnpm— не плоская: каждый пакет видит через симлинки только те зависимости, что реально объявлены в егоpackage.json. Это даёт строгую изоляцию: если код импортирует необъявленный пакет, он просто не найдётся — ошибка проявится сразу, а не позже в проде или у другого разработчика. - За счёт хардлинков на одну и ту же физическую копию файла экономия места особенно заметна в монорепозиториях и при большом числе проектов с одинаковыми зависимостями — одна версия
reactфизически лежит на диске один раз, сколько бы проектов её ни использовали.
Yarn Berry (2+) тоже пытается решить проблему hoisting'а через режим Plug'n'Play (без node_modules вообще, резолвинг через .pnp.cjs), но это менее совместимо с экосистемой, поэтому многие остаются на node-modules linker.
С точки зрения командного API все три менеджера сегодня похожи (install, add, run), различия — в lock-файлах (package-lock.json, yarn.lock, pnpm-lock.yaml) и в скорости/дисциплине резолвинга.
Пример
# npm / yarn classic — плоский node_modules, возможны фантомные зависимости
npm install lodash
# pnpm — пакет физически один раз в сторе, в проекте — симлинки
pnpm add lodash
# посмотреть, где реально лежит store
pnpm store path
# C:\Users\User\AppData\Local\pnpm\store\v3
# pnpm сразу подсветит проблему, если код импортирует
# пакет, не объявленный в package.json своего модуля —
# он просто не будет доступен через строгие симлинки
// package.json — команды одинаковы независимо от менеджера
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
Дополнительные вопросы
- Что такое workspaces (монорепозитории) и как их поддерживают npm, yarn и pnpm?
- Почему lock-файл обязательно нужно коммитить в репозиторий?
- Как pnpm экономит место именно на Windows, где хардлинки работают иначе, чем на Linux/macOS?