Guida ai Monorepos per il codice front-end
Pubblicato: 2022-03-11I monorepos sono un argomento caldo per una discussione. Di recente sono stati pubblicati molti articoli sul perché dovresti e non dovresti usare questo tipo di architettura per il tuo progetto, ma la maggior parte di essi è distorta in un modo o nell'altro. Questa serie è un tentativo di raccogliere e spiegare quante più informazioni possibili per capire come e quando utilizzare i monorepos.
Un Monorepository è un concetto architettonico, che contiene sostanzialmente tutto il significato nel suo titolo. Invece di gestire più repository, mantieni tutte le parti di codice isolate all'interno di un repository. Tieni presente la parola isolato : significa che monorepo non ha nulla in comune con le app monolitiche. Puoi mantenere molti tipi di app logiche all'interno di un repository; ad esempio, un sito Web e la relativa app iOS.
Questo concetto è relativamente vecchio ed è apparso circa un decennio fa. Google è stata una delle prime aziende ad adottare questo approccio per la gestione delle proprie codebase. Potresti chiederti, se esiste da un decennio, allora perché è un argomento così caldo solo ora? Per lo più, nel corso degli ultimi 5-6 anni, molte cose hanno subito cambiamenti radicali. ES6, preprocessori SCSS, task manager, npm e così via: oggigiorno, per mantenere una piccola app basata su React, devi avere a che fare con bundler di progetti, suite di test, script CI/CD, configurazioni Docker e chissà cos'altro. E ora immagina che invece di una piccola app, devi mantenere un'enorme piattaforma composta da molte aree funzionali. Se stai pensando all'architettura, vorrai fare due cose principali: separare le preoccupazioni ed evitare duplicazioni del codice.
Per fare in modo che ciò accada, probabilmente vorrai isolare funzionalità di grandi dimensioni in alcuni pacchetti e quindi utilizzarle tramite un unico punto di ingresso nell'app principale. Ma come gestisci quei pacchetti? Ogni pacchetto dovrà avere la propria configurazione dell'ambiente del flusso di lavoro, e questo significa che ogni volta che si desidera creare un nuovo pacchetto, sarà necessario configurare un nuovo ambiente, copiare tutti i file di configurazione e così via. Oppure, ad esempio, se devi modificare qualcosa nel tuo sistema di build, dovrai esaminare ogni repository, eseguire un commit, creare una richiesta pull e attendere ogni build, il che ti rallenta molto. A questo punto, stiamo incontrando i monorepos.
Invece di avere molti repository con le proprie configurazioni, avremo solo una fonte di verità: il monorepo: un corridore della suite di test, un file di configurazione Docker e una configurazione per Webpack. E hai ancora scalabilità, opportunità di separare le preoccupazioni, condivisione del codice con pacchetti comuni e molti altri vantaggi. Suona bene, vero? Bene, lo è. Ma ci sono anche alcuni inconvenienti. Diamo un'occhiata da vicino ai pro e ai contro esatti dell'utilizzo del monorepo in natura.
Vantaggi di Monorepo:
- Un posto dove archiviare tutte le configurazioni e i test. Poiché tutto si trova all'interno di un repository, puoi configurare il tuo CI/CD e bundler una volta e quindi riutilizzare le configurazioni per creare tutti i pacchetti prima di pubblicarli in remoto. Lo stesso vale per i test di unità, e2e e di integrazione: il CI sarà in grado di avviare tutti i test senza dover gestire una configurazione aggiuntiva.
- Refactoring semplice delle funzionalità globali con commit atomici. Invece di eseguire una richiesta pull per ogni repository, per capire in quale ordine creare le modifiche, devi solo fare una richiesta pull atomica che conterrà tutti i commit relativi alla funzionalità su cui stai lavorando.
- Pubblicazione semplificata dei pacchetti. Se prevedi di implementare una nuova funzionalità all'interno di un pacchetto che dipende da un altro pacchetto con codice condiviso, puoi farlo con un singolo comando. È una funzione che richiede alcune configurazioni aggiuntive, che verranno discusse in seguito in una parte di revisione degli strumenti di questo articolo. Attualmente, esiste una ricca selezione di strumenti, tra cui Lerna, Yarn Workspaces e Bazel.
- Gestione delle dipendenze più semplice. Solo un package.json . Non c'è bisogno di reinstallare le dipendenze in ogni repository ogni volta che vuoi aggiornare le tue dipendenze.
- Riutilizza il codice con i pacchetti condivisi mantenendoli comunque isolati. Monorepo ti consente di riutilizzare i tuoi pacchetti da altri pacchetti mantenendoli isolati l'uno dall'altro. È possibile utilizzare un riferimento al pacchetto remoto e consumarli tramite un unico punto di ingresso. Per utilizzare la versione locale, puoi utilizzare i collegamenti simbolici locali. Questa funzionalità può essere implementata tramite script bash o introducendo alcuni strumenti aggiuntivi come Lerna o Yarn.
Svantaggi di Monorepo:
- Non c'è modo di limitare l'accesso solo ad alcune parti dell'app. Sfortunatamente, non puoi condividere solo la parte del tuo monorepo: dovrai dare accesso all'intero codebase, il che potrebbe causare alcuni problemi di sicurezza.
Scarse prestazioni di Git quando si lavora su progetti su larga scala. Questo problema inizia a comparire solo su applicazioni enormi con più di un milione di commit e centinaia di sviluppatori che svolgono il proprio lavoro contemporaneamente ogni giorno sullo stesso repository. Ciò diventa particolarmente problematico poiché Git utilizza un grafo aciclico diretto (DAG) per rappresentare la storia di un progetto. Con un numero elevato di commit, qualsiasi comando che percorre il grafico potrebbe diventare lento man mano che la cronologia si approfondisce. Anche le prestazioni rallentano a causa del numero di riferimenti (ad es. rami o tag, risolvibili rimuovendo i riferimenti che non servono più) e della quantità di file tracciati (nonché del loro peso, anche se è possibile risolvere problemi di file pesanti utilizzando Git LFS).
Nota: al giorno d'oggi, Facebook cerca di risolvere i problemi con la scalabilità VCS applicando patch a Mercurial e, probabilmente presto, questo non sarà un grosso problema.
- Tempo di costruzione più elevato. Poiché avrai molto codice sorgente in un unico posto, ci vorrà molto più tempo prima che il tuo CI esegua tutto al fine di approvare ogni PR.
Revisione degli strumenti
Il set di strumenti per la gestione dei monorepo è in continua crescita e, attualmente, è davvero facile perdersi in tutta la varietà di sistemi di costruzione per i monorepo. Puoi sempre essere consapevole delle soluzioni popolari utilizzando questo repository. Ma per ora, diamo una rapida occhiata agli strumenti che sono ampiamente utilizzati al giorno d'oggi con JavaScript:
- Bazel è il sistema di build orientato al monorepo di Google. Altro su Bazel: Awesome-bazel
- Yarn è uno strumento di gestione delle dipendenze JavaScript che supporta i repository mono tramite gli spazi di lavoro.
- Lerna è uno strumento per la gestione di progetti JavaScript con più pacchetti, basato su Yarn.
La maggior parte degli strumenti utilizza un approccio molto simile, ma ci sono alcune sfumature.

