Pnpm - альтернативный менеджер пакетов для Node.js. Это прямая замена npm, но быстрее и эффективнее.

Как быстро? В 3 раза быстрее! (по состоянию на март 2017 г.) См. тесты здесь.

Почему эффективнее? Когда вы устанавливаете пакет, мы храним его в глобальном хранилище на вашем компьютере, а затем вместо копирования создаем из него жесткую ссылку. Для каждой версии модуля на диске всегда хранится только одна копия. Например, при использовании npm или yarn, если у вас есть 100 пакетов, использующих lodash, у вас будет 100 копий lodash на диске. Pnpm позволяет сэкономить гигабайты дискового пространства!

Почему не пряжа?

TBH, я был очень разочарован, когда Yarn стала достоянием общественности. Я активно участвовал в pnpm в течение нескольких месяцев, и нигде не было новостей о Yarn. Информация о его разработке не разглашалась.

Через несколько дней я понял, что Yarn - это всего лишь небольшое улучшение по сравнению с npm. Хотя он ускоряет установку и имеет несколько хороших новых функций, он использует ту же плоскую структуру node_modules, что и npm (начиная с версии 3).

А сглаженные деревья зависимостей порождают ряд проблем:

  1. модули могут получить доступ к пакетам, от которых они не зависят
  2. алгоритм выравнивания дерева зависимостей довольно сложен
  3. некоторые пакеты необходимо скопировать в папку node_modules одного проекта

Кроме того, есть проблемы, которые Yarn не планирует решать, например проблема использования дискового пространства. Поэтому я решил и дальше тратить свое время на pnpm, и с большим успехом. На данный момент (март 2017 г.) pnpm имеет все дополнительные функции, которые Yarn имеет по сравнению с npm:

  1. безопасность. Как и Yarn, pnpm имеет специальный файл с контрольными суммами всех установленных пакетов для проверки целостности каждого установленного пакета перед выполнением его кода.
  2. автономный режим. pnpm сохраняет все загруженные архивы пакетов в локальном зеркале реестра. Он никогда не делает запросов, если пакет доступен локально. Параметр --offline позволяет вообще запретить HTTP-запросы.
  3. скорость pnpm не только быстрее, чем npm, но и быстрее, чем Yarn. Это быстрее, чем Yarn, как с холодным, так и с горячим кешем. Yarn копирует файлы из кеша, тогда как pnpm просто связывает их из глобального хранилища.

Как это возможно?

Как я упоминал ранее, pnpm не сглаживает дерево зависимостей. В результате алгоритмы, используемые pnpm, могут быть намного проще! Вот почему возможно, что только 1 разработчик сможет поспевать за десятками участников Yarn.

Итак, как pnpm структурирует каталог node_modules, если не путем сглаживания? Чтобы понять это, мы должны вспомнить, как папка node_modules выглядела до npm версии 3. До npm @ 3 структура node_modules была предсказуемо и чисто, поскольку каждая зависимость в node_modules имела свою собственную папку node_modules со всеми ее зависимостями, указанными в package.json.

node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json

У этого подхода были две серьезные проблемы:

  • часто пакеты создавали слишком глубокие деревья зависимостей, что вызывало проблему с длинными путями к каталогам в Windows
  • пакеты копировались и вставлялись несколько раз, когда они требовались в разных зависимостях

Чтобы решить эти проблемы, npm переосмыслил структуру node_modules и придумал выравнивание. В npm @ 3 структура node_modules теперь выглядит так:

node_modules
├─ foo
|  ├─ index.js
|  └─ package.json
└─ bar
   ├─ index.js
   └─ package.json

Дополнительные сведения о разрешении зависимостей npm v3 см. В разделе Разрешение зависимостей npm v3.

В отличие от npm @ 3, pnpm пытается решить проблемы, которые были у npm @ 2, без выравнивания дерева зависимостей. В папке node_modules, созданной pnpm, все пакеты имеют свои собственные зависимости, сгруппированные вместе, но дерево каталогов никогда не бывает таким глубоким, как в npm @ 2. pnpm сохраняет все зависимости плоскими, но использует символические ссылки для их группировки.

-> - a symlink (or junction on Windows)
node_modules
├─ foo -> .registry.npmjs.org/foo/1.0.0/node_modules/foo
└─ .registry.npmjs.org
   ├─ foo/1.0.0/node_modules
   |  ├─ bar -> ../../bar/2.0.0/node_modules/bar
   |  └─ foo
   |     ├─ index.js
   |     └─ package.json
   └─ bar/2.0.0/node_modules
      └─ bar
         ├─ index.js
         └─ package.json

Чтобы увидеть живой пример, посетите репозиторий sample pnpm project.

Хотя пример кажется слишком сложным для небольшого проекта, для более крупных проектов структура выглядит лучше, чем то, что создается с помощью npm / yarn. Посмотрим, почему это работает.

Прежде всего, вы могли заметить, что пакет в корне node_modules - это просто символическая ссылка. Это нормально, поскольку Node.js игнорирует символические ссылки и выполняет реальный путь. Таким образом, require('foo') выполнит файл в node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/foo/index.js, а не в node_modules/foo/index.js.

Во-вторых, ни один из установленных пакетов не имеет собственной папки node_modules внутри своих каталогов. Так как же foo требовать bar? Давайте посмотрим на папку, содержащую пакет foo:

node_modules/.registry.npmjs.org/foo/1.0.0/node_modules
├─ bar -> ../../bar/2.0.0/node_modules/bar
└─ foo
   ├─ index.js
   └─ package.json

Как вы видете

  1. зависимости foo (который представляет собой просто bar) установлены, но на один уровень выше в структуре каталогов.
  2. оба пакета находятся в папке с именем node_modules

foo может потребовать bar, потому что Node.js ищет модули в структуре каталогов до корня диска. И foo также может потребовать foo, потому что он находится в папке с именем node_modules (да, это то, что делают некоторые пакеты).

Вы убеждены?

Просто установите pnpm через npm: npm install -g pnpm. И используйте его вместо npm, когда хотите что-то установить: pnpm i foo.

Также вы можете прочитать дополнительную информацию в pnpm GitHub repo или pnpm.js.org. Вы можете подписаться на pnpm в Твиттере или обратиться за помощью в pnpm Gitter Chat Room.

(Этот пост изначально был размещен на https://www.kochan.io/nodejs/why-should-we-use-pnpm.html)