Pnpm: самый продвинутый инструмент управления пакетами

внешний интерфейс GitHub

Привет~ Всем привет, сегодня я познакомлю вас с современным инструментом управления пакетами под названием pnpm, что в переводе с английского означаетperformant npm, что означает «высокопроизводительный npm», адрес официального сайта может относиться кПНП М.ИО/.

В настоящее время pnpm практикуется и внедряется во многих проектах внутри Byte, таких как команда TikTok FE на рисунке ниже Последняя версия инструмента Monorepo, разработанная нашей командой, также по умолчанию использует pnpm в качестве инструмента управления зависимостями внизу. слой.

По сравнению с yarn/npm, двумя широко используемыми инструментами управления пакетами, производительность pnpm значительно улучшилась.benchmarkДанные показывают, что в некоторых комплексных сценариях он примерно в два раза быстрее, чем npm/yarn:

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

управление зависимостями

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

механизм жестких ссылок

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

Так как же pnpm добился такого большого улучшения? Это потому, что есть компьютер под названиемHard linkМеханизмы,hard linkПозволяет пользователям находить файл по разным ссылкам на пути. pnpm сохранит проект в глобальном каталоге хранилищаnode_modulesдокументhard links.

Например, например, в проекте есть зависимость a размером 1 МБ, в pnpm кажется, что эта зависимость занимает 1 МБ каталога node_modules и 1 МБ каталога глобального хранилища одновременно (итого 2 МБ), но потому чтоhard linkМеханизм позволяет обращаться к одному и тому же 1 МБ пространства в двух каталогах из двух разных мест, поэтому на самом деле эта зависимость занимает только 1 МБ, а не 2 МБ.

Каталог магазина

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

Как правило, каталог магазина устанавливается по умолчанию.${os.homedir}/.pnpm-storeВ этом каталоге вы можете обратиться к@pnpm/store-pathКод в этом подпакете pnpm:

const homedir = os.homedir()
if (await canLinkToSubdir(tempFile, homedir)) {
  await fs.unlink(tempFile)
  // If the project is on the drive on which the OS home directory
  // then the store is placed in the home directory
  return path.join(homedir, relStore, STORE_VERSION)
}

Конечно, пользователи также могут.npmrcУстановите это местоположение каталога хранилища, но, как правило, каталог хранилища менее заметен для пользователей.

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

В случае с npm или yarn эта зависимость используется в нескольких проектах и ​​будет повторно загружаться при каждой установке.

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

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

Ответ, конечно, будет, для этой проблемы pnpm предоставляет команду для решения этой проблемы:pnpm store | pnpm.

В то же время эта команда предоставляет опцию, способ использованияpnpm store prune, который предоставляет функцию для удаления некоторых пакетов, на которые не ссылается глобальный проект, например, пакетаaxios@1.0.0На него ссылается проект, но модификация приводит к обновлению пакета в проекте до1.0.1, то аксиома 1.0.0 в хранилище становится пакетом без ссылок, выполняемpnpm store pruneВы можете удалить его в магазине.

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

структура node_modules

На официальном сайте pnpm есть очень классическая статья о введении структуры node_modules проекта pnpm:Flat node_modules is not the only way | pnpm.

В этой статье представлены некоторые файловые структуры текущих модулей node_modules pnpm.Например, использование pnpm в проекте устанавливает файл с именемexpressзависимости, то в node_modules будут сформированы следующие две структуры каталогов:

node_modules/express/...
node_modules/.pnpm/express@4.17.1/node_modules/xxx

Первый путь — это каталог, который nodejs будет искать в обычном пути поиска.Если вы посмотрите на содержимое этого каталога, вы обнаружите, что есть дажеnode_modulesНи один файл:

▾ express
    ▸ lib
      History.md
      index.js
      LICENSE
      package.json
      Readme.md

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

из которых это.pnpmэто каталог виртуального диска, а затем некоторые зависимости экспресс-зависимости будут плитовать в.pnpm/express@4.17.1/node_modules/Ниже этого каталога это гарантирует, что зависимости могут потребоваться без формирования глубокой иерархии зависимостей.

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

