Я думаю, все, кто использует node.js, понимает про что эта картинка.

npm - это ужасный менеджер пакетов. В этом признавался даже сам создатель node.js. Npm для каждого вашего проекта создает папку node_modules, в которую он качает из интернета и сохраняет на диске каждый пакет из всей иерархии зависимостей.

Если у вас 100 проектов с одними и теми же зависимостями, то npm 100 раз скачает из интернета и сохранит на диске 100 копий одних и тех же пакетов. Ему плевать. Популярный yarn, к сожалению, делает то же самое.

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

Знакомьтесь - pnpm: https://pnpm.io/ru/

Что делает pnpm? Он делает то, что должен был делать npm с самого начала.

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

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

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

Я правда не понимаю, почему на Хабре нет ни одной статьи про pnpm.

Теперь одна статья есть.

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


  1. dimuska139
    03.11.2021 18:04
    +23

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


    1. halfcupgreentea
      03.11.2021 18:24
      -6

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


      1. dolfinus
        03.11.2021 21:48
        +31

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


        1. halfcupgreentea
          03.11.2021 21:50
          -4

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


          1. D0001
            04.11.2021 00:53
            +10

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

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

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


        1. kornell
          03.11.2021 22:52
          +3

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

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

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

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

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

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

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


          1. dolfinus
            03.11.2021 23:13
            +3

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

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


            1. kornell
              03.11.2021 23:30

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

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


        1. saboteur_kiev
          04.11.2021 01:42

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

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


          1. dolfinus
            04.11.2021 01:50
            +1

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


          1. gecube
            04.11.2021 09:52

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


          1. Telmah
            04.11.2021 14:56

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

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


        1. jMas
          04.11.2021 02:53
          +1

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

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


          1. dolfinus
            04.11.2021 10:40
            +3

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

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

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

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


        1. Enverest
          04.11.2021 20:52

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


      1. ivankudryavtsev
        04.11.2021 04:41
        +2

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

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


    1. dark_ruby
      03.11.2021 18:38
      +7

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


      1. leon0399
        03.11.2021 21:28
        +13

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


      1. dimuska139
        03.11.2021 21:42

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


      1. fiftin
        03.11.2021 22:29
        +3

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


        1. D0001
          04.11.2021 11:34
          +1

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


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

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


          1. fiftin
            09.11.2021 11:15

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


    1. fiftin
      03.11.2021 22:31
      +3

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


      1. saboteur_kiev
        04.11.2021 01:43
        +1

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


        1. fiftin
          09.11.2021 11:12

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

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


    1. fancy-apps
      04.11.2021 04:45

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

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


      1. Saiv46
        04.11.2021 08:54

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


    1. larixer
      04.11.2021 18:41

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


      1. dimuska139
        04.11.2021 18:47

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


        1. larixer
          04.11.2021 18:53

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


          1. dimuska139
            04.11.2021 20:33

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


            1. larixer
              04.11.2021 21:41

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


              1. dimuska139
                04.11.2021 21:48

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


                1. larixer
                  04.11.2021 22:01

                  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.


                  1. dimuska139
                    04.11.2021 22:09

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


  1. halfcupgreentea
    03.11.2021 18:04
    +8

    Yarn PnP делает то же самое + еще ускоряет запуск ноды за счет экономии на чтениях с диска.


    1. evnuh
      03.11.2021 18:42

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


      1. halfcupgreentea
        03.11.2021 21:54
        +2

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


    1. funca
      03.11.2021 23:20

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

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

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

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

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

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


      1. halfcupgreentea
        03.11.2021 23:36
        +7

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

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


        1. funca
          03.11.2021 23:49

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

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


          1. halfcupgreentea
            04.11.2021 00:36
            +1

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


            1. funca
              04.11.2021 00:40

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


  1. APXEOLOG
    03.11.2021 19:10
    +3

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

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

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


    1. eyudkin
      03.11.2021 20:00
      +1

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


      1. APXEOLOG
        03.11.2021 20:29
        +2

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


        1. gatoazul
          03.11.2021 20:44
          +1

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


        1. faiwer
          03.11.2021 22:15
          +11

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


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


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


        1. Lynn
          04.11.2021 13:51

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


  1. pullover
    03.11.2021 20:18
    -3

    появятся новые версии, хэши устареют и опять надо качать пол-интернета


  1. DmitryOlkhovoi
    03.11.2021 20:58
    +6

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

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


    1. Saiv46
      03.11.2021 21:29

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


    1. Lynn
      04.11.2021 13:55

      1. DmitryOlkhovoi
        06.11.2021 11:59

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


  1. Fen1kz
    03.11.2021 21:19
    +2

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


    1. gecube
      03.11.2021 22:17
      -1

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


    1. shybovycha
      03.11.2021 23:52
      +2

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

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

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


      1. funca
        03.11.2021 23:59

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


        1. shybovycha
          04.11.2021 01:06
          +1

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

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

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


    1. VladVR
      04.11.2021 14:05
      +1

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

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

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

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


  1. alemiks
    03.11.2021 23:02
    +1

    За счет этого он работает в разы быстрее что 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 от силы?


    1. funca
      04.11.2021 00:25

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


    1. Regis
      04.11.2021 00:27
      +8

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

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


      1. nick1612
        04.11.2021 10:40
        +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 экосистеме доведена до абсурда, и как мне кажется, делая очередной пакетный менеджер вы решаете не ту проблему.


        1. polearnik
          04.11.2021 12:47
          +2

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


          1. nick1612
            04.11.2021 13:08
            +3

            Вы шутите?

            $ 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 строчки?


            1. extempl
              04.11.2021 15:05

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

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


              1. nick1612
                04.11.2021 19:59
                +1

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


                1. extempl
                  05.11.2021 07:17
                  -1

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

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

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

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

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

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

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


                  1. nick1612
                    05.11.2021 16:29

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

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

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

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

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

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

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


                    1. extempl
                      05.11.2021 21:54
                      +1

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

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


                      1. nin-jin
                        06.11.2021 06:01

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


                      1. extempl
                        06.11.2021 06:37

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


      1. PsyHaSTe
        18.11.2021 13:56

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


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

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


    1. Devoter
      05.11.2021 09:11
      +1

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


  1. akazakou
    04.11.2021 00:50
    +2

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


  1. pavelsc
    04.11.2021 03:13
    +4

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


    1. Metotron0
      04.11.2021 04:30

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


    1. shushu
      04.11.2021 05:17
      +2

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

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


      1. nin-jin
        04.11.2021 06:18
        -1

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


        1. shushu
          04.11.2021 07:23
          +1

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

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


  1. Psychosynthesis
    04.11.2021 05:07

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


    1. dasFlug
      04.11.2021 07:24

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


      1. larixer
        04.11.2021 16:55

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


        1. dasFlug
          04.11.2021 23:01

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


          1. larixer
            05.11.2021 08:17

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


  1. maxzhurkin
    04.11.2021 10:01
    +3

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


    1. Lynn
      04.11.2021 13:58

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


  1. aamonster
    04.11.2021 11:09
    +1

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


    1. Lynn
      04.11.2021 13:59
      +1

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


      1. larixer
        04.11.2021 16:52

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


        1. aamonster
          04.11.2021 18:24

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

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


          1. larixer
            04.11.2021 18:32
            +2

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


          1. ratijas
            05.11.2021 16:10

            потому что https://xkcd.com/2531/


  1. Singaporian
    04.11.2021 15:55

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

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

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


    1. gecube
      04.11.2021 16:21

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


      1. Singaporian
        04.11.2021 17:33
        -1

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


    1. Lynn
      04.11.2021 20:09

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

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

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


      1. Singaporian
        04.11.2021 20:18

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

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


        1. Ndochp
          06.11.2021 15:53

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


          1. Singaporian
            06.11.2021 16:19

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


            1. Ndochp
              06.11.2021 16:51

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


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


              1. Singaporian
                06.11.2021 19:03

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

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


                1. Ndochp
                  06.11.2021 19:23

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


  1. Arqwer
    04.11.2021 16:44
    +1

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

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


    1. napa3um
      04.11.2021 19:27
      +4

      BTRFS


      1. funca
        05.11.2021 01:57

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

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


  1. larixer
    04.11.2021 16:46

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


  1. SergeiMinaev
    04.11.2021 17:26
    +5

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

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

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


    1. funca
      05.11.2021 01:23

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


    1. gresolio
      05.11.2021 14:53

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


      1. dimuska139
        05.11.2021 14:55
        +1

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


  1. VladVR
    05.11.2021 06:06

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

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


    1. Lynn
      05.11.2021 10:12

  1. greybax
    08.12.2021 02:29

    дак уж начали еще с 2019 года https://alfilatov.com/posts/is-your-npm-install-still-too-slow/