вступление

Этот пост объясняет, почему были созданы npm, Yarn и pnpm, а также другие важные проблемы, которые они решили со временем.

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

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

Прежде чем углубляться в менеджеры пакетов, также стоит понять, как node может найти пакет, который вы пытаетесь импортировать. Для этого я бы рекомендовал прочитать мое объяснение системы разрешения модулей узла.

Npm

Npm, выпущенный 12 января 2010 года, был первым реестром пакетов и менеджером пакетов для node.

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

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

Кроме того, по умолчанию npm хранит только одну копию глобального пакета. Например, если вы запустили npm install express, а затем запустили npm install [email protected], версия 4.16.4 express перезапишет последнюю версию express. Это затруднило бы использование разных версий глобальных пакетов в разных проектах.

В версии 1 npm реализовал вложенную структуру зависимостей. Это означает, что вы найдете пакеты, которые вы установили локально, в корневой папке node_modules, а все ваши подчиненные зависимости будут храниться в папке node_modules ваших зависимостей.

Например, представьте, что ваше приложение имеет пакет A и пакет C в вашем package.json, пакет A имеет пакет зависимостей B версии 1.0, а пакет C имеет пакет зависимостей B версии 2.0. Пакеты A и C будут в вашем корневом node_modules. Пакет B версии 1.0 будет находиться в папке node_modules пакета A, а пакет B версии 2.0 будет в папке node_modules пакета C.

Такой подход решил проблему «ада зависимости». Ад зависимости возникает, если вы пытаетесь установить две версии пакета в одной папке, что нарушит работу вашего приложения. Поскольку зависимости хранились в папке node_modules их зависимости в npm, ад зависимостей никогда не возникнет при установке пакета.

Однако эта вложенная структура зависимостей приводила к длинным путям к файлам, поскольку одна зависимость могла иметь подчиненную зависимость, которая имела свои собственные зависимости и т. Д. Это приводило к сбоям приложений при использовании Windows. По умолчанию Windows ограничивает размер путей к файлам 260 символами, и это ограничение нельзя было изменить до Windows 10.

Npm версии 3 «сгладил» дерево зависимостей, чтобы решить эту проблему. Это означало, что все зависимости и вложенные зависимости по умолчанию будут помещены в корневую папку node_modules. Если версия пакета уже была в корневой папке node_modules, во избежание ада зависимостей она будет помещена в папку node_modules зависимости, которая использовала ее, как в npm v2. Таким образом, дерево зависимостей не было полностью плоским, но оно было достаточно плоским, чтобы проблема пути к файлу для пользователей Windows значительно уменьшилась.

Еще раз представьте, что у вашего приложения есть пакет A и пакет C в вашем package.json. Пакет A имеет пакет зависимостей B версии 1.0, а пакет C имеет пакет зависимостей B версии 2.0. Когда пакет A установлен, пакет B версии 1.0 добавляется в вашу корневую папку node_modules. Когда установлен пакет C, пакет B версии 2.0 нельзя добавить в корневую папку node_modules, так как пакет B версии 1.0 уже там.

Сглаживание дерева зависимостей также помогло сэкономить дисковое пространство и ускорить установку. Если у вас есть приложение, указанное выше, и вы пытаетесь установить пакет D версии 1.0, который имеет подчиненную зависимость от пакета B версии 1.0, вам не нужно будет снова устанавливать пакет B версии 1.0, как в npm версии 2.

С появлением таких сборщиков модулей, как browserify и webpack, стало проще использовать npm и во внешнем интерфейсе. В то время разработчики уже могли использовать менеджер пакетов Bower на стороне клиента. Однако Bower вынуждает пользователей устанавливать только одну версию пакета для каждого приложения, что означает, что разработчики должны вручную решать ад зависимостей.

Кроме того, в то время браузеры не поддерживали модули ES, также известные как модули JavaScript, и вы по-прежнему не можете использовать простые спецификаторы в браузере. Это означает, что пользователи Bower должны вводить пути к пакетам и беспокоиться о глобальном загрязнении области видимости. Я привожу пример этой проблемы в этом репо.

Меньшая часть пользователей предпочла Bower npm, потому что они предпочитали вручную решать ад зависимостей, чтобы иметь полностью плоскую структуру node_modules. Но даже специалисты по сопровождению Bower рекомендовали использовать Yarn после того, как он был выпущен 11 октября 2016 года. Похоже, они предпочли yarn npm, потому что он дает возможность полностью сгладить ваши node_modules.

Пряжа

Yarn быстро набирала обороты, потому что его поддерживали Facebook и Google, а установка пакетов с Yarn была намного быстрее, чем с npm. Одна из причин, по которой Yarn устанавливался быстрее, чем npm, заключалась в том, что он использовал более быстрый алгоритм для получения данных из своего кеша.