pnpmСуществуют чрезвычайно строгие требования к различению зависимостей разных версий.Если зависимость в проекте действительно зависит отpeerDepsВ конкретных версиях есть отличия, и такие зависимости будут в каталоге виртуального диска.pnpmСуществует относительно строгое разграничение, к которому можно отнести:ПНП М.ИО/как-пиры - ааа...Эта статья.

Подводя итог, по сути, pnpmnode_modulesСтруктура представляет собой структуру каталогов сетки + плитки. Эта структура зависимости в основном основана на способе мягкого соединения (т.е. символической ссылке).

механизм символических и жестких ссылок

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

После совместной работы, если есть проект, который зависит отbar@1.0.0а такжеfoo@1.0.0, то структура зависимостей, представленная окончательной структурой node_modules, может выглядеть так:

node_modules
└── bar // symlink to .pnpm/bar@1.0.0/node_modules/bar
└── foo // symlink to .pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar -> <store>/bar
    │           ├── index.js
    │           └── package.json
    └── foo@1.0.0
        └── node_modules
            └── foo -> <store>/foo
                ├── index.js
                └── package.json

node_modulesДва каталога bar и foo в каталоге .pnpm будут мягко связаны с реальными зависимостями в каталоге .pnpm, и эти реальные зависимости будут храниться в каталоге глобального хранилища с помощью жестких ссылок.

Проблемы совместимости

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

На самом деле жесткая ссылка на основные системы (Unix/Win) не проблема, но симлинк, то есть мягкое подключение, может иметь некоторые проблемы с совместимостью в windows, но для этой проблемы pnpm также предоставляет соответствующие решения:

В системах win используйте файл с именемjunctionsфункция замены мягкой ссылки, это решение лучше совместимо с win, чем символическая ссылка.

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

На самом деле, зависимости в каталоге хранилища также можно найти через программные ссылки.Сам Nodejs предоставляет услугу под названием--preserve-symlinksПараметр для поддержки симлинка, но на самом деле этот параметр на самом деле плохо поддерживает симлинк, поэтому автор отказался от схемы и принял путь жестких ссылок:

Для получения подробной информации см.GitHub.com/node будет /node…Вопрос обсуждается.

Поддержка монорепозиториев

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

поддержка рабочего пространства

Для проектов монорепозитория pnpm предоставляет рабочее пространство для поддержки.Подробности см. в официальной документации сайта:PNP M.IO/рабочие области/…

Решение болевой точки

Наиболее критикуемой проблемой в Monorepo обычно является проблема структуры зависимостей. Две распространенные проблемыPhantom dependenciesа такжеNPM doppelgangers,использоватьспешка официальный сайтКартина может очень хорошо показать две проблемы:

Следующие два вопроса будут представлены один за другим.

Phantom dependencies

Фантомные зависимости называются призрачными зависимостями, и объяснение очень простое, то есть пакет не установлен (package.json, но пользователи могут обращаться к этому пакету).

Причина этого явления, как правило, связана со структурой node_modules. Например, использование пряжи для установки зависимостей в проекте. В зависимости есть зависимость с именем foo. Зависимость foo также зависит от бара. Пряжа сделает плоскую структура для установленных node_modules.Обработка (которая также выполняется после npm v3) выровняет зависимости в node_modules, что эквивалентно тому, что foo и bar находятся на одном уровне. Затем, в соответствии с принципом поиска пути nodejs, пользователь может потребовать foo, а также может потребовать bar.

package.json -> foo(bar 为 foo 依赖)

node_modules

  /foo

  /bar -> 👻依赖

Затем бар здесь становится призрачной зависимостью.Если однажды определенная версия зависимости foo больше не зависит от бара или версия foo изменится, модульная часть require bar выдаст ошибку.

Вышеприведенное на самом деле просто простой пример, но согласно некоторым монорепозиториям (в основном дляlerna + yarn), это на самом деле относительно распространенное явление, и даже некоторые пакеты будут напрямую использовать этот неполный метод введения для уменьшения размера пакета.

