Yarn: Een nieuwe pakketbeheerder voor JavaScript – Facebook Engineering

In de JavaScript-gemeenschap delen technici honderdduizenden stukjes code, zodat we kunnen voorkomen dat we zelf basiscomponenten, -bibliotheken of -frameworks moeten herschrijven. Elk stukje code kan op zijn beurt weer afhankelijk zijn van andere stukjes code, en deze afhankelijkheden worden beheerd door package managers. De populairste JavaScript package manager is de npm client, die toegang biedt tot meer dan 300.000 packages in het npm register. Meer dan 5 miljoen engineers gebruiken het npm-register, dat maandelijks tot 5 miljard keer wordt gedownload.

We hebben de npm-client jarenlang met succes gebruikt bij Facebook, maar toen de omvang van onze codebase en het aantal engineers groeide, liepen we tegen problemen aan op het gebied van consistentie, beveiliging en prestaties. Na het proberen op te lossen voor elk probleem dat zich voordeed, zijn we begonnen met het bouwen van een nieuwe oplossing om ons te helpen onze afhankelijkheden betrouwbaarder te beheren. Het product van dat werk heet Yarn – een snelle, betrouwbare en veilige alternatieve npm client.

Het doet ons genoegen om de open source release van Yarn aan te kondigen, een samenwerking met Exponent, Google, en Tilde. Met Yarn hebben engineers nog steeds toegang tot het npm register, maar kunnen ze sneller packages installeren en afhankelijkheden consistent beheren op verschillende machines of in veilige offline omgevingen. Yarn stelt engineers in staat om sneller en met vertrouwen gedeelde code te gebruiken, zodat ze zich kunnen concentreren op wat belangrijk is – het bouwen van nieuwe producten en functies.

De evolutie van JavaScript-pakketbeheer bij Facebook

In de dagen voor pakketbeheerders was het gebruikelijk voor JavaScript-engineers om te vertrouwen op een klein aantal afhankelijkheden die direct in hun projecten waren opgeslagen of werden geserveerd door een CDN. De eerste grote JavaScript package manager, npm, werd gebouwd kort nadat Node.js werd geïntroduceerd, en het werd al snel een van de meest populaire package managers in de wereld. Er werden duizenden nieuwe open source-projecten gemaakt en technici deelden meer code dan ooit tevoren.

Veel van onze projecten bij Facebook, zoals React, zijn afhankelijk van code in het npm-register. Echter, toen we intern schaalden, ondervonden we problemen met de consistentie bij het installeren van dependencies op verschillende machines en gebruikers, de hoeveelheid tijd die het kostte om dependencies binnen te halen, en hadden we enkele beveiligingsproblemen met de manier waarop de npm-client code van sommige van deze dependencies automatisch uitvoert. We probeerden oplossingen te bouwen voor deze problemen, maar ze riepen vaak zelf nieuwe problemen op.

Pogingen om de npm client te schalen

In eerste instantie, volgens de voorgeschreven best practices, checkten we alleen package.json in en vroegen we engineers om npm install handmatig uit te voeren. Dit werkte goed genoeg voor engineers, maar ging kapot in onze continue integratie-omgevingen, die om veiligheids- en betrouwbaarheidsredenen gesandboxed en afgesloten moeten zijn van het internet.

De volgende oplossing die we implementeerden was om alle node_modules in het repository te checken. Hoewel dit werkte, maakte het sommige eenvoudige operaties erg moeilijk. Bijvoorbeeld, het updaten van een minor versie van babel genereerde een commit van 800.000 regels die moeilijk te landen was en lint regels activeerde voor ongeldige utf8 byte reeksen, windows regeleinden, niet png-crushed afbeeldingen, en meer. Het samenvoegen van wijzigingen in node_modules kostte engineers vaak een hele dag. Ons broncontroleteam wees er ook op dat onze ingecheckte node_modules map verantwoordelijk was voor een enorme hoeveelheid metadata. De React Native package.json vermeldt momenteel slechts 68 afhankelijkheden, maar na het uitvoeren van npm install bevat de node_modules map 121.358 bestanden.

