Как стать автором
Обновить

Комментарии 115

Насколько я знаю, многие бэк на NodeJS разрабатывают в Docker-контейнерах. Стало быть, в каждом контейнере будут свои зависимости, переиспользовать их между контейнерами через символические ссылки всё равно не получится.

Можно монтировать volume с локальным хранилищем

Это антипаттерн. Контейнеры - это про изоляцию приложения вместе с его набором зависимостей и окружением от остального мира. Шарить код между контейнерами через volume значит идти против основной идеи докера.

Ну а вам шашечки или ехать?

В данном случае нет смысла, слишком много возни.

Как вариант можно ставить пакеты в контейнеры по очереди, в одной и той же последовательности, начиная с самых наиболее используемых, тогда контейнеры будут использовать слои общие) Но это тоже извращение какое-то)

Вот для dev машины этот менеджер пакетов интересен.

Думаю, что для сценария CI/CD это достаточно удачное решение.

Например, если используется Jenkins и универсальный build контейнер для NodeJs приложений, то такому контейру очень выгодно иметь актуальный репозиторий без накладных расходов, которые непременно сопровождают npm.

Вообще, для production я не встречал предложений использовать какой-нибудь ng serve (Angular), сервер в этом режиме даже заботливо предупреждает о том, что так не стоит делать.

Исходя из этой логики, любое test/staging/prod окружение по-хорошему работает с (далее конкатенировать по степени важности) собранным, упакованным, перепакованным, оптимизированным, обфусцированным, минифицированным кодом. А значит, обсуждая npm/yarn/pnpm, логично предположить, что либо речь о перчисленных выше шагах, либо это сугубо локальное окружение.

Первый вариант выше уже подробно раскрыт, а во втором не так важно куда идти - к докеру или от него.

Опять же, можно прокидывать fs в докер, а можно держать image/контейнер, который использует преимущества pnpm и в случае image просто обнуляется собранный кэш, контейнер же позволяет инкрементально наращивать репозиторий.

И все ради благой цели - быстро и надежно доставлять код!

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

Для этого есть buildkit и опция RUN --mount=type=cache в Dockerfile. А с внешними симлинками в докере легко напортачить, могут начать вести на несуществующие пути, если ошибиться с монтированием.

В целом с Вами соглашусь, есть вещи, которые лучше не делать, потому что потом дороже разбираться. В любом случае, в обсуждаемых сценариях можно комбинировать разные подходы. Volumes табуировать тоже не стоит, просто надо понимать принципы работы AUFS, не забывать про uid/gid пользователей хост системы и контейнера. Так что, на мой взгляд, все сводится именно к осознанности в практике применения этих строительных блоков.

*повторение последнего предложения из предыдущего комментария*

Насколько я себе представляю, npm это не про рантайм в продакшене, а про билд.

Для билда вполне себе отличное решение монтировать volume с локальным хранилищем где будет глобальный репозиторий npm или maven

Вот только на этапе docker build volume не подключаются, они есть только в runtime

Ну, npm и pnpm serve в проще отдельные ребята очень даже используют…

"После чего в папке node_modules он создает символические ссылки на эти файлы - вместо того чтобы их каждый раз копировать"

и куда будут вести эти ссылки после того как вы сбилдите контейнер и захотите запустить его гдето?

Хм, любые догмы - это тоже антипаттерн?

Если pnpm нормально контролит зависимости (если произведены соответствующие тесты), то в чем собственно проблема?

Лично я не против самого инструмента - если он реально помогает на этапе разработки, то супер.

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

А все попытки притащить инструмент костыльными путями (через volume, через монтирование папки node_modules с хоста, и т.п.) приводят к тому, что возникают проблемы с изоляцией зависимостей и воспроизводимостью сборки. На проде это критично, поэтому крайне не рекомендую так делать.

Максимум где такое можно использовать - локальная разработка в докере, но не более. А для ускорения продовой сборки есть другие варианты, один с buildkit я уже описал выше.

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

Вообще, за пределами Dev-среды, кажется, что проблема надумана (по крайней мере для Docker). Просто сделайте base image со всеми зависимостями, он не будет дублироваться между контейнерами.