Пряжа также изначально имела несколько других преимуществ по сравнению с npm.

Yarn также использовал свой кеш, поэтому вы можете использовать любой ранее загруженный пакет в автономном режиме. Эта функция была важна для Facebook и, предположительно, других корпораций. Это потому, что Facebook хотел отрезать свои среды непрерывной интеграции от Интернета, чтобы их было труднее взломать.

Использование автономных копий пакетов также сокращает время непрерывной интеграции и регулярную установку пакетов, поскольку вам не нужно делать сетевой запрос для получения пакетов. А если вы не отправляете сетевые запросы, вам не нужно беспокоиться о сбое сетевого запроса для пакета, что делает сборки более надежными. Например, хранение пакетов в автономном режиме позволило бы компаниям избежать проблем, когда популярный пакет npm left-pad был удален из реестра npm.

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

Yarn также получила большое внимание из-за своего «файла блокировки». При первой установке пакета создается файл с названием yarn.lock, в котором перечислены точные версии каждого установленного пакета. Этот файл обновляется Yarn каждый раз при установке и обновлении пакетов.

При установке пакетов с помощью npm для пакетов в package.json по умолчанию устанавливается символ каретки, что означает установку последней минорной версии пакета. Это означает, что разработчик A может клонировать приложение, которое использует React в 13:00, и все работает, но тогда React может быть обновлен в 13:30, и обновление может случайно содержать ошибку. Поэтому, когда разработчик Б устанавливает то же приложение в 14:00, устанавливается новая версия React, и приложение не работает.

По умолчанию Yarn устанавливает версии пакетов, перечисленные в файле yarn.lock, а не последнюю версию semver на основе package.json. Это не позволяет разработчикам тратить время на отладку, почему приложение работает на одном компьютере, а на другом не работает.

На самом деле у Npm уже была собственная версия файла блокировки, которую он назвал shrinkwrap file, которая исправила бы эту проблему. Но многие разработчики не знали об этом файле, потому что он не был включен по умолчанию.

Npm в значительной степени догнал Yarn, когда 25 мая 2017 года была выпущена npm версии 5. В этом выпуске был оптимизирован кеш npm, чтобы повысить скорость установки примерно до такой же скорости, как у Yarn. Кроме того, npm по умолчанию включил свою версию файла блокировки, который переименовал в package-lock. О небольших различиях между yarn.lock и package-lock.json вы можете прочитать здесь. Npm также начал использовать контрольные суммы и позволять пользователям устанавливать пакеты в автономном режиме из своего локального кеша.

Pnpm

Pnpm появился в истории в июне 2017 года, когда он выпустил версию 1, а его создатель Золтан Кочан написал об этом в своем блоге. В своем сообщении Почему мы должны использовать pnpm Кочан объяснил, как pnpm позволяет пользователям экономить дисковое пространство и избегать проблем, связанных с npm и структурой node_modules в Yarn.

В то время, когда npm и Yarn устанавливали пакеты, они устанавливали по крайней мере одну копию каждого пакета для каждого проекта, даже если пакет уже был в его кеше. Pnpm только один раз устанавливает пакет в свой кеш, который он называет своим хранилищем. Если вы устанавливаете pnpm, вы можете найти хранилище в каталоге .pnpm-store в вашей домашней папке.

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

Это означает, что вы будете использовать почти нулевое дополнительное пространство на жестком диске, если будете использовать какую-либо версию пакета более одного раза. Например, даже если вы используете response в 2 или более проектах pnpm, response был установлен pnpm только один раз.

Эти сбережения усугубляются. Популярный стартовый набор create-react-app, который по умолчанию использует Yarn, поставляется с папкой node_modules размером 212 мегабайт. Так что, если бы на моем компьютере было еще 10 проектов create-react-app, я бы сэкономил 2,12 гигабайта плюс больше места для любых дополнительных библиотек, используемых более одного раза.

Помимо экономии места на диске, вы сэкономите много времени, которое вы бы потратили на установку этих дополнительных копий пакетов.

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

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

Кочану также не понравилось, как плоская структура node_modules, используемая в npm, поскольку версия 3 и Yarn позволяли пользователю требовать вложенные зависимости в корневой папке node_modules в коде своего приложения. В своем сообщении в блоге Строгость pnpm помогает избежать глупых ошибок Кочан объясняет, что это может вызвать проблемы, если пользователь забыл установить пакет, но он уже был установлен как подчиненная зависимость. Это означает, что ваш проект сейчас может работать, но он сломается, если эта подзависимость будет удалена как зависимость вашей зависимости. Ваш проект также может сломаться, если ваша зависимость начнет использовать новую версию этой подчиненной зависимости.