Другой сценарий — в проекте рабочей области lerna + пряжа, потому что механизм подъема предусмотрен в пряже (то есть зависимости некоторых нижележащих подпроектов будут повышены до верхнего уровняnode_modules), такого рода фантомных зависимостей будет больше, некоторые низкоуровневые подпроекты часто требуют каких-то зависимостей, которые сами по себе не вводятся, и напрямую ищут зависимости узловых_модулей верхнего уровня (нахождение пути здесь в nodejs рекурсивный процесс вверх-вниз) и используйте.

И согласно вышеупомянутому pnpmnode_modulesОпираясь на структуру, это явление, очевидно, не идет, потому что зависимость квартиры будет поставлена.pnpmЕсли вы перейдете в этот каталог виртуального диска, пользователь вообще не сможет найти его через require.

Стоит отметить, что сам pnpm фактически предоставляет возможность обновить зависимость и организовать структуру node_modules в виде пряжи, автор назвал это как--shamefully-hoist, "позорный подъемник".....

NPM doppelgangers

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

Например, есть пакет, следующие зависимости lib_a, lib_b, lib_c, lib_d, от которых зависят a и butil_e@1.0.0, а c и d зависят отutil_e@2.0.0.

Тогда структура зависимостей раннего npm должна быть такой:

- package
- package.json
- node_modules
- lib_a
  - node_modules <- util_e@1.0.0
- lib_b
  - node_modules <- util_e@1.0.0
_ lib_c
  - node_modules <- util_e@2.0.0
- lib_d
  - node_modules <- util_e@2.0.0

Это неизбежно приведет к многократной установке множества зависимостей, поэтому есть операции для подъема и выравнивания зависимостей:

- package
- package.json
- node_modules
- util_e@1.0.0
- lib_a
- lib_b
_ lib_c
  - node_modules <- util_e@2.0.0
- lib_d
  - node_modules <- util_e@2.0.0

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

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

Сценарии, которые в настоящее время не применимы

Как упоминалось ранее, основная проблема с pnpm заключается в том, что символические ссылки (мягкие ссылки) имеют проблемы совместимости в некоторых сценариях.Вы можете обратиться к обсуждению, которое автор открыл на nodejs:GitHub.com/node будет /node…

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

  • Приложение Electron не может использовать pnpm
  • развернут вlambdaПриложения не могут использовать pnpm

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

Некоторые вещи, которые нужно сделать в будущем

из узлов

Для получения подробной информации см.GitHub.com/ПНП М/ПНП М/…

  • Если вы устанавливаете pnpm, вы можете в основном избавиться от среды выполнения nodejs для установки и использования.
  • Вы можете использовать разные версии nodejs для установки зависимостей через pnpm, аналогично функциям, предоставляемым nvm.

В настоящее время эта функция фактически достигла бета-версии, вы можете обратиться кУууу, эта лошадь plus.com/package/@company you…эта сумка. Для управления различными версиями функций nodejs вы можете обратиться к подкоманде env:pnpm.io/cli/env

Напишите несколько модулей, используя rust

В частности, вы можете увидетьGitHub.com/ПНП М/ПНП М/…Содержание этого обсуждения, вероятно, заключается в том, что автор надеется предоставить некоторые оболочки rust cli для некоторых подкоманд pnpm для повышения производительности.

На данный момент особо больших подвижек в этом нет, но идея автора мне все же нравится.Ответ автора на это "если этот pnpm этого не делает, то будут другие инструменты для этого, и в конце концов pnpm будет устранено».

В настоящее время автор также все еще находится в процессе изучения ржавчины, конкретный адрес склада оболочки cli rust может относиться к:github.com/pnpm/pn, в настоящее время…

Суммировать

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

Как «восходящая звезда» менеджера пакетов, pnpm прекрасно решает многие болевые точки, оставленные дизайном существующих инструментов управления пакетами npm, yarn и node_modules благодаря гениальному дизайну автора. В то же время сам автор также очень предприимчив, усердно работает над улучшением функций pnpm и планирует будущее направление развития, ожидая, что в будущем станет все лучше и лучше~