Yarn: Un nuovo gestore di pacchetti per JavaScript – Facebook Engineering

Nella comunità JavaScript, gli ingegneri condividono centinaia di migliaia di pezzi di codice per evitare di riscrivere componenti di base, librerie o framework propri. Ogni pezzo di codice può a sua volta dipendere da altri pezzi di codice, e queste dipendenze sono gestite dai package manager. Il più popolare gestore di pacchetti JavaScript è il client npm, che fornisce accesso a più di 300.000 pacchetti nel registro npm. Più di 5 milioni di ingegneri usano il registro npm, che vede fino a 5 miliardi di download ogni mese.

Abbiamo usato con successo il client npm a Facebook per anni, ma con la crescita delle dimensioni della nostra base di codice e del numero di ingegneri, ci siamo imbattuti in problemi di coerenza, sicurezza e prestazioni. Dopo aver cercato di risolvere ogni problema man mano che si presentava, abbiamo deciso di costruire una nuova soluzione per aiutarci a gestire le nostre dipendenze in modo più affidabile. Il prodotto di questo lavoro si chiama Yarn – un client npm alternativo veloce, affidabile e sicuro.

Siamo lieti di annunciare il rilascio open source di Yarn, una collaborazione con Exponent, Google e Tilde. Con Yarn, gli ingegneri hanno ancora accesso al registro npm, ma possono installare i pacchetti più rapidamente e gestire le dipendenze in modo coerente tra le macchine o in ambienti offline sicuri. Yarn permette agli ingegneri di muoversi più velocemente e con fiducia quando usano il codice condiviso, in modo che possano concentrarsi su ciò che conta: costruire nuovi prodotti e funzionalità.

L’evoluzione della gestione dei pacchetti JavaScript in Facebook

Nei giorni precedenti ai package manager, era normale per gli ingegneri JavaScript fare affidamento su un piccolo numero di dipendenze memorizzate direttamente nei loro progetti o servite da un CDN. Il primo grande gestore di pacchetti JavaScript, npm, è stato costruito poco dopo l’introduzione di Node.js, ed è diventato rapidamente uno dei più popolari gestori di pacchetti al mondo. Migliaia di nuovi progetti open source sono stati creati e gli ingegneri hanno condiviso più codice che mai.

Molti dei nostri progetti a Facebook, come React, dipendono dal codice nel registro npm. Tuttavia, quando siamo cresciuti internamente, abbiamo affrontato problemi di coerenza nell’installazione delle dipendenze su diverse macchine e utenti, la quantità di tempo che ci voleva per inserire le dipendenze, e abbiamo avuto alcuni problemi di sicurezza con il modo in cui il client npm esegue automaticamente il codice da alcune di queste dipendenze. Abbiamo tentato di costruire soluzioni intorno a questi problemi, ma spesso essi stessi hanno sollevato nuovi problemi.

Tentativi di scalare il client npm

Inizialmente, seguendo le best practice prescritte, abbiamo solo fatto il check in package.json e chiesto agli ingegneri di eseguire manualmente npm install. Questo ha funzionato abbastanza bene per gli ingegneri, ma si è rotto nei nostri ambienti di integrazione continua, che devono essere sandboxed e tagliati fuori da internet per ragioni di sicurezza e affidabilità.

La soluzione successiva che abbiamo implementato è stata quella di controllare tutti i node_modules nel repository. Anche se questo ha funzionato, ha reso alcune semplici operazioni abbastanza difficili. Per esempio, l’aggiornamento di una versione minore di babel generava un commit di 800.000 linee che era difficile da atterrare e innescava regole di lint per sequenze di byte utf8 non valide, terminazioni di linea di windows, immagini non png-crushed e altro. Unire le modifiche a node_modules spesso richiedeva agli ingegneri un giorno intero. Il nostro team di controllo dei sorgenti ha anche sottolineato che la nostra cartella node_modules controllata era responsabile di una quantità enorme di metadati. Il React Native package.json attualmente elenca solo 68 dipendenze, ma dopo aver eseguito npm install la directory node_modules contiene 121.358 file.