Логика Кочана верна, но npm и Yarn настолько широко используются, что многие разработчики уже создали проекты, которые импортировали пакеты, которых не было в их package.json. Например, это была проблема с create-react-native-app. Это означало, что вы могли ввести что-то вроде npm install create-react-native-app или использовать Yarn, и это сработало бы, но не с pnpm.

В случае create-react-native-app он использовал зависимость metro-bundler, которая забыла добавить ряд зависимостей в свой package.json. Пользователи pnpm должны будут найти зависимости, которые разработчик библиотеки забыл добавить в свой package.json, и использовать крючки pnpm для установки этих зависимостей. Чтобы исправить проблему для других в будущем, они могли отправить запрос на перенос, чтобы исправить стороннюю библиотеку.

Кочан признал, что npm и Yarn были намного популярнее, чем pnpm, и что большинство пользователей хотели, чтобы их пакеты работали как можно быстрее. Кочан пошел на компромисс в pnpm версии 4, которая переносит ваши подчиненные зависимости в папку node_modules, где ваши зависимости могут получить к ним доступ.

Это означает, что у вас не должно возникнуть проблем при установке других пакетов, которые работают с npm и Yarn. В случае, если вы это сделаете, Pnpm действительно предоставляет вариант постыдно-подъем для использования плоской структуры node_modules, такой как npm и Yarn.

Кочан оставался строгим, не позволяя вашему приложению получать доступ к его собственным зависимостям. Это означает, что ни один новый проект pnpm не будет иметь проблем, которые были у metro-bundler, но вам не нужно исправлять существующие пакеты, чтобы pnpm работал стабильно.

Вы также можете использовать npm для установки пакетов с флагом - global-style, чтобы создать структуру node_modules, которая позволяет вам импортировать только ваши прямые зависимости.

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

React, единственная прямая зависимость, установленная в моем примере, указана непосредственно в папке node_modules. Эта папка символически связана с папкой node_modules / .pnpm / registry.npmjs.org / react / 16.12.0 / node_modules / response, где содержится фактический пакет. Node следует за символической ссылкой, и путь к файлу, состоящий из 68 символов, достаточно короткий, чтобы pnpm легко избегал превышения ограничения Windows в 260 символов.

Один из оставшихся недостатков pnpm заключается в том, что поскольку он использует символические ссылки, он не работает с некоторыми инструментами для просмотра файлов, такими как Watchman. Это одна из причин, по которой Yarn отказался от первоначального плана использования символических ссылок. Он также не будет работать с файловыми системами FAT32, которые не поддерживают жесткие ссылки или символические ссылки. Это может быть проблемой только в том случае, если вы хотите сохранить свой проект на флешке или SD-карте.

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

  1. Https://www.ostechnix.com/explaining-soft-link-and-hard-link-in-linux-with-examples/
  2. Https://stackoverflow.com/questions/9042542/what-is-the-difference-between-ntfs-junction-points-and-symbolic-links/48586946#48586946
  3. Https://pnpm.js.org/en/faq

Рабочие места

В августе 2017 команда Yarn выпустила Рабочие области Yarn, функцию, которая упрощает загрузку и управление монорепозиториями.

Монорепозиторий - это репозиторий, содержащий несколько проектов. Monorepo часто используются как для хранения частного кода крупных компаний, таких как Google и Facebook, так и для проектов с открытым исходным кодом, таких как React и Babel. Многие разработчики предпочитают структуру монорепозитория, а не создание отдельного репозитория для каждого проекта. Это потому, что они считают, что монорепозиторий упрощает обновление версий и рефакторинг кода без создания нового репозитория.

Yarn помогает разработчикам управлять монорепозиториями, позволяя им устанавливать пакеты одновременно для всех проектов в своем монорепозитории. Команды yarn workspace также упрощают разработчикам управление отдельными проектами из корневого каталога.

Инструмент Lerna также имеет все возможности рабочих пространств Yarn и многое другое. Команда Yarn рекомендует использовать рабочие области, потому что они быстрее и стабильнее устанавливают пакеты. Они добавили, что Lerna тоже не может этого сделать, потому что это оболочка вокруг диспетчера пакетов, а не сам диспетчер пакетов. Lerna можно использовать с рабочими пространствами Yarn, и он по-прежнему полезен, если вы хотите использовать его функции, чтобы упростить публикацию пакетов в реестре npm.

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

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

PnP

В сентябре 2018 года команда Yarn представила Yarn Plug’n’Play (Yarn PnP). Yarn PnP решает те же проблемы, что и плоская структура node_modules и дублирование установки пакетов, которые pnpm исправил другим способом. Они решили использовать другой подход, чтобы ускорить установку пакетов и в конечном итоге устранить необходимость установки в сборках с непрерывной интеграцией.