скорее это сделано чтоб решать проблему на время разработки, а не в продакшене. Т.е. если у вас зачекаутено много реп каждая со своими (часто повторяющимися зависимостями) то оно не будет пожирать место на диске. В продакшене понятное дело что всё по контейнерам и там уже не важно.

И получается разное окружение в разработке и продакшене 🤷‍♂️

Так многие локально в Docker разрабатывают

Я думал смысл контейнеров как раз иметь одно и тоже окружение при разработке и на проде. У нас например не используются контейнеры на продакшне. Я чаще использую контейнеры как раз при разработке.

Не совсем, разработчик может что угодно делать на своей машине, а вот то, что протестировали и то, что пошло в прод, должно быть идентично и это как раз легко делается с помощью Docker.


Единственно, что меня смущает, это то, что некоторые библиотеки (или примеры с ними), требуют задавать некоторые параметры в момент билда... вот это уже ломает всё (например требуют задавать код счетчика или внешний url).

Потому что смысл в том, чтобы скомпилировать образ, выложить его в свой реестр, потом отправить на тест, потом на прод (тот же самый контейнер), а тут получаются разные контейнеры на тесте и проде.

Но это не рашает проблему "на моей машине работает".

Интересно, какой процент людей разрабатывают в докере? Может есть какая-нибудь статистика у кого-нибудь?

Что значит "разрабатывают в докере" ?
Запускать билды в докере - отличная идея, особенно если этот докер в кубере/опенщифте и сборка идет в каком-нить CI. У тебя столько нод, сколько тебе нужно и каждая идеально чистая.

Что значит "разрабатывают в докере" ?

Так написано в коммента, на который я ответил) Я не вижу смысла запускать NodeJS-приложение в докере при разработке (для отладки). На мой взгляд это только все усложняет.

Практически любой Node.JS проект это 90% dev/build time зависимостей, которые нужны для разработки и сборки локально. Когда вы собираете Docker image вы просто копируете туда конечный бандл, на многих моих проектах внутри даже npm-а нет.

В контейнерах нужно писать npm install --only=prod , а это, как я сказал выше - 10% от всех зависимостей как правило.

Вообще для контейнеров и прочего CI уже есть командаnpm ci --production, делающая то же самое, но быстрее (при условии что есть package-lock.json).

pnpm будет экономить место и время установки node_modules за счет использования симлинков в вашем Docker-контейнере. Если симлинки не совместимы по каким-то причинам с вашим проектом вы также можете попробовать Yarn 3.0+, он поддерживает классическую структуру node_modules с хардлинками на контенто-адресуемое хранилище (nmMode: hardlinks-global) либо локально в проекте (nmMode: hardlinks-local), как результат - очень хорошая совместимость с экосистемой пакетов npm + экономия места в Docker-контейнере

Не очень понял. У меня есть 2 разных Docker-контейнера, где пакеты частично пересекаются. Как именно симлинки могут ссылаться на содержимое других контейнеров? Особенно учитывая, что контейнер другого проекта может быть вообще не запущен в этот момент.

Симлинки не будут ссылаться на содержимое других контейнеров, симлинки будут внутри node_modules и за счет этого установка будет быстрее и места в контейнере будет требоваться меньше

Каким образом? Если симлинк на что-то должен ссылаться, то это "что-то" должно быть установлено в контейнере. Если у вас несколько контейнеров, и в каждом из них надо установить, скажем, typeorm, то эта библиотека будет продублирована в каждом из контейнеров. То есть в любом случае получается дублирование, т.к. typeorm будет в обоих контейнерах.

Есть разное дублирование. Да, дублирование между контейнерами pnpm не уменьшает. pnpm решает другую задачу - дублирование файлов и пакетов внутри node_modules и таким образом размер Docker-контейнера будет меньше.

Но ведь и в npm есть команда dedupe. Она не решает эту проблему?