Abbiamo fatto un ultimo tentativo di scalare il client npm per lavorare con il numero di ingegneri di Facebook e la quantità di codice che dobbiamo installare. Abbiamo deciso di zippare l’intera cartella node_modules e caricarla su un CDN interno in modo che sia gli ingegneri che i nostri sistemi di integrazione continua possano scaricare ed estrarre i file in modo coerente. Questo ci ha permesso di rimuovere centinaia di migliaia di file dal controllo dei sorgenti, ma ha fatto sì che gli ingegneri avessero bisogno di un accesso ad internet non solo per estrarre nuovo codice, ma anche per costruirlo.

Abbiamo anche dovuto lavorare intorno ai problemi con la funzione shrinkwrap di npm, che abbiamo usato per bloccare le versioni delle dipendenze. I file shrinkwrap non sono generati di default e andranno fuori sincrono se gli ingegneri si dimenticano di generarli, così abbiamo scritto uno strumento per verificare che il contenuto del file shrinkwrap corrisponda a ciò che è in node_modules. Questi file sono enormi blob JSON con chiavi non ordinate, quindi le modifiche genererebbero commit massicci e difficili da rivedere. Per mitigare questo, abbiamo dovuto aggiungere uno script aggiuntivo per ordinare tutte le voci.

Infine, l’aggiornamento di una singola dipendenza con npm aggiorna anche molte altre non correlate in base alle regole semantiche di versioning. Questo rende ogni cambiamento molto più grande del previsto, e dover fare cose come il commit node_modules o l’upload su un CDN rendeva il processo meno ideale per gli ingegneri.

Costruire un nuovo client

Piuttosto che continuare a costruire infrastrutture intorno al client npm, abbiamo deciso di provare a guardare il problema in modo più olistico. E se invece avessimo tentato di costruire un nuovo client che affrontasse i problemi fondamentali che stavamo riscontrando? Sebastian McKenzie nel nostro ufficio di Londra ha iniziato a lavorare su questa idea e ci siamo subito entusiasmati per il suo potenziale.

Mentre lavoravamo su questo, abbiamo iniziato a parlare con gli ingegneri di tutto il settore e abbiamo scoperto che hanno affrontato una serie simile di problemi e hanno tentato molte delle stesse soluzioni, spesso concentrate sulla risoluzione di un singolo problema alla volta. È diventato ovvio che collaborando sull’intera serie di problemi che la comunità stava affrontando, avremmo potuto sviluppare una soluzione che funzionasse per tutti. Con l’aiuto di ingegneri di Exponent, Google e Tilde, abbiamo costruito il client Yarn e testato e convalidato le sue prestazioni su tutti i principali framework JS e per ulteriori casi d’uso al di fuori di Facebook. Oggi, siamo entusiasti di condividerlo con la comunità.

Introduzione a Yarn

Yarn è un nuovo gestore di pacchetti che sostituisce il flusso di lavoro esistente per il client npm o altri gestori di pacchetti pur rimanendo compatibile con il registro npm. Ha le stesse caratteristiche dei flussi di lavoro esistenti, ma funziona più velocemente, in modo più sicuro e più affidabile.

La funzione primaria di qualsiasi gestore di pacchetti è quella di installare alcuni pacchetti – un pezzo di codice che serve a uno scopo particolare – da un registro globale nell’ambiente locale di un tecnico. Ogni pacchetto può dipendere o meno da altri pacchetti. Un tipico progetto potrebbe avere decine, centinaia o anche migliaia di pacchetti nel suo albero delle dipendenze.