Approfondiremo il flusso di lavoro di Lerna e gli altri strumenti nella Parte 2 di questo articolo poiché si tratta di un argomento piuttosto ampio. Per ora, diamo solo una panoramica di cosa c'è dentro:
Lerna
Questo strumento aiuta davvero a gestire le versioni semantiche, impostare il flusso di lavoro di costruzione, spingere i tuoi pacchetti, ecc. L'idea principale alla base di Lerna è che il tuo progetto ha una cartella dei pacchetti, che contiene tutte le parti di codice isolate. E oltre ai pacchetti, hai un'app principale, che ad esempio può risiedere nella cartella src. Quasi tutte le operazioni in Lerna funzionano tramite una semplice regola: esegui l'iterazione di tutti i tuoi pacchetti ed esegui alcune azioni su di essi, ad esempio aumentare la versione del pacchetto, aggiornare la dipendenza di tutti i pacchetti, compilare tutti i pacchetti, ecc.
Con Lerna, hai due opzioni su come utilizzare i tuoi pacchetti:
- Senza spingerli in remoto (NPM)
- Spingere i tuoi pacchi in remoto
Durante l'utilizzo del primo approccio, sei in grado di utilizzare riferimenti locali per i tuoi pacchetti e fondamentalmente non ti interessano i collegamenti simbolici per risolverli.
Ma se stai usando il secondo approccio, sei costretto a importare i tuoi pacchetti da remoto. (ad esempio, import { something } from @yourcompanyname/packagename;
), il che significa che otterrai sempre la versione remota del tuo pacchetto. Per lo sviluppo locale, dovrai creare collegamenti simbolici nella radice della tua cartella per fare in modo che il bundler risolva i pacchetti locali invece di usare quelli che si trovano all'interno del tuo node_modules/
. Ecco perché, prima di avviare Webpack o il tuo bundler preferito, dovrai avviare lerna bootstrap
, che collegherà automaticamente tutti i pacchetti.
Filato
Yarn inizialmente è un gestore delle dipendenze per i pacchetti NPM, che inizialmente non è stato creato per supportare i monorepos. Ma nella versione 1.0, gli sviluppatori di Yarn hanno rilasciato una funzionalità chiamata Workspaces . Al momento del rilascio, non era così stabile, ma dopo un po' è diventato utilizzabile per progetti di produzione.
L'area di lavoro è fondamentalmente un pacchetto, che ha il suo package.json e può avere alcune regole di build specifiche (ad esempio, un tsconfig.json separato se usi TypeScript nei tuoi progetti). In realtà puoi in qualche modo gestire senza Yarn Workspaces usando bash e avere la stessa identica configurazione, ma questo strumento aiuta a facilitare il processo di installazione e aggiornare le dipendenze per pacchetto.
A colpo d'occhio, Yarn con i suoi spazi di lavoro offre le seguenti utili funzioni:
- Singola cartella
node_modules
nella radice per tutti i pacchetti. Ad esempio, se haipackages/package_a
epackages/package_b
—con il loropackage.json
—tutte le dipendenze verranno installate solo nella radice. Questa è una delle differenze tra il modo in cui Yarn e Lerna funzionano. - Collegamento simbolico delle dipendenze per consentire lo sviluppo di pacchetti locali.
- File di blocco singolo per tutte le dipendenze.
- Aggiornamento mirato delle dipendenze nel caso in cui desideri reinstallare le dipendenze per un solo pacchetto. Questo può essere fatto usando il flag
-focus
. - Integrazione con Lerna. Puoi facilmente fare in modo che Yarn gestisca tutta l'installazione/collegamento simbolico e lasciare che Lerna si occupi della pubblicazione e del controllo della versione. Questa è la configurazione più popolare finora poiché richiede meno sforzo ed è facile da lavorare.
Link utili:
- Aree di lavoro del filato
- Come creare un progetto TypeScript mono-repo
Bazel
Bazel è uno strumento di compilazione per applicazioni su larga scala, in grado di gestire dipendenze multilingua e supportare molti linguaggi moderni (Java, JS, Go, C++, ecc.). Nella maggior parte dei casi, l'utilizzo di Bazel per applicazioni JS di piccole e medie dimensioni è eccessivo, ma su larga scala può fornire molti vantaggi grazie alle sue prestazioni.
Per sua natura, Bazel è simile a Make, Gradle, Maven e altri strumenti che consentono build di progetti in base al file che contiene una descrizione delle regole di build e delle dipendenze del progetto. Lo stesso file in Bazel si chiama BUILD e si trova all'interno dell'area di lavoro del progetto Bazel. Il file BUILD utilizza il suo Starlark, un linguaggio di build di alto livello leggibile dall'uomo che assomiglia molto a Python.
Di solito, non avrai a che fare molto con BUILD perché c'è molto standard che può essere facilmente trovato sul web e che è già configurato e pronto per lo sviluppo. Ogni volta che vuoi costruire il tuo progetto, Bazel fa sostanzialmente quanto segue:
- Carica i file BUILD rilevanti per la destinazione.
- Analizza gli input e le relative dipendenze, applica le regole di compilazione specificate e produce un grafico delle azioni.
- Esegue le azioni di compilazione sugli input fino a quando non vengono prodotti gli output di compilazione finali.
Link utili:
- JavaScript e Bazel: documenti per la creazione di un progetto Bazel per JS da zero.
- Regole JavaScript e TypeScript per Bazel – Boilerplate per JS.
Conclusione
I monorepos sono solo uno strumento. Ci sono molte discussioni sul fatto che abbia un futuro o meno, ma la verità è che in alcuni casi questo strumento fa il suo lavoro e lo affronta in modo efficiente. Nel corso degli ultimi anni, questo strumento si è evoluto, ha acquisito molta più flessibilità, superato molti problemi e rimosso un livello di complessità in termini di configurazione.
Ci sono ancora molti problemi da risolvere, come le scarse prestazioni di Git, ma si spera che questo verrà risolto nel prossimo futuro.
Se desideri imparare a creare una solida pipeline CI/CD per la tua app, ti consiglio Come creare una pipeline di distribuzione iniziale efficace con GitLab CI .