npm dedupe, уменьшает дублирование, которое возникает из-за разницы в разрешении версий запрошенных пакетов по пересекающимся semver ranges, например c@1.0.x и c@~1.0.9 могут быть разрезолвлены в c@1.0.3 и в c@1.0.10 в разных частях графа зависимостей, а могут быть разрезолвлены оба в с@1.0.10. npm dedupe анализирует lock файл и пытается проапгрейдить resolution для зависимостей по пересекающимся semver ranges с целью, чтобы их было как можно меньше.

Это уменьшает node_modules и убирает из него дублирование, но не полностью, там все равно остается существенное дублирование, из-за конфликтов версий пакета с одним и тем же именем по разным путям графа, или из-за того, что без дублирования невозможно соблюсти гарантии peer dependencies.

Понял, спасибо!

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

Да, но это уже и так экономит львиную часть занимаемого места. То, что в локальном репо будут две версии одного пакета с 90% одинаковыми файлами мне видится уже сильно меньшей проблемой.

Я не скажу, что это работает прям супер. Время "холодной" установки pnp (пока не сформирован локальный кэш и не подсчитаны все зависимости) больше чем у yarn 1.

Ладно, допустим это не проблема - они предлагают кэш формировать один раз в жизни, после чего наступает полный zero install.

Для приложения, созданным Create React App, в кэше я насчитал свыше 2000 архивов, общим объемом около 120Mb. Что дальше с этим делать?

Можно скоммитить кэш вместе с исходниками в git. Тогда время повторной установки действительно сокращается на порядок у всей вашей команды и даже на CI. Но размер репозитория уже не радует. Более того, эти архивы начинают в нем версионироваться и спустя какое-то время после череды обновлений пакетов, git со всей этой историей будет весить уже гигабайты.

Напрашивается вариант переложить кэш в Git LFS. Нужно лишь установить приложение и добавить конфиг. В таком случае в репозитории будут храниться только ссылки, а сами пакеты - снаружи. Но месячные лимиты по трафику у GitHub для LFS на таких объемах выжираются за пару дней. Нужно искать какие то альтернативы.

В общем, pnp штука интересная, но требует специфичной инфраструктуры. Возможно у Фейсбука это все давно есть, или я что-то делаю не правильно, но пока обычный кеширующий npm сервер в связке со стандартным npm (или yarn 1), в плане ускорения установки, выглядит проще, удобнее и надёжнее.

А зачем хранить кеш в репозитории?

Для детерминированных зависимостей есть yarn.lock, его должно быть достаточно.

У yarn.lock есть ряд фатальных недостатков. Например, он привязывается к конкретному зеркалу npm, который использовал разработчик в момент установки зависимостей. Но ваш CI сервер может захотеть использовать другое зеркало пакетов, которое ему ближе. Сам же npmjs.com стабильностью не отличается.

Поэтому вместо него уже придумали .pnp.cjs (конфиг на JavaScript, Карл! за что, кстати, их невзлюбили разработчики Typescript, ну да это отдельная история). А развитие мысли привело к https://yarnpkg.com/features/zero-installs - запуску приложений без установки зависимостей.

Ок, согласен. Но тогда уж проще устроить для CI такой же локальный репо с кешом пакетов и монтировать его в каждый агент. Так еще стабильнее, чем ходить куда то по сети :)

Видимо фейсбук как то так и поступает.

В результате, если у вас 100 проектов с одинаковыми зависимости - pnpm полезен в интернет и сохранит пакеты на диске только 1 раз. Остальные 99 раз он лишь создаст символические ссылки.

Я не спорю, но надо отметить, что в процессе разработки не так уж часто встречаются случаи, когда нужно регулярно переустанавливать с нуля одни и те же пакеты в сотне проектов. Как правило ты делаешь `npm i` один раз после пулла проекта(ов) и изредка вызываешь, когда зависимости обновляются. Если пакет не поменялся и есть в папке модулей - он не будет переустанавливаться.

Вещь полезная, но "всем разработчикам" он не нужен - его применение имеет смысл в специфичных кейсах

А почему бы и не всем разработчикам? Хотя бы ради экономии личного места на диске и трафика, чтобы в рамках одного проекта какой-нибудь leftpad лежал один раз, а не 100500.