We hebben nog een laatste poging gedaan om de npm-client te schalen om te kunnen werken met het aantal engineers bij Facebook en de hoeveelheid code die we moeten installeren. We besloten om de hele node_modules map te zippen en te uploaden naar een interne CDN zodat zowel engineers als onze continuous integration systemen de bestanden consistent konden downloaden en uitpakken. Hierdoor konden we honderdduizenden bestanden uit het bronbeheer verwijderen, maar hadden onze engineers wel toegang tot internet nodig, niet alleen om nieuwe code op te halen, maar ook om deze te bouwen.

We moesten ook problemen oplossen met de shrinkwrap-functie van npm, die we gebruikten om afhankelijkheidsversies vast te zetten. Shrinkwrap-bestanden worden niet standaard gegenereerd en zullen uit sync vallen als ingenieurs vergeten ze te genereren, dus schreven we een tool om te controleren of de inhoud van het shrinkwrap-bestand overeenkomt met wat er in node_modules staat. Deze bestanden zijn echter enorme JSON klodders met ongesorteerde sleutels, dus wijzigingen daarin zouden enorme, moeilijk te beoordelen commits genereren. Om dit te voorkomen, moesten we een extra script toevoegen om alle entries te sorteren.

Finitief, het bijwerken van een enkele dependency met npm werkt ook vele niet-gerelateerde dependencies bij, gebaseerd op semantische versiebeheer regels. Dit maakt elke wijziging veel groter dan verwacht, en het moeten doen van dingen als committen node_modules of uploaden naar een CDN maakte het proces minder dan ideaal voor engineers.

Bouwen van een nieuwe client

In plaats van door te gaan met het bouwen van infrastructuur rond de npm client, besloten we om te proberen het probleem meer holistisch te bekijken. Wat als we in plaats daarvan zouden proberen een nieuwe client te bouwen die de kernproblemen zou aanpakken waar we tegenaan liepen? Sebastian McKenzie in ons kantoor in Londen begon te hacken op dit idee en we werden al snel enthousiast over de potentie ervan.

Terwijl we hieraan werkten, begonnen we te praten met ingenieurs uit de hele industrie en ontdekten we dat ze met een vergelijkbare set problemen te maken hadden en veel van dezelfde oplossingen hadden geprobeerd, vaak gericht op het oplossen van een enkel probleem per keer. Het werd duidelijk dat door samen te werken aan het geheel van problemen waarmee de gemeenschap werd geconfronteerd, we een oplossing konden ontwikkelen die voor iedereen werkte. Met de hulp van ingenieurs van Exponent, Google en Tilde hebben we de Yarn-client ontwikkeld en de prestaties ervan getest en gevalideerd op alle belangrijke JS-frameworks en voor aanvullende gebruikssituaties buiten Facebook. Vandaag zijn we verheugd om het met de gemeenschap te delen.

Introducing Yarn

Yarn is een nieuwe package manager die de bestaande workflow voor de npm client of andere package managers vervangt terwijl het compatibel blijft met het npm register. Het heeft dezelfde functies als bestaande workflows, maar werkt sneller, veiliger en betrouwbaarder.

De primaire functie van een package manager is het installeren van een package – een stuk code dat een bepaald doel dient – vanuit een globaal register in de lokale omgeving van een engineer. Elk pakket kan al dan niet afhankelijk zijn van andere pakketten. Een typisch project kan tientallen, honderden of zelfs duizenden pakketten in zijn boom van afhankelijkheden hebben.

Deze afhankelijkheden worden geversioneerd en geïnstalleerd op basis van semantische versionering (semver). Semver definieert een versiebeheerschema dat de soorten wijzigingen in elke nieuwe versie weergeeft, of een wijziging een API breekt, een nieuwe functie toevoegt, of een bug repareert. Semver vertrouwt er echter op dat pakketontwikkelaars geen fouten maken – afbrekende wijzigingen of nieuwe bugs kunnen hun weg vinden naar geïnstalleerde dependencies als de dependencies niet zijn afgesloten.

Architectuur

In het Node ecosysteem worden dependencies geplaatst in een node_modules directory in je project. Deze bestandsstructuur kan echter afwijken van de werkelijke dependency tree omdat dubbele dependencies worden samengevoegd. De npm client installeert dependencies in de node_modules directory op niet-deterministische wijze. Dit betekent dat op basis van de volgorde waarin afhankelijkheden worden geïnstalleerd, de structuur van een node_modules directory verschillend kan zijn van de ene persoon tot de andere. Deze verschillen kunnen leiden tot “werkt op mijn machine” bugs die lang duren om op te sporen.