Queste dipendenze sono versionate e installate in base al semantic versioning (semver). Semver definisce uno schema di versioning che riflette i tipi di cambiamenti in ogni nuova versione, se un cambiamento rompe un’API, aggiunge una nuova caratteristica o corregge un bug. Tuttavia, semver si basa sul fatto che gli sviluppatori di pacchetti non commettano errori – le modifiche di rottura o i nuovi bug possono trovare la loro strada nelle dipendenze installate se le dipendenze non sono bloccate.

Architettura

Nell’ecosistema Node, le dipendenze sono collocate in una directory node_modules nel tuo progetto. Tuttavia, questa struttura di file può differire dall’albero delle dipendenze reale, poiché le dipendenze duplicate vengono fuse insieme. Il client npm installa le dipendenze nella directory node_modules in modo non deterministico. Questo significa che in base all’ordine di installazione delle dipendenze, la struttura di una directory node_modules potrebbe essere diversa da una persona all’altra. Queste differenze possono causare bug “funziona sulla mia macchina” che richiedono molto tempo per essere risolti.

Yarn risolve questi problemi di versioning e non-determinismo usando lockfiles e un algoritmo di installazione che è deterministico e affidabile. Questi lockfile bloccano le dipendenze installate ad una versione specifica, e assicurano che ogni installazione risulti esattamente nella stessa struttura di file in node_modules su tutte le macchine. Il lockfile scritto usa un formato conciso con chiavi ordinate per assicurare che i cambiamenti siano minimi e la revisione sia semplice.

Il processo di installazione è suddiviso in tre passi:

  1. Risoluzione: Yarn inizia a risolvere le dipendenze facendo richieste al registro e cercando ricorsivamente ogni dipendenza.
  2. Fetching: Successivamente, Yarn guarda in una directory di cache globale per vedere se il pacchetto necessario è già stato scaricato. Se non lo è, Yarn recupera il tarball del pacchetto e lo mette nella cache globale in modo da poter lavorare offline e non dover scaricare le dipendenze più di una volta. Le dipendenze possono anche essere messe nel controllo dei sorgenti come tarball per installazioni completamente offline.
  3. Collegamento: Infine, Yarn collega tutto insieme copiando tutti i file necessari dalla cache globale nella directory locale node_modules.

Facendo questi passi in modo pulito e avendo risultati deterministici, Yarn è in grado di parallelizzare le operazioni, che massimizza l’utilizzo delle risorse e rende il processo di installazione più veloce. Su alcuni progetti di Facebook, Yarn ha ridotto il processo di installazione di un ordine di grandezza, da diversi minuti a pochi secondi. Yarn usa anche un mutex per assicurare che più istanze CLI in esecuzione non si scontrino e non si inquinino a vicenda.

In tutto questo processo, Yarn impone garanzie rigorose sull’installazione dei pacchetti. Si ha il controllo su quali script del ciclo di vita vengono eseguiti per quali pacchetti. Le checksum dei pacchetti sono anche memorizzate nel lockfile per assicurare che si ottenga lo stesso pacchetto ogni singola volta.

Caratteristiche

Oltre a rendere le installazioni molto più veloci e affidabili, Yarn ha caratteristiche aggiuntive per semplificare ulteriormente il flusso di lavoro della gestione delle dipendenze.

  • Compatibilità con entrambi i flussi di lavoro npm e bower e supporta la miscelazione dei registri.
  • Possibilità di limitare le licenze dei moduli installati e un mezzo per l’output di informazioni sulla licenza.
  • Espone una stabile API JS pubblica con un log astratto per il consumo tramite strumenti di compilazione.
  • Leggibile, minimo, grazioso output CLI.

Yarn in produzione

A Facebook stiamo già usando Yarn in produzione, e sta funzionando molto bene per noi. Alimenta la gestione delle dipendenze e dei pacchetti per molti dei nostri progetti JavaScript. Con ogni migrazione abbiamo permesso agli ingegneri di costruire offline e contribuito ad accelerare il loro flusso di lavoro. 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.