Может это мое личное предубеждение против симлинков, но я сталкивался с конкретной проблемой, когда фреймворк, который я использовал, не поддерживал их (долгое время, пока не пофиксили. Хотя даже после того как пофиксили я ловил там баги)

webpack-web-dev, например, иногда чудит при симлинках, не желая замечать, что файл обновился.

Я года четрые назад пытался пользоваться npm link. Спустя некоторое время свыкся с тем, что вся экосистема меня люто ненавидит. Я был кем-то вроде alpha-тестера. У меня ломалось вот буквально всё, что только могло сломаться. Оказалось что почти никто не разрабатывает свой код с учётом поддержки sym-линков. Отваливались тест-runner-ы, сборщики, backend библиотеки, docker (кажется), линтеры,… По сути сложно вспомнить что продолжало работать.


В какой-то момент я понял, что я 2 трети времени работаю над проектов, одну треть — чиню поддержку sym-линков. Даже сам npm link ломался при любой операции с npm.


Возможно сейчас всё уже не так плохо, но вышеописанный опыт отбил всякое желание прикасаться к sym-линкам со стороны JS окружений.

А это похоже на ошибку в тексте. Насколько я помню на самом деле в pnpm используются hardlink на файлы. Во всяком случае я предлагал именно это

появятся новые версии, хэши устареют и опять надо качать пол-интернета
pnpm создает на вашем компьютере единый репозиторий (по крайней мере единый для вашего пользователя) npm-пакетов. Он создает контентно-адресуемую файловую-систему, как git. (Для тех кто не знает что это, поясню — каждый файл получает имя, равное хэшу от его содержания, а значит файлы с одинаковым содержанием никогда не повторяются дважды). После чего в папке node_modules он создает символические ссылки на эти файлы — вместо того чтобы их каждый раз копировать.

Каждый день какой-то из модулей обновляется. Как чистятся эти некропакеты в централизованном месте?

В теории, при добавлении/удалении пакета изменения также вносятся и в центральное хранилище, для последующей чистки от неиспользуемых файлов.

В среднем после каждого инстала, надо в ручную запускать очистку. Может они сделают это по дефолту

Просветите, а разве npm не кеширует файлы на диске чтобы не качать их несколько раз?

это не помогает!
а pnpm действительно вещь! Коллеги уже перешли и скорость/стабильность сборки повысилась в разы!!!!

Да, но он это делает для каждой версии и для каждого модуля зависящего от данного файла.

Грубо говоря, если у вас проект зависит от lodash 1.0.1 а его зависимость зависит от lodash 1.0.2, npm выкачает обе версии (даже если 90% исходников lodash между версиями 1.0.1 и 1.0.2 будут идентичными) и положит в node_modules каждого из модулей.

А pnpm, насколько я понял, создаст что-то вроде дерева git и выкачает только те 10% отличающихся исходников, а в остальном - понасоздает тучу симлинков.

В случае npm зависимостей, выкачивает он все равно пакеты целиком (они же в tar.gz). Но потом при установке вроде как может постараться дедуплицировать файлы (честно говоря сам туда не смотрел, только с чужих слов). Другая стратегия сводится к тому, чтобы для пакетов, различающихся только patch версией (третья цифра), устанавливать лишь одну версию - самую старшую. Тут делается допущение, что патч версии не ломают обратной совместимости.

допущение крайне слабое - авторы пакетов часто пренебрегают semver.

но и, получается, npm вроде как плохо старается с дедупликацией.

к тому же, как кто-то выше верно подметил, следующим шагом приводящим к дикой дупликации является сборщик - тот же вебпак запросто порождает тучу дубликующего кода. при чем проблемма не только с зависимостяими - всякие ES6 штуки (вроде fat arrow, identity function, etc.) повторяются неимоверное количество раз. не так давно была статья на эту тему, Только 39% функций в node_modules уникальны в дефолтном Angular проекте

Проблема, кстати, не в скорости интернета. Интернет не был медленным когда сидели в офисе, и тем более не медленный дома на удаленке.

Проблема, по крайней мере у меня, в том, что медленный диск. Хоть бы и ссд, но все равно раза в 2 медленнее, чем диск на домашнем компе, да еще и корпоративный антивирус, который замедляет это еще этак раз в 5. Итого классическая операция "снести все и сделать npm i начисто" занимает минуты четыре, вместо 15 секунд.

