blog
blog copied to clipboard
Yarn vs. npm vs. pnpm
Yarn vs. npm vs. pnpm
Inspirován zápiskem https://orta.io/notes/js/yarn-vs-npm, rád bych si taky poznamenal, jak se v roce 2021 dívám na situaci kolem JS package managerů.
Úvodem
JavaScript vypadá jako většina C-čkových jazyků, ale na rozdíl od těch klasických (Java / C# / PHP / ...) je hodně o konkrétních objektech namísto globálních jmen. Obecně tak není problém mít v projektu víc věcí jmenujících se stejně, resp. jednu knihovnu ve více verzích:

Ne každý to považuje za dobrou věc, ale já si moc dobře vzpomínám, jak jsme půl roku nemohli updatovat na novější PhpUnit, protože ten začal záviset na novější verzi knihovny X, kterou ještě jiný balíček nestihnul aktualizovat, a ještě horší to je u systémů, které člověk nemá plně pod kontrolou – VersionPress chtěl záviset na knihovnách typu Guzzle nebo Symfony FileSystem, ale nikdy jsme nemohli vědět, jestli výsledný WordPress web nebude používat další pluginy s nekompatibilními verzemi téhož. Zlé...
JavaScript tenhle problém nemá, ale to neznamená, že jsou věci jednoduché.
Věci jsou poměrně jednoduché v moderních prostředích typu ESM v browseru nebo Deno, kde se používají konkrétní importy z konkrétních cest, např.:
import { add } from 'https://x.nest.land/[email protected]/source/index.js';
Ale převážná realita v Node.js je pořád něco jako:
import { add } from 'ramda';
(Technicky require
, ale to je detail.)
A tam je otázka, co ta "ramda" je. Na odpovědi spolupracují dvě věci:
- Node.js module resolution algoritmus
- Package manager (připravuje soubory na disku tak, aby Node.js načetl správné moduly)
Jednička je daná, ale pro bod 2 existují různé softwary dělající různé kompromisy. A o nich je tenhle blog post.
npm
npm je klasika. Dlouhé roky jsme s ním žili a vlastně to bylo docela OK. Z dnešního pohledu mi sice připadá šílené, že každé npm install
mohlo vést k drobně jiné instalaci node_modules
, ale asi naše aplikace byly jednodušší, repozitáře menší atd. 😄
Když pak v roce 2016 přišel Yarn, nebylo co řešit – daleko rychlejší, konečně lockfiles, lepší command-line UX, workspaces atd.
npm jsem ale nepřestal sledovat, hlavně kvůli verzi 7, která měla v mnoha ohledech dohnat Yarn. Bohužel se někdy kolem let 2018/19 začalo npm Inc. dostávat do velkých problémů – startupu docházely peníze, řada lidí uvnitř byla toxických, nebyla radost to sledovat a npm si prošlo poměrně temným obdobím. Záchrana nakonec přišla ze strany GitHubu / Microsoftu – bylo to o fous, ale vlastně to líp dopadnout nemohlo.
Co se mi na npm líbí:
- (+) Přichází s Node.js, začátečníci ho prostě mají
- (+) Je to relativně jednoduchý package manager, nepouští se do kontroverzních instalačních strategií a je tak dobře kompatibilní
- (+) Stojí za ním silná firma (dnes už)
Co se mi na npm nelíbí:
- (-) CLI je pořád takové lehce neohrabané –
npm run <script>
místonpm <script>
, nutnost tam někdy dávat--
, já vlastně ani nevím, jestli jenpm install
ten správný příkaz nebo by to mělo býtnpm ci
, zkrátka řada drobností v použitelnosti. Ale s tím se dá žít. - (-) npm umí pouze tradiční
node_modules
instalaci, která má problémy typu doppelgangers nebo phantom dependencies (oba výborné články z dokumentace Rush.js) nebo nutnost dělat kompromisy kolem hoistování (viz např. změna layoutunode_modules
mezi verzemi 2 a 3). Větší projekty toto začne dřív nebo později trápit a npm na to nemá odpověď. - (-) Problematické roky a cca dvouleté zpoždění npm@7 byl varovný signál, který je potřeba uklidnit. To ale nedělají změny typu, že npm 7 začlo automaticky instalovat
peerDependencies
– kontroverzní rozhodnutí, a npm by podle mě nemělo jít proti jedné ze svých největších výhod.
Celkově bych si npm 7 asi už celkem v pohodě vybral na jednodušší JS projekt, možná to je i nejlepší volba. U složitějšího projektu (např. company monorepo) jsou ale limity node_modules
podstatné a nějaká alternativa je skoro "nutností".
Yarn
Yarn má za sebou dvě životní etapy:
- Yarn 1
- Yarn 2+, vydaný na začátku roku 2020
Yarn 1 byl miláček – používali ho "všichni" a dal JS světu mnoho dobrého – reproducible installs, rychlost, workspaces atd.
Yarn 2 ("Berry") vykročil tak trochu za hranice běžného package manageru – vedle instalace balíčků se snaží pomoct s DX v monorepu, s release workflows, ale taky má některé silné názory na to, jak by měl spolehlivý vývoj vypadat obecně – PnP, Zero-Installs atd.
Asi největší kontroverze je právě kolem PnP (Plug'n'Play) – tato instalační strategie existovala už za časů Yarnu 1 (a nikomu nevadila, protože byla volitelná a "schovaná"), ale Yarn 2 se rozhodl z ní udělat default. To byla za mě chyba, dejte si třeba tvíty z ledna 2020 nebo https://github.com/yarnpkg/berry/issues/766. Pořád to neutichlo, tohle je čerstvý příklad:

Je to škoda, protože Yarn 2 lze během dvou minut nakonfigurat tak, aby fungoval "postaru", ale nechme historie a pojďme k technickým věcem.
Co se mi na Yarnu líbí:
- (+) Velmi mu záleží na spolehlivosti instalací, možná až moc (viz některé z nevýhod níže), ale určitá maniakálnost v této oblasti je za mě lepší než nedeterminismus / "to bude dobré".
- (+) Umí víc instalačních strategií – node_modules, PnP, ale přes plugin by šlo napsat např. pnpm linker (https://github.com/yarnpkg/berry/issues/1845).
- (+) Instalační strategie lze používat dle situace, např. my momentálně používáme
node_modules
pro lokální vývoj (lepší DX), ale na CIčku už je PnP (odhalí např. chybějící dependencies / peerDependencies), stejně tak pro Docker buildy. Obecně mít víc možností oceňujeme. - (+) Yarn má výborný systém pluginů. I interní funkcionalita jsou všechno pluginy a přidávat lze další, například
plugin-typescript
automaticky doinstaluje odpovídající@types
, yarn.BUILD je hezký, minimalistický build systém, atd. - (+) Za dlouhé roky, co Yarn sleduju, mám respekt a důvěru ve vývojový tým – @arcanis, @merceyz, @larixer a další. Jsou to nejen šikovní inženýři, ale i nápomocní lidé na Discordu, dobře reagují v GitHub issues, posílají patche do cizích projektů, snaží se do Node.js dostat podporu více package managerů (corepack) atd. Důvěra v lidi za něčím tak důležitým, jako je PM, je pro mě důležitá.
Co se mi na Yarnu nelíbí:
- (-) Je tak flexibilní / umí toho tolik, že trvá ho poznat a plně pochopit. Yarn 1 vs. Yarn 2, node_modules vs. PnP, různé protokoly pro dependencies, Zero Install mode, je toho hodně a je snadné se v tom ztratit.
- (-) Yarn 2 není veřejně vydanou binárkou, např.
npm i -g yarn
nebobrew install yarn
vždy nainstaluje verzi 1.22, Yarn 2 se pak instaluje jako.cjs
soubor do konkrétního projektu. Na jednu stranu je dobré, že různé části většího monorepa můžou používat svou specifickou verzi Yarnu, a taky je filosoficky pěkné, že package manager je u projektu commitnutý v přesně dané verzi, na druhou stranu my obecně do Gitu binárky necommitujeme, takže nám to vytváří drobné dilema. (Zmínka bokem: y2.)
Celkově je Yarn pokročilý package manager, který i z velké části dokáže nahradit Lernu, build tool, monorepo managera atd. (což je za mě super, např. Lerna mě svého času vyloženě štvala; čím míň softwaru, tím líp). Hodí se nám dvě různé instalační strategie, různé stupně striktnosti, občas si do Yarnu napíšeme vlastní plugin, zkrátka pokročilý parťák.
pnpm
S pnpm nemáme moc reálných zkušeností, ale už dlouho po něm pokukujeme, protože jeho instalační strategie vypadá naprosto skvěle (zdroj):
node_modules
├── foo -> ./.pnpm/[email protected]/node_modules/foo
└── .pnpm
├── [email protected]
│ └── node_modules
│ ├── bar -> <store>/bar
│ └── qar -> ../../[email protected]/node_modules/qar
├── [email protected]
│ └── node_modules
│ ├── foo -> <store>/foo
│ ├── bar -> ../../[email protected]/node_modules/bar
│ └── qar -> ../../[email protected]/node_modules/qar
└── [email protected]
└── node_modules
└── qar -> <store>/qar
Trochu to připomíná PnP, ale zůstává u prostých složek namísto ZIP souborů, což je v mnoha ohledech praktičtější.
Zde se nehodlám pustit do většího hodnocení, protože mi chybí zkušenosti, ale určité dojmy jsou:
- Samotný layout vypadá skvěle, pnpm navíc umí i PnP nebo klasický n-m layout ("shamefully-hoist"), takže jde o nejuniverzálnějšího package managera. Hezký přehled zde.
- Klasicky je potřeba být opatrný před "zlobivými" projekty typu React Native, Angular, TypeScript apod., které dělají tvrdé předpoklady o struktuře node_modules, viz třeba tabulka Yarnu nebo starší issue pnpm, ale to je problém všech alternativ.
- pnpm používá globální "content-addressable store" (ono
<store>
v příkladu výše) a hardlinky na něj, což znamená, že daný balíček v konkrétní verzi je na disku pouze jednou. pnpm tak nevytváří černé díry. - pnpm bohužel kopíruje CLI npm, které není nijak skvělé.
- Je to malý projekt s de facto jedním maintainerem. Na druhou stranu, @zkochan je u mě v kategorii "skvělých lidí" – odvádí fantastickou práci.
- pnpm na mnoha místech doporučuje Rush.js, monorepo manager používaný Microsoftem, což je docela podstatné požehnání.
pnpm dále sledujeme, nebo by mi vlastně stačilo, kdyby se pnpm instalační strategie dostala do Yarnu jako plugin (https://github.com/yarnpkg/berry/issues/1845) 😎.
Asi se mi úplně nepovedlo udělat si pár krátkých poznámek 😄.
Package manager je pro nás super-důležitou věcí – už třeba instalace devDependencies v repo rootu do běžných node_modules není ve větším repu moc dobrý nápad, a vůbec s rostoucí komplexitou / množstvím balíčků je PM čím dál důležitější.
Já momentálně za "nejlepší" package manager považuju Yarn, ale vybrat si není jednoduché – tolik různých možností, tolik nuancí...
Opravy:
- První verze blog postu mluvila o tom, jak PnP bylo po vydání Yarnu 2 jedinou instalační strategií a node-modules linker přišel až později. To jsem si popletl s betou; stabilní Yarn 2.0 na node-modules linker počkal.
Nice article! (I had to read it through Google Translate so pray forgive if I misunderstood something 😄) Two little notes:
Pak ale přišel Yarn 2 a ten udělal jedno ~vysoce kontroverzní~ špatné rozhodnutí: instalační strategii PnP (Plug'n'Play), která existovala už za časů Yarnu 1, udělal jedinou možnou. Z Yarnu se tak dočasně stal JS package manager, který neuměl nainstalovat závislosti do
node_modules
, resp. idea byla, že kdo chce node_modules, má používat Yarn 1 – no zkrátka chyba.
No version of Yarn 2.x shipped without node_modules support. It's been there from the very first stable release. I think some large accounts relayed incorrect information at the start (and we could have highlighted it a bit more, admittedly), but we specifically waited for it to be ready before getting out of beta.
Yarn 2 není veřejně vydanou binárkou, např.
npm i -g yarn
nebobrew install yarn
vždy nainstaluje verzi 1.22, Yarn 2 se pak instaluje jako.cjs
soubor do konkrétního projektu. Na jednu stranu je dobré, že různé části většího monorepa můžou používat svou specifickou verzi Yarnu, a taky je filosoficky pěkné, že package manager je u projektu commitnutý v přesně dané verzi, na druhou stranu my obecně do Gitu binárky necommitujeme, takže nám to vytváří drobné dilema. (Zmínka bokem: y2.)
Node just voted this week to include Corepack in future releases, so there's reasonable hope that the install experience will improve significantly during the next few months!
Děkuji za dobré slova!
What also makes pnpm unique is that it uses a content-addressable store and files are hard-linked from it. So you end up using less disk space for your dependencies. And it probably contributes to faster installation times as well.
@arcanis You're right, I was probably thinking about the pre-release period in 2019 where we were evaluating Yarn 2 (beta) and PnP was the only linker back then. I'll update the blog post, thanks for catching this.
What's your account of why there was a such a strong backlash against Yarn 2 then? Was it only because of the changed defaults? Would be great to know how you remember that...
@zkochan That is a great point, I'll add it to the lists of pnpm advantages. Thanks!
Díky za pěkné shrnutí. U nás dlouhodobě řešíme, na co zmigrovat naše NX monorepo z yarn classic. Největší bolístkou je rychlost (pomalost) předávání node_modules artefaktů v rámci Gitlab CI jobů. V tomhle ohledu zatím jasně vítězí Yarn pnp, jelikož upload/download několika zip souborů je řádově rychlejší (5s vs 1m:20s), než udělat to samé s node_modules složkou. Bohužel se nám zatím v pnp módu nepodařilo dosáhnout funkčního stavu.
@belaczek Většinou se "cachuje cache" neboli (globální) Yarn cache. Dělá to tak i actions/cache, viz zde.
@borekb Jojo, tady jde ale o předávání node_modules atrefaktů mezi jednotlivými joby v CI. GitLab to má tak, že každý job se spouští na čistém podu a je tak potřeba pokažde stáhnout a extrahovat už předtím nainstalované artefakty. A tohle stahování a extrahování docela trvá (alespoň na nešem železe). Alternativou by bylo instalovat node modules v každém jobu zvlášť, ale to by bylo ještě pomalejší.