В документации Yarn PnP говорится, что жесткие ссылки исключают дублирование пакетов, но их использование требует от Node времени, чтобы сделать кучу обращений к вашей операционной системе. Они более подробно описывают недостатки использования символических и жестких ссылок на странице 2 официального документа Yarn PnP.

С Yarn PnP, когда ваши пакеты установлены, yarn создает файл с именем .pnp.js вместо папки node_modules. В файле .pnp.js перечислены ваши пакеты, их зависимости и относительный путь к их расположению на вашем жестком диске.

Yarn PnP изменяет разрешение модуля по умолчанию для узла, по сути говоря, вместо того, чтобы искать каталоги node_modules в родительских папках файла, посмотрите файл .pnp.js. Если он найдет эту версию пакета там, он будет использовать поле packageLocation, чтобы перейти в соответствующее место на вашем диске.

При использовании Yarn PnP вы также можете заметить папку с названием .pnp, которая будет переименована в .yarn, когда выйдет Yarn версии 2. В нем хранятся пакеты с сценариями после установки - сценариями, которые запускаются сразу после установки пакета. Это связано с тем, что содержимое пакета со сценарием после установки, например node-sass, может различаться в зависимости от версии вашего узла. Это означает, что пакет необходимо устанавливать отдельно для каждого проекта, поскольку вы можете использовать разные версии узлов для каждого проекта.

Поскольку Yarn отвечает за разрешение зависимостей, а не за узел в Yarn PnP, Yarn может распознать, импортируете ли вы зависимость, которая указана как devDependency, а не как зависимость в вашем package.json.

Это решает еще одну проблему плоской структуры node_modules, которую pnpm не мог решить напрямую, потому что он полагался на систему разрешения модулей по умолчанию для узла. Без использования Yarn PnP вам пришлось бы решать эту проблему с помощью внешнего пакета, такого как package-preview от Золтана Кочана, чтобы поймать, когда вы импортируете devDependencies во время выполнения тестов.

Хотя Yarn не включает PnP по умолчанию, вы можете настроить его, добавив несколько строк кода. Но Yarn PnP в настоящее время не вносит изменений, внесенных pnpm версии 4, чтобы позволить зависимостям получать доступ к любой подзависимости. Это означает, что если вы используете пакет, который забыл добавить зависимость, ваше приложение сломается при использовании Yarn PnP, в то время как оно пока работает с другими менеджерами пакетов.

Как и во многих новых технологиях, потребуется время, прежде чем Yarn PnP станет стабильным. Facebook недавно отключил его, потому что он несовместим с достаточным количеством их пакетов.

Yarn PnP - это спецификация, а не эксклюзивная функция Yarn, поэтому в будущем она может быть принята другими менеджерами пакетов.

Будущее

Сопровождающие Yarn обнародовали свои планы на версию 2. Они включают оптимизацию Yarn, чтобы вам больше не нужно было запускать установку Yarn после клонирования репозитория, и упрощение использования Yarn с другими языками, помимо JavaScript. Пряжа PnP также будет включена по умолчанию.

Npm объявила о планах внести серьезные изменения в версию 8, которую они назвали tink. Tink будет работать, перезаписывая базовый модуль узла файловой системы (fs), чтобы загружать ваши пакеты во время выполнения в кеш, общий для всех ваших проектов.

Это дает пользователям несколько преимуществ. Вам больше не придется вводить npm install, даже если вы впервые используете пакет в новом проекте. Кроме того, в некоторых случаях npm сможет более разумно загружать только те части пакета, которые вам нужны. Замена модуля файловой системы Npm также сможет распознавать файлы TypeScript и JSX без какой-либо дополнительной настройки.

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

Поскольку npm еще не выпустил версию 7, я ожидаю, что пройдет некоторое время, прежде чем tink будет официально выпущен. Если вы не можете дождаться, вы можете опробовать разрабатываемую версию tink, но npm предупреждает, что не стоит использовать ее в продакшене.

Пряжа PnP и npm tink имеют разные компромиссы. Сопровождающий Yarn Маэль Нисон считает, что перезапись основных модулей узла является рискованной, потому что это достаточно серьезное изменение, которое, вероятно, приведет к появлению ошибок, которые могут сделать ваше приложение менее безопасным и надежным. Npm признает этот риск, но указывает, что Electron Уже успешно применил этот подход.

Npm также предпочитает свой подход, потому что Yarn PnP требует дополнительной настройки для работы с некоторыми инструментами, такими как webpack, Jest и TypeScript. Эти инструменты работали над включением Yarn PnP (jest, TypeScript) и webpack 5 будет поддерживать Yarn PnP по умолчанию, так что это, вероятно, не будет долгосрочной проблемой.

Надеюсь, вам понравился этот пост! Не стесняйтесь, дайте мне знать, что вы думаете, в комментариях.