А работать на домашнем компе, к сожалению, нельзя, не настроить доступ в корпоративную сеть.

А вообще - стало интересно попробовать, как я понял ничего ведь не меняется, в любое время можно отказаться.

За счет этого он работает в разы быстрее что npm
под «разы» вы имели в виду 2? Из официальной рекламки:
pnpm is up to 2x faster than the alternatives

Основная фича, я так понял
As a result, you save a lot of space on your disk proportional to the number of projects and dependencies

Серьёзно? Это проблема разрабов с диском на 512 гигов? Сколько там этот ваш node_modules занимает? Мегов 300-500 от силы?

Мне кажется они решают проблемы не столько разрабов, сколько своей инфраструктуры. Можно предложить, что на объемах Фейсбука, им существенно выгоднее держать общее хранилище пакетов разделяемых между кучей приложений (может даже в отдельном реплицируемом volume для docker как советуют выше), нежели выстраивать уникальные по сути директории node_modules под каждое приложение. Фактически, вместо того, чтобы городить дерево зависимостей на файловой системе, они сделали сами зависимости плоскими и иммутабельными (zip архивы), а структуру отношений между ними (которая специфична для каждого приложения) записывают в небольшой по размеру конфиг.

300-500 в одном проекте, 300-500 в другом... Не успеешь и опомниться, как несколько гигабайт заняты под дублирующиеся файлы.

Ну и вообще с таким подходом не удивительно, что софт всё толще и медленнее с каждым годом. Ждём, когда через 10 лет будут говорить "Разве это проблема для пользователей с диском в 5-10 Тб. Сколько там ваши пакеты весят, 3-5Гб от силы?" :)

Вот меня тоже несколько пугает, что 300-500MB для пакетов, это считается норм.

Разработчики совсем оторвались от реальности. Никого не удивляет, что для того, чтобы собрать какую-то страничку с формочкой, тебе нужно качать такие объемы с десятками непонятных пакетов.

Я некоторое время назад разбирался с поломкой в webpack (после очередного обновления) и решил посмотреть, что там за модули он с собой тянет. Как оказалось со времен left-pad ничего особо не поменялось. Теперь у нас есть не менее важный пакет https://github.com/tarruda/has:

Object.prototype.hasOwnProperty.call shortcut

Вот его код:

'use strict';

var bind = require('function-bind');

module.exports = bind.call(Function.call, Object.prototype.hasOwnProperty);

Заметьте, что он даже имеет зависимость 'function-bind'.

Также есть пакет https://github.com/jonschlinkert/isobject с чуть более замысловатой имплементацией, но хоть без зависимостей:

/*!
 * isobject <https://github.com/jonschlinkert/isobject>
 *
 * Copyright (c) 2014-2017, Jon Schlinkert.
 * Released under the MIT License.
 */

'use strict';

module.exports = function isObject(val) {
  return val != null && typeof val === 'object' && Array.isArray(val) === false;
};

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

не вижу никаких проблем с пакетами типа isObject isArray итд. Корректные проверки с учетом нюансом выливаются в нетривиальный код. К такому условию как в функции isObject придете через пару итераций и то столкнувшись с парочкой багов. Если ктото уже прошел этот путь то спасибо за расшаренный опыт и нужную библиотеку

Вы шутите?