Yarn lost deze problemen rond versiebeheer en niet-determinisme op door lockfiles te gebruiken en een installatie algoritme dat deterministisch en betrouwbaar is. Deze lockfiles zetten de geïnstalleerde dependencies vast op een specifieke versie, en zorgen ervoor dat elke installatie resulteert in exact dezelfde bestandsstructuur in node_modules op alle machines. De geschreven lockfile gebruikt een beknopt formaat met geordende sleutels om ervoor te zorgen dat veranderingen minimaal zijn en review eenvoudig is.

Het installatieproces is opgedeeld in drie stappen:

  1. Resolutie: Yarn begint met het oplossen van dependencies door verzoeken te doen aan het register en recursief elke dependency op te zoeken.
  2. Fetching: Vervolgens kijkt Yarn in een globale cache directory om te zien of het benodigde pakket al is gedownload. Als dat niet zo is, haalt Yarn de tarball voor het pakket op en plaatst die in de globale cache, zodat het offline kan werken en de afhankelijkheden niet meer dan eens hoeft te downloaden. Dependencies kunnen ook in source control worden geplaatst als tarballs voor volledige offline installs.
  3. Koppelen: Tenslotte koppelt Yarn alles aan elkaar door alle benodigde bestanden uit de globale cache naar de lokale node_modules directory te kopiëren.

Door deze stappen netjes af te breken en deterministische resultaten te hebben, is Yarn in staat om operaties te parallelliseren, wat het gebruik van bronnen maximaliseert en het installatieproces sneller maakt. Op sommige Facebook-projecten heeft Yarn het installatieproces met een orde van grootte teruggebracht, van enkele minuten tot slechts enkele seconden. Yarn gebruikt ook een mutex om ervoor te zorgen dat meerdere lopende CLI instanties niet met elkaar botsen en elkaar vervuilen.

Door dit hele proces heen, legt Yarn strikte garanties op rond de installatie van pakketten. Je hebt controle over welke lifecycle scripts worden uitgevoerd voor welke packages. Pakket checksums worden ook opgeslagen in de lockfile om er zeker van te zijn dat je elke keer hetzelfde pakket krijgt.

Features

Naast dat Yarn de installatie veel sneller en betrouwbaarder maakt, heeft het ook extra features om de dependency management workflow verder te vereenvoudigen.

  • Compatibiliteit met zowel de npm en bower workflows en ondersteunt het mixen van registries.
  • Mogelijkheid om licenties van geïnstalleerde modules te beperken en een manier om licentie-informatie te exporteren.
  • Ontsluit een stabiele openbare JS API met geabstraheerde logging voor consumptie via build tools.
  • Leesbare, minimale, mooie CLI-uitvoer.

Yarn in productie

Bij Facebook gebruiken we Yarn al in de productie, en het werkt erg goed voor ons. Het ondersteunt het afhankelijkheids- en pakketbeheer voor veel van onze JavaScript-projecten. Met elke migratie hebben we engineers in staat gesteld offline te bouwen en hebben we hun workflow versneld. You can see how install times for Yarn and npm compare on React Native under different conditions, which you can find here.

Getting started

The easiest way to get started is to run:

npm install -g yarn

yarn

The yarn CLI replaces npm in your development workflow, either with a matching command or a new, similar command:

  • npm installyarn

    With no arguments, the yarn command will read your package.json, fetch packages from the npm registry, and populate your node_modules folder. It is equivalent to running npm install.

  • npm install --save <name>yarn add <name>

    We removed the “invisible dependency” behavior of npm install <name> and split the command. Running yarn add <name> is equivalent to running npm install --save <name>.

Future

Many of us came together to build Yarn to solve common problems, and we knew that we wanted Yarn to be a true community project that everyone can use. Yarn is now available on GitHub and we’re ready for the Node community to do what it does best: Use Yarn, share ideas, write documentation, support each other, and help build a great community to care for it. We believe that Yarn is already off to a great start, and it can be even better with your help.