Я думаю, все, кто использует 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)
halfcupgreentea
03.11.2021 18:04+8Yarn PnP делает то же самое + еще ускоряет запуск ноды за счет экономии на чтениях с диска.
evnuh
03.11.2021 18:42Точно ли то же самое? Вроде как он не использует хеши файлов для резолва, а просто хранит все зависимости в одном месте зазипованные, при этом если у вас один пакет двух разных версий, то так и будет две папки этого пакета?
halfcupgreentea
03.11.2021 21:54+2Да, но это уже и так экономит львиную часть занимаемого места. То, что в локальном репо будут две версии одного пакета с 90% одинаковыми файлами мне видится уже сильно меньшей проблемой.
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), в плане ускорения установки, выглядит проще, удобнее и надёжнее.
halfcupgreentea
03.11.2021 23:36+7А зачем хранить кеш в репозитории?
Для детерминированных зависимостей есть yarn.lock, его должно быть достаточно.
funca
03.11.2021 23:49У yarn.lock есть ряд фатальных недостатков. Например, он привязывается к конкретному зеркалу npm, который использовал разработчик в момент установки зависимостей. Но ваш CI сервер может захотеть использовать другое зеркало пакетов, которое ему ближе. Сам же npmjs.com стабильностью не отличается.
Поэтому вместо него уже придумали .pnp.cjs (конфиг на JavaScript, Карл! за что, кстати, их невзлюбили разработчики Typescript, ну да это отдельная история). А развитие мысли привело к https://yarnpkg.com/features/zero-installs - запуску приложений без установки зависимостей.
halfcupgreentea
04.11.2021 00:36+1Ок, согласен. Но тогда уж проще устроить для CI такой же локальный репо с кешом пакетов и монтировать его в каждый агент. Так еще стабильнее, чем ходить куда то по сети :)
APXEOLOG
03.11.2021 19:10+3В результате, если у вас 100 проектов с одинаковыми зависимости - pnpm полезен в интернет и сохранит пакеты на диске только 1 раз. Остальные 99 раз он лишь создаст символические ссылки.
Я не спорю, но надо отметить, что в процессе разработки не так уж часто встречаются случаи, когда нужно регулярно переустанавливать с нуля одни и те же пакеты в сотне проектов. Как правило ты делаешь `npm i` один раз после пулла проекта(ов) и изредка вызываешь, когда зависимости обновляются. Если пакет не поменялся и есть в папке модулей - он не будет переустанавливаться.
Вещь полезная, но "всем разработчикам" он не нужен - его применение имеет смысл в специфичных кейсах
eyudkin
03.11.2021 20:00+1А почему бы и не всем разработчикам? Хотя бы ради экономии личного места на диске и трафика, чтобы в рамках одного проекта какой-нибудь leftpad лежал один раз, а не 100500.
APXEOLOG
03.11.2021 20:29+2Может это мое личное предубеждение против симлинков, но я сталкивался с конкретной проблемой, когда фреймворк, который я использовал, не поддерживал их (долгое время, пока не пофиксили. Хотя даже после того как пофиксили я ловил там баги)
gatoazul
03.11.2021 20:44+1webpack-web-dev, например, иногда чудит при симлинках, не желая замечать, что файл обновился.
faiwer
03.11.2021 22:15+11Я года четрые назад пытался пользоваться npm link. Спустя некоторое время свыкся с тем, что вся экосистема меня люто ненавидит. Я был кем-то вроде alpha-тестера. У меня ломалось вот буквально всё, что только могло сломаться. Оказалось что почти никто не разрабатывает свой код с учётом поддержки sym-линков. Отваливались тест-runner-ы, сборщики, backend библиотеки, docker (кажется), линтеры,… По сути сложно вспомнить что продолжало работать.
В какой-то момент я понял, что я 2 трети времени работаю над проектов, одну треть — чиню поддержку sym-линков. Даже сам npm link ломался при любой операции с npm.
Возможно сейчас всё уже не так плохо, но вышеописанный опыт отбил всякое желание прикасаться к sym-линкам со стороны JS окружений.
Lynn
04.11.2021 13:51А это похоже на ошибку в тексте. Насколько я помню на самом деле в pnpm используются hardlink на файлы. Во всяком случае я предлагал именно это
DmitryOlkhovoi
03.11.2021 20:58+6pnpm создает на вашем компьютере единый репозиторий (по крайней мере единый для вашего пользователя) npm-пакетов. Он создает контентно-адресуемую файловую-систему, как git. (Для тех кто не знает что это, поясню — каждый файл получает имя, равное хэшу от его содержания, а значит файлы с одинаковым содержанием никогда не повторяются дважды). После чего в папке node_modules он создает символические ссылки на эти файлы — вместо того чтобы их каждый раз копировать.
Каждый день какой-то из модулей обновляется. Как чистятся эти некропакеты в централизованном месте?
Saiv46
03.11.2021 21:29В теории, при добавлении/удалении пакета изменения также вносятся и в центральное хранилище, для последующей чистки от неиспользуемых файлов.
Lynn
04.11.2021 13:55DmitryOlkhovoi
06.11.2021 11:59В среднем после каждого инстала, надо в ручную запускать очистку. Может они сделают это по дефолту
Fen1kz
03.11.2021 21:19+2Просветите, а разве npm не кеширует файлы на диске чтобы не качать их несколько раз?
gecube
03.11.2021 22:17-1это не помогает!
а pnpm действительно вещь! Коллеги уже перешли и скорость/стабильность сборки повысилась в разы!!!!
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% отличающихся исходников, а в остальном - понасоздает тучу симлинков.
funca
03.11.2021 23:59В случае npm зависимостей, выкачивает он все равно пакеты целиком (они же в tar.gz). Но потом при установке вроде как может постараться дедуплицировать файлы (честно говоря сам туда не смотрел, только с чужих слов). Другая стратегия сводится к тому, чтобы для пакетов, различающихся только patch версией (третья цифра), устанавливать лишь одну версию - самую старшую. Тут делается допущение, что патч версии не ломают обратной совместимости.
shybovycha
04.11.2021 01:06+1допущение крайне слабое - авторы пакетов часто пренебрегают semver.
но и, получается, npm вроде как плохо старается с дедупликацией.
к тому же, как кто-то выше верно подметил, следующим шагом приводящим к дикой дупликации является сборщик - тот же вебпак запросто порождает тучу дубликующего кода. при чем проблемма не только с зависимостяими - всякие ES6 штуки (вроде fat arrow, identity function, etc.) повторяются неимоверное количество раз. не так давно была статья на эту тему, Только 39% функций в node_modules уникальны в дефолтном Angular проекте
VladVR
04.11.2021 14:05+1Проблема, кстати, не в скорости интернета. Интернет не был медленным когда сидели в офисе, и тем более не медленный дома на удаленке.
Проблема, по крайней мере у меня, в том, что медленный диск. Хоть бы и ссд, но все равно раза в 2 медленнее, чем диск на домашнем компе, да еще и корпоративный антивирус, который замедляет это еще этак раз в 5. Итого классическая операция "снести все и сделать npm i начисто" занимает минуты четыре, вместо 15 секунд.
А работать на домашнем компе, к сожалению, нельзя, не настроить доступ в корпоративную сеть.
А вообще - стало интересно попробовать, как я понял ничего ведь не меняется, в любое время можно отказаться.
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 от силы?funca
04.11.2021 00:25Мне кажется они решают проблемы не столько разрабов, сколько своей инфраструктуры. Можно предложить, что на объемах Фейсбука, им существенно выгоднее держать общее хранилище пакетов разделяемых между кучей приложений (может даже в отдельном реплицируемом volume для docker как советуют выше), нежели выстраивать уникальные по сути директории node_modules под каждое приложение. Фактически, вместо того, чтобы городить дерево зависимостей на файловой системе, они сделали сами зависимости плоскими и иммутабельными (zip архивы), а структуру отношений между ними (которая специфична для каждого приложения) записывают в небольшой по размеру конфиг.
Regis
04.11.2021 00:27+8300-500 в одном проекте, 300-500 в другом... Не успеешь и опомниться, как несколько гигабайт заняты под дублирующиеся файлы.
Ну и вообще с таким подходом не удивительно, что софт всё толще и медленнее с каждым годом. Ждём, когда через 10 лет будут говорить "Разве это проблема для пользователей с диском в 5-10 Тб. Сколько там ваши пакеты весят, 3-5Гб от силы?" :)
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 экосистеме доведена до абсурда, и как мне кажется, делая очередной пакетный менеджер вы решаете не ту проблему.
polearnik
04.11.2021 12:47+2не вижу никаких проблем с пакетами типа isObject isArray итд. Корректные проверки с учетом нюансом выливаются в нетривиальный код. К такому условию как в функции
isObject
придете через пару итераций и то столкнувшись с парочкой багов. Если ктото уже прошел этот путь то спасибо за расшаренный опыт и нужную библиотеку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 строчки?
extempl
04.11.2021 15:05Строго говоря в результирующий бандл войдёт только index.js. Но вообще да, для этого есть тот же lodash который при сборке тем же вебпаком может не включать в бандл тот код, функции которого не были использованы (я полагаю, именно для этого созданы такие пакеты-одиночки).
Хотя если во всём проекте из того же lodash вы используете только isObject, то есть ли в этом смысл? Не всё так просто и однозначно, об этом должен думать человек добавляющий пакеты в проект.
nick1612
04.11.2021 19:59+1Тут же стоял вопрос не размера конечного бандла, так как все современные сборщики умеют делать tree shaking и использование pnpm в этом плане ничего не решает. Речь идет о том, что вместо того, чтобы написать 3 строчки кода, люди непонятно зачем тянут в проект зависимость в 277 строк, которую они никак не контролируют. То есть webpack и множество других утилит, которыми пользуются тысячи людей, стоит на вот таком вот фундаменте.
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 репозиторий и поменяет текущие версии в нём? Всё может быть, но это уже паранойя.
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, что совсем не есть параноя и происходит достаточно часто.
extempl
05.11.2021 21:54+1кто-то может взломать одного из авторов и залить туда malware
Да, и для этого стоит использовать точные версии (без `^`) и package-lock, так как malware попадёт только в новые версии, но не в существующие.
extempl
06.11.2021 06:37Вы излишне драматизируете. Звучит как "давайте гулять по всем полям, но знакомый моего знакомого рассказывал, что на одном из них может быть зарыта неразорвавшаяся бомба со времён войны, но это не точно. А ещё её можно вовремя заметить, позвать сапёров и они обезвредят."
PsyHaSTe
18.11.2021 13:56Несколько гигабайт? ЖСеры, подержите моё пиво :)
; du -hs work/<project>/target 284G work/<project>/target
Стата одного моего знакомого. У меня не так печально но все равно норма когда полсотни гигабайт занято всякими умершими пакетами
Devoter
05.11.2021 09:11+1Лично в моей рабочей директории несколько десятков проектов только на ноде, и большинство из них живые. Есть ещё проекты, которые создаются временно для каких-нибудь экспериментов и потом благополучно удаляются. И да, почти каждый из них тянет за собой 300-500 dev-зависимостей, без которых неудобно работать с проектом. Но вот pnpm, отнюдь - не панацея. Глобальный набор зависимостей имеет свои минусы вроде отстуствия возможности по-быстрому подправить что-то прямо в исходнике установленного пакета при отладке или быстрой тотальной замены зависимостей. Но, кому-то, безусловно, подойдёт.
akazakou
04.11.2021 00:50+2Если честно, не совсем понимаю какую проблему (кроме дублирования файлов) решает pnpm. Есть npm ci что из локального кеша ставит за́висимости за секунды. Для CI используется кеширование node_modules целиком. Для CD при любом раскладе надо пакеты пихать в контейнер, ибо mount volume не подойдет, т.к. там со временем будет весь npm репозиторий, из-за непоняток что можно удалять или нет (откат к предыдущей версии контейнера к примеру)
pavelsc
04.11.2021 03:13+4"Если у вас 100 проектов с одними и теми же зависимостями" - значит в своей жизни вы свернули куда-то не туда. Проблема далеко не в npm. Напомнило индуса с Андроид маркета с сотней приложений, которому акк забанили :)
Metotron0
04.11.2021 04:30Там, где я работал, был шаблон с фреймворком, которым все пользовались для разработок. Чтобы проще было учить новеньких сразу тому, с чем они потом будут работать. И чтобы легче въезжать в код на чужом проекте.
shushu
04.11.2021 05:17+2"Если у вас 100 проектов с одними и теми же зависимостями" - значит в своей жизни вы свернули куда-то не туда.
Не все так просто, в нпм очень много пакетов, которые используют много общих зависимостей. В итоге даже если в разных проектах используются абсолютный разный набор пакетов, всё равно, очень много пакетов из зависимостей будут общими
nin-jin
04.11.2021 06:18-1В MAM тоже очень много реп, использующих много общих зависимостей, но таких проблем нет. Вся JS экосистема свернула не туда, подстраиваясь под кривую архитектуру NodeJS, вместо её исправления.
shushu
04.11.2021 07:23+1Вся JS экосистема свернула не туда, подстраиваясь под кривую архитектуру NodeJS, вместо её исправления.
Вот тут согласен. И мне кажется что тту уже нечего не исправишь :(
Надо все переделывать...
Psychosynthesis
04.11.2021 05:07Я только не понял, он использует всё те же пакеты из npm?
dasFlug
04.11.2021 07:24Да, pnpm только оптимизирует место на диске которое занимают их локальные копии.
larixer
04.11.2021 16:55Дело не только в месте. У него еще и скорость установки node_modules выше, за счет меньшего количества файлов. Также он предотвращает доступ к незадекларированным зависимостям (уровень контроля определяется настройками)
dasFlug
04.11.2021 23:01Немного не понял о чем вы. Количества каких файлов меньше? Для скачивания из глобального репозитория при выполнении install? Но это просто следствие того что есть локальный репозиторий на который node_modules проекта ссылается. Или это про дедупликацию файлов по содержимому?
larixer
05.11.2021 08:17Я имел ввиду количество файлов внутри node_modules. Однако, кажется, оно ненамного меньше, не похоже чтобы существенно меньше, даже если аккуратно пытаться определить, а определить тяжело, потому что много факторов на это влияет.
maxzhurkin
04.11.2021 10:01+3Можно было рассказать хотя бы вкратце, что именно нужно сделать для перехода с npm на pnpm кроме добавления замены npm на pnpm
aamonster
04.11.2021 11:09+1А почему это сделано на симлинках, а не на хардлинках? Ради возможности держать на другом разделе?
Lynn
04.11.2021 13:59+1Это сделано на хардлинках. Автор поста ошибается
larixer
04.11.2021 16:52Это сделано и на симлинках (внутри node_modules) и на хардлинках файлов из node_modules на общее хранилище адресуемое на основе контента (используется хэш от контента). Симлинки предназначены для исключения дублирования одних и тех же пакетов внутри node_modules (дедупликации), а также для ограничения использования незадекларированных зависимостей. Обратная сторона симлинков - ухудшение совместимости с существующей экосистемой npm. Положительная сторона - минимизация ошибок доступа к незадекларированным зависимостям при разработке большими командами.
aamonster
04.11.2021 18:24А почему для дедупликации внутри node_modules не могут тоже использоваться хардлинки? Для папок нельзя?
И нет ли режима, создающего папки, но использующего хардлинки для файлов (чтобы не было проблем совместимости с симлинками)?
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, но без симлинков)
Singaporian
04.11.2021 15:55Тогда вопрос.
Например, у меня два проекта: X и Y
В package.json проекта X стоит "vuetify": "2.3.21"
А проект Y требует уже версию "vuetify": "v2.6.0-beta.0"
Какую мне поставит pnpm и как мне объяснить это второму проекту, которому эта версия не подходит?gecube
04.11.2021 16:21очевидно, что обе?
Singaporian
04.11.2021 17:33-1Мне очевидно пока обратное. Поставлю от X - ок. Начну ставить Y - он апнет версию от X и теперь X сломается.
Lynn
04.11.2021 20:09Что значит «объяснить второму проекту»?
Ваши проекты вообще ничего не знаю друг о друге, в глобальном хранилище pnpm будут лежать обе версии.
А вот когда вы заведёте проект Z зависящий от "vuetify": "2.3.21", то pnpm переиспользует файлы из глобального хранилища.
Singaporian
04.11.2021 20:18"в глобальном хранилище pnpm будут лежать обе версии"
Именно это я и спрашивал.Ndochp
06.11.2021 15:53Так больше того, с учетом того, что 2.3.21 от v2.6.0-beta.0 может отличаться не всеми файлами у вас лежать будут обе, а места занимать как полторы
Singaporian
06.11.2021 16:19Стоп, тогда еще интереснее. То есть подходящие файлы хранятся рядом? А как же шастать по библиотеке и смотреть, как она работает? Как дебажить?
Ndochp
06.11.2021 16:51Так дебажить не проблема — вы отличено пробежитесь по всем файлам.
проблема исправлять — одно неловкое движение прямо в библиотеке и вы поправили этот модуль во всех своих проектах.Все файлы лежат на диске (не важно где), на каждый файл лежит заголовок в нужной папке модулей. Если в двух папках модулей должны лежать одинаковые файлы, то в каждом лежит заголовок, указывающий на один и тот же файл.
Это по сути и так было бы в FAT, а в любой нормальной FS эта связка ярлык — файл всегда есть "из коробки". При обычной жизни на один файл есть один ярлык. В некоторых специфичных случаях (WinSxS, описанный, еще когда-то, где часто нужно смотреть с разных сторон на один файл, например при классификации фото по папкам — дача это или котики) ярлыков делают много. При исправлении файла по любому ярлыку он правится у всех. При удалении — удаляется только ярлык. Когда ярлыков не осталось, то помечается к удалению и тело файла. (Вот грубое и не академичное описание хардлинков)Singaporian
06.11.2021 19:03"вы отличено пробежитесь по всем файлам."
Понял. Это можно было сказать более кратко: используются хардлинки.Ndochp
06.11.2021 19:23Просто в статье было про симлинки, ниже в комментариях поправили, что имеются в виду хардлинки.
Мне по вашим уточнениям показалось, что вам не ясен именно механизм хардлинков.
Arqwer
04.11.2021 16:44+1Имхо, в светлой инфраструктуре будущего дедупликацией должна заниматься файловая система на уровне отдельных сегментов битов. Тогда во-первых не придётся каждому пользовательскому приложению переизобретать велосипед, во-вторых она сама собой просочится сквозь все слои контейнеризаций и виртуализаций. Так решится проблема с занимаемым местом. А чтобы не скачивать пол интернета по нескольку раз, надо ещё сделать в этой файловой системе отдельную ручку, по которой можно будет спрашивать ФС, не лежит ли в ней случайно уже файл с таким то хэшем.
И я не удивлюсь, если окажется, что кто-то такую ФС уже сделал.
larixer
04.11.2021 16:46Yarn 3.0 и выше тоже поддерживает установку с использованием контентно-адресуемой файловой-системы, причем с классическим расположением файлов и директорий в `node_modules`, без симлинков (опции `nodeLinker: node-modules` и `nmMode: hardlinks-global`).
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 вручную. Почему-то в последнее время считается нормальным засирать систему установкой всего подряд в обход пакетного менеджера.
funca
05.11.2021 01:23Просто многие устанавливают node.js через nvm или подобное, чтобы была возможность переключаться между различными версиями. В таком случае весь global оказывается в вашей домашней директории. Использовать такой способ установки для системного интерпретатора конечно не нужно.
gresolio
05.11.2021 14:53Многие негодуют, я тоже. Лучше бы статью «Пожалуйста, перестаньте использовать js» :)
VladVR
05.11.2021 06:06Потрогал. Работает, конечно, заметно быстрее, чем npm, но сразу уперся в неработающий сценарий. У меня gulp таски вынесены в отдельный пакет, который в компонент ставится как зависимость, соответственно gulp не является прямой зависимостью текущего компонента, а зависимостью зависимости. И команда pnpx gulp [taskname] при этом не работает. Если установить gulp как прямую зависимость, то не хватает ts-node, далее не хватает тайпскрипта и т.д.
Т.е. все зависимости, которые я унес из каждого компонента в один пакет с тасками должны будут вернуться обратно в каждый компонент, чтобы это работало. Это deal-breaker, делает pnpm неюзабельным для меня.
greybax
08.12.2021 02:29дак уж начали еще с 2019 года https://alfilatov.com/posts/is-your-npm-install-still-too-slow/
dimuska139
Насколько я знаю, многие бэк на NodeJS разрабатывают в Docker-контейнерах. Стало быть, в каждом контейнере будут свои зависимости, переиспользовать их между контейнерами через символические ссылки всё равно не получится.
halfcupgreentea
Можно монтировать volume с локальным хранилищем
dolfinus
Это антипаттерн. Контейнеры - это про изоляцию приложения вместе с его набором зависимостей и окружением от остального мира. Шарить код между контейнерами через volume значит идти против основной идеи докера.
halfcupgreentea
Ну а вам шашечки или ехать?
D0001
В данном случае нет смысла, слишком много возни.
Как вариант можно ставить пакеты в контейнеры по очереди, в одной и той же последовательности, начиная с самых наиболее используемых, тогда контейнеры будут использовать слои общие) Но это тоже извращение какое-то)
Вот для dev машины этот менеджер пакетов интересен.
kornell
Думаю, что для сценария CI/CD это достаточно удачное решение.
Например, если используется Jenkins и универсальный build контейнер для NodeJs приложений, то такому контейру очень выгодно иметь актуальный репозиторий без накладных расходов, которые непременно сопровождают npm.
Вообще, для production я не встречал предложений использовать какой-нибудь ng serve (Angular), сервер в этом режиме даже заботливо предупреждает о том, что так не стоит делать.
Исходя из этой логики, любое test/staging/prod окружение по-хорошему работает с (далее конкатенировать по степени важности) собранным, упакованным, перепакованным, оптимизированным, обфусцированным, минифицированным кодом. А значит, обсуждая npm/yarn/pnpm, логично предположить, что либо речь о перчисленных выше шагах, либо это сугубо локальное окружение.
Первый вариант выше уже подробно раскрыт, а во втором не так важно куда идти - к докеру или от него.
Опять же, можно прокидывать fs в докер, а можно держать image/контейнер, который использует преимущества pnpm и в случае image просто обнуляется собранный кэш, контейнер же позволяет инкрементально наращивать репозиторий.
И все ради благой цели - быстро и надежно доставлять код!
dolfinus
Для этого есть buildkit и опция RUN --mount=type=cache в Dockerfile. А с внешними симлинками в докере легко напортачить, могут начать вести на несуществующие пути, если ошибиться с монтированием.
kornell
В целом с Вами соглашусь, есть вещи, которые лучше не делать, потому что потом дороже разбираться. В любом случае, в обсуждаемых сценариях можно комбинировать разные подходы. Volumes табуировать тоже не стоит, просто надо понимать принципы работы AUFS, не забывать про uid/gid пользователей хост системы и контейнера. Так что, на мой взгляд, все сводится именно к осознанности в практике применения этих строительных блоков.
*повторение последнего предложения из предыдущего комментария*
saboteur_kiev
Насколько я себе представляю, npm это не про рантайм в продакшене, а про билд.
Для билда вполне себе отличное решение монтировать volume с локальным хранилищем где будет глобальный репозиторий npm или maven
dolfinus
Вот только на этапе docker build volume не подключаются, они есть только в runtime
gecube
Ну, npm и pnpm serve в проще отдельные ребята очень даже используют…
Telmah
"После чего в папке node_modules он создает символические ссылки на эти файлы - вместо того чтобы их каждый раз копировать"
и куда будут вести эти ссылки после того как вы сбилдите контейнер и захотите запустить его гдето?
jMas
Хм, любые догмы - это тоже антипаттерн?
Если pnpm нормально контролит зависимости (если произведены соответствующие тесты), то в чем собственно проблема?
dolfinus
Лично я не против самого инструмента - если он реально помогает на этапе разработки, то супер.
Вот только в контейнере он не даст никаких преимуществ, потому что внутри docker образа нет никакого кэша пакетов. Во-первых, одно приложение - один образ, значит нет никакого переиспользования. Во-вторых, кэши раздувают размер образа, поэтому в докере они обычно выключены. Поэтому его применение здесь выглядит как натягивание совы на глобус.
А все попытки притащить инструмент костыльными путями (через volume, через монтирование папки node_modules с хоста, и т.п.) приводят к тому, что возникают проблемы с изоляцией зависимостей и воспроизводимостью сборки. На проде это критично, поэтому крайне не рекомендую так делать.
Максимум где такое можно использовать - локальная разработка в докере, но не более. А для ускорения продовой сборки есть другие варианты, один с buildkit я уже описал выше.
Enverest
Но ведь зависимости в любом случае качаются из остального мира. В чём принципиальная разница, если зависимость скачается или она уже закеширована в общем локальном хранилище?
Теоретически, одна версия зависимости иммутабельна и скачанные файлы должны быть идентичны локальным.
ivankudryavtsev
Это трэш и угар, так в общем случае делать не надо. Даже если как RO монтировать. Вам придется поддерживать кроссконтейнерный консистентный набор версий всего.
Вообще, за пределами Dev-среды, кажется, что проблема надумана (по крайней мере для Docker). Просто сделайте base image со всеми зависимостями, он не будет дублироваться между контейнерами.
dark_ruby
скорее это сделано чтоб решать проблему на время разработки, а не в продакшене. Т.е. если у вас зачекаутено много реп каждая со своими (часто повторяющимися зависимостями) то оно не будет пожирать место на диске. В продакшене понятное дело что всё по контейнерам и там уже не важно.
leon0399
И получается разное окружение в разработке и продакшене ????♂️
dimuska139
Так многие локально в Docker разрабатывают
fiftin
Я думал смысл контейнеров как раз иметь одно и тоже окружение при разработке и на проде. У нас например не используются контейнеры на продакшне. Я чаще использую контейнеры как раз при разработке.
D0001
Не совсем, разработчик может что угодно делать на своей машине, а вот то, что протестировали и то, что пошло в прод, должно быть идентично и это как раз легко делается с помощью Docker.
Единственно, что меня смущает, это то, что некоторые библиотеки (или примеры с ними), требуют задавать некоторые параметры в момент билда... вот это уже ломает всё (например требуют задавать код счетчика или внешний url).
Потому что смысл в том, чтобы скомпилировать образ, выложить его в свой реестр, потом отправить на тест, потом на прод (тот же самый контейнер), а тут получаются разные контейнеры на тесте и проде.
fiftin
Но это не рашает проблему "на моей машине работает".
fiftin
Интересно, какой процент людей разрабатывают в докере? Может есть какая-нибудь статистика у кого-нибудь?
saboteur_kiev
Что значит "разрабатывают в докере" ?
Запускать билды в докере - отличная идея, особенно если этот докер в кубере/опенщифте и сборка идет в каком-нить CI. У тебя столько нод, сколько тебе нужно и каждая идеально чистая.
fiftin
Так написано в коммента, на который я ответил) Я не вижу смысла запускать NodeJS-приложение в докере при разработке (для отладки). На мой взгляд это только все усложняет.
fancy-apps
Практически любой Node.JS проект это 90% dev/build time зависимостей, которые нужны для разработки и сборки локально. Когда вы собираете Docker image вы просто копируете туда конечный бандл, на многих моих проектах внутри даже npm-а нет.
В контейнерах нужно писать npm install --only=prod , а это, как я сказал выше - 10% от всех зависимостей как правило.
Saiv46
Вообще для контейнеров и прочего CI уже есть команда
npm ci --production
, делающая то же самое, но быстрее (при условии что естьpackage-lock.json
).larixer
pnpm будет экономить место и время установки node_modules за счет использования симлинков в вашем Docker-контейнере. Если симлинки не совместимы по каким-то причинам с вашим проектом вы также можете попробовать Yarn 3.0+, он поддерживает классическую структуру node_modules с хардлинками на контенто-адресуемое хранилище (nmMode: hardlinks-global) либо локально в проекте (nmMode: hardlinks-local), как результат - очень хорошая совместимость с экосистемой пакетов npm + экономия места в Docker-контейнере
dimuska139
Не очень понял. У меня есть 2 разных Docker-контейнера, где пакеты частично пересекаются. Как именно симлинки могут ссылаться на содержимое других контейнеров? Особенно учитывая, что контейнер другого проекта может быть вообще не запущен в этот момент.
larixer
Симлинки не будут ссылаться на содержимое других контейнеров, симлинки будут внутри node_modules и за счет этого установка будет быстрее и места в контейнере будет требоваться меньше
dimuska139
Каким образом? Если симлинк на что-то должен ссылаться, то это "что-то" должно быть установлено в контейнере. Если у вас несколько контейнеров, и в каждом из них надо установить, скажем, typeorm, то эта библиотека будет продублирована в каждом из контейнеров. То есть в любом случае получается дублирование, т.к. typeorm будет в обоих контейнерах.
larixer
Есть разное дублирование. Да, дублирование между контейнерами pnpm не уменьшает. pnpm решает другую задачу - дублирование файлов и пакетов внутри node_modules и таким образом размер Docker-контейнера будет меньше.
dimuska139
Но ведь и в npm есть команда dedupe. Она не решает эту проблему?
larixer
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.
dimuska139
Понял, спасибо!