$ wc -l node_modules/isobject/*
   5 node_modules/isobject/index.d.ts
  12 node_modules/isobject/index.js
  20 node_modules/isobject/LICENSE
 119 node_modules/isobject/package.json
 121 node_modules/isobject/README.md
 277 total

Каждый раз качать 5 файлов и 277 строк, ради функции в 3 строчки?

Строго говоря в результирующий бандл войдёт только index.js. Но вообще да, для этого есть тот же lodash который при сборке тем же вебпаком может не включать в бандл тот код, функции которого не были использованы (я полагаю, именно для этого созданы такие пакеты-одиночки).

Хотя если во всём проекте из того же lodash вы используете только isObject, то есть ли в этом смысл? Не всё так просто и однозначно, об этом должен думать человек добавляющий пакеты в проект.

Тут же стоял вопрос не размера конечного бандла, так как все современные сборщики умеют делать tree shaking и использование pnpm в этом плане ничего не решает. Речь идет о том, что вместо того, чтобы написать 3 строчки кода, люди непонятно зачем тянут в проект зависимость в 277 строк, которую они никак не контролируют. То есть webpack и множество других утилит, которыми пользуются тысячи людей, стоит на вот таком вот фундаменте.

А где я говорил про pnpm? Мой комментарий только относительно переиспользования кода, переиспользования хелперов, без которых код часто ломается (i.e. isObject/isArray) и использование одиночных пакетов vs lodash.

вместо того, чтобы написать 3 строчки кода, люди непонятно зачем тянут в проект зависимость

Тут есть нюанс. Ну мы с вами напишем корректный isObject в три строчки сходу, и всё будет ок. Но я предпочту чтоб джуны в моей команде использовали lodash вместо того, чтоб писать свои сравнения, которые потом что-то сломают.

Да, это решается code-review (но зачем?). Да, можно открыть исходники lodash и скопировать имплементацию. Но чем это отличается от запекания текущей версии lodash в package-lock и использовании его как пакета?

Защита от удаления пакета? Теоретически возможно, но маловероятно.

Защита от того, что используемый код изменится в будущих минорных версиях? Используйте точные версии в package.json и package-lock.

Защита что кто-то сломает npm репозиторий и поменяет текущие версии в нём? Всё может быть, но это уже паранойя.

А где я говорил про pnpm? Мой комментарий только относительно переиспользования кода, переиспользования хелперов, без которых код часто ломается (i.e. isObject/isArray) и использование одиночных пакетов vs lodash.

Вы не говорили, но статья изначально про то как с помощью pnpm сэкономить место и ускорить работу.

Тут есть нюанс. Ну мы с вами напишем корректный isObject в три строчки сходу, и всё будет ок. Но я предпочту чтоб джуны в моей команде использовали lodash вместо того, чтоб писать свои сравнения, которые потом что-то сломают.

Да, это решается code-review (но зачем?). Да, можно открыть исходники lodash и скопировать имплементацию. Но чем это отличается от запекания текущей версии lodash в package-lock и использовании его как пакета?

Я соглашусь, что если рассматривать ситуацию в общем, то все не так однозначно, но тут скорее вопрос к контрибьютерам webpack и его зависимостей - почему нельзя заменить этот и подобные пакеты несколькими строчками кода? Или они тоже боятся, что джуны все сломают?

Защита что кто-то сломает npm репозиторий и поменяет текущие версии в нём? Всё может быть, но это уже паранойя.

Есть вариант, что кто-то может взломать одного из авторов и залить туда malware, что совсем не есть параноя и происходит достаточно часто.

кто-то может взломать одного из авторов и залить туда malware

Да, и для этого стоит использовать точные версии (без `^`) и package-lock, так как malware попадёт только в новые версии, но не в существующие.

Звучит как "давайте ходить по минному полю по одиночке, авось пронесёт".

Вы излишне драматизируете. Звучит как "давайте гулять по всем полям, но знакомый моего знакомого рассказывал, что на одном из них может быть зарыта неразорвавшаяся бомба со времён войны, но это не точно. А ещё её можно вовремя заметить, позвать сапёров и они обезвредят."

Несколько гигабайт? ЖСеры, подержите моё пиво :)


; du -hs work/<project>/target
284G  work/<project>/target

Стата одного моего знакомого. У меня не так печально но все равно норма когда полсотни гигабайт занято всякими умершими пакетами

Лично в моей рабочей директории несколько десятков проектов только на ноде, и большинство из них живые. Есть ещё проекты, которые создаются временно для каких-нибудь экспериментов и потом благополучно удаляются. И да, почти каждый из них тянет за собой 300-500 dev-зависимостей, без которых неудобно работать с проектом. Но вот pnpm, отнюдь - не панацея. Глобальный набор зависимостей имеет свои минусы вроде отстуствия возможности по-быстрому подправить что-то прямо в исходнике установленного пакета при отладке или быстрой тотальной замены зависимостей. Но, кому-то, безусловно, подойдёт.

Если честно, не совсем понимаю какую проблему (кроме дублирования файлов) решает pnpm. Есть npm ci что из локального кеша ставит за́висимости за секунды. Для CI используется кеширование node_modules целиком. Для CD при любом раскладе надо пакеты пихать в контейнер, ибо mount volume не подойдет, т.к. там со временем будет весь npm репозиторий, из-за непоняток что можно удалять или нет (откат к предыдущей версии контейнера к примеру)

"Если у вас 100 проектов с одними и теми же зависимостями" - значит в своей жизни вы свернули куда-то не туда. Проблема далеко не в npm. Напомнило индуса с Андроид маркета с сотней приложений, которому акк забанили :)

Там, где я работал, был шаблон с фреймворком, которым все пользовались для разработок. Чтобы проще было учить новеньких сразу тому, с чем они потом будут работать. И чтобы легче въезжать в код на чужом проекте.

"Если у вас 100 проектов с одними и теми же зависимостями" - значит в своей жизни вы свернули куда-то не туда.

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

В MAM тоже очень много реп, использующих много общих зависимостей, но таких проблем нет. Вся JS экосистема свернула не туда, подстраиваясь под кривую архитектуру NodeJS, вместо её исправления.

Вся JS экосистема свернула не туда, подстраиваясь под кривую архитектуру NodeJS, вместо её исправления.

Вот тут согласен. И мне кажется что тту уже нечего не исправишь :(
Надо все переделывать...

Я только не понял, он использует всё те же пакеты из npm?

Да, pnpm только оптимизирует место на диске которое занимают их локальные копии.

Дело не только в месте. У него еще и скорость установки node_modules выше, за счет меньшего количества файлов. Также он предотвращает доступ к незадекларированным зависимостям (уровень контроля определяется настройками)

Немного не понял о чем вы. Количества каких файлов меньше? Для скачивания из глобального репозитория при выполнении install? Но это просто следствие того что есть локальный репозиторий на который node_modules проекта ссылается. Или это про дедупликацию файлов по содержимому?

Я имел ввиду количество файлов внутри node_modules. Однако, кажется, оно ненамного меньше, не похоже чтобы существенно меньше, даже если аккуратно пытаться определить, а определить тяжело, потому что много факторов на это влияет.

Можно было рассказать хотя бы вкратце, что именно нужно сделать для перехода с npm на pnpm кроме добавления замены npm на pnpm

Ну вкратце вы уже написали :-)

А почему это сделано на симлинках, а не на хардлинках? Ради возможности держать на другом разделе?

Это сделано на хардлинках. Автор поста ошибается

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

А почему для дедупликации внутри node_modules не могут тоже использоваться хардлинки? Для папок нельзя?

И нет ли режима, создающего папки, но использующего хардлинки для файлов (чтобы не было проблем совместимости с симлинками)?

Хардлинки могут использоваться внутри node_modules, но pnpm так не делает. Если использовать хардлинки внутри node_modules, скорость записи пакетов в node_modules будет меньше, потому что на скорость больше всего влияет количество записываемых файлов, а не их общий размер (то что хардлинки экономят место не означает что создание копий мгновенно). Тот режим о котором вы пишете - классическая структура node_modules но с хардлинками для экономии места реализован в Yarn 3.0+, nmMode: hardlinks-local - для использования хардлинков внутри проекта, есть еще nmMode: hardlinks-global - для использования хардлинков, указывающих на общее хранилище адресуемое контентом (примерно так же как делает это pnpm, но без симлинков)

Тогда вопрос.

Например, у меня два проекта: X и Y
В package.json проекта X стоит "vuetify": "2.3.21"
А проект Y требует уже версию "vuetify": "v2.6.0-beta.0"

Какую мне поставит pnpm и как мне объяснить это второму проекту, которому эта версия не подходит?

очевидно, что обе?

Мне очевидно пока обратное. Поставлю от X - ок. Начну ставить Y - он апнет версию от X и теперь X сломается.

Что значит «объяснить второму проекту»?

Ваши проекты вообще ничего не знаю друг о друге, в глобальном хранилище pnpm будут лежать обе версии.

А вот когда вы заведёте проект Z зависящий от "vuetify": "2.3.21", то pnpm переиспользует файлы из глобального хранилища.

"в глобальном хранилище pnpm будут лежать обе версии"

Именно это я и спрашивал.

Так больше того, с учетом того, что 2.3.21 от v2.6.0-beta.0 может отличаться не всеми файлами у вас лежать будут обе, а места занимать как полторы

Стоп, тогда еще интереснее. То есть подходящие файлы хранятся рядом? А как же шастать по библиотеке и смотреть, как она работает? Как дебажить?

Так дебажить не проблема — вы отличено пробежитесь по всем файлам.
проблема исправлять — одно неловкое движение прямо в библиотеке и вы поправили этот модуль во всех своих проектах.


Все файлы лежат на диске (не важно где), на каждый файл лежит заголовок в нужной папке модулей. Если в двух папках модулей должны лежать одинаковые файлы, то в каждом лежит заголовок, указывающий на один и тот же файл.
Это по сути и так было бы в FAT, а в любой нормальной FS эта связка ярлык — файл всегда есть "из коробки". При обычной жизни на один файл есть один ярлык. В некоторых специфичных случаях (WinSxS, описанный, еще когда-то, где часто нужно смотреть с разных сторон на один файл, например при классификации фото по папкам — дача это или котики) ярлыков делают много. При исправлении файла по любому ярлыку он правится у всех. При удалении — удаляется только ярлык. Когда ярлыков не осталось, то помечается к удалению и тело файла. (Вот грубое и не академичное описание хардлинков)

"вы отличено пробежитесь по всем файлам."

Понял. Это можно было сказать более кратко: используются хардлинки.

Просто в статье было про симлинки, ниже в комментариях поправили, что имеются в виду хардлинки.
Мне по вашим уточнениям показалось, что вам не ясен именно механизм хардлинков.

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

И я не удивлюсь, если окажется, что кто-то такую ФС уже сделал.

BTRFS

Ещё xfs и zfs. Но deduplication на таком уровне очень требовательна к ресурсам.

В качестве альтернативы можно посмотреть на DwarFS. Там создатель решал похожую проблему только для perl https://github.com/mhx/dwarfs/blob/main/doc/dwarfs.md .

Yarn 3.0 и выше тоже поддерживает установку с использованием контентно-адресуемой файловой-системы, причем с классическим расположением файлов и директорий в `node_modules`, без симлинков (опции `nodeLinker: node-modules` и `nmMode: hardlinks-global`).

Я, как линуксоид, негодую от предложенного на официальном сайте способа установки:

curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm

Проверил от юзера - оно пытается записать файлы даже не в /usr/local, а сразу в /usr/bin, а может и ещё куда-то. Такой способ установки сродни копированию библиотеки в node_modules вручную. Почему-то в последнее время считается нормальным засирать систему установкой всего подряд в обход пакетного менеджера.

Просто многие устанавливают node.js через nvm или подобное, чтобы была возможность переключаться между различными версиями. В таком случае весь global оказывается в вашей домашней директории. Использовать такой способ установки для системного интерпретатора конечно не нужно.

Многие негодуют, я тоже. Лучше бы статью «Пожалуйста, перестаньте использовать js» :)

Альтернатив так-то особо и нет, по крайней мере, во фронтенде.

Потрогал. Работает, конечно, заметно быстрее, чем npm, но сразу уперся в неработающий сценарий. У меня gulp таски вынесены в отдельный пакет, который в компонент ставится как зависимость, соответственно gulp не является прямой зависимостью текущего компонента, а зависимостью зависимости. И команда pnpx gulp [taskname] при этом не работает. Если установить gulp как прямую зависимость, то не хватает ts-node, далее не хватает тайпскрипта и т.д.

Т.е. все зависимости, которые я унес из каждого компонента в один пакет с тасками должны будут вернуться обратно в каждый компонент, чтобы это работало. Это deal-breaker, делает pnpm неюзабельным для меня.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.