developer.overheid.nl

Ontwikkelaarsportaal van de Nederlandse overheid

Ga naar hoofdinhoud

Overweeg om node_modules in git repositories te zetten

· 9 minuten leestijd
Tim van der Lippe
Architect/API Adviseur - Logius

In de meeste projecten is dit de perceptie: node_modules worden door package managers geinstalleerd en zijn dus geen source code die in een git repository thuis hoort. Deze blog post probeert je aan het denken te zetten. Niet om je te overtuigen dat het altijd een goed idee is om node_modules in git repositories te zetten, maar om te realiseren dat in sommige projecten het verstandig kan zijn.

TL;DR

node_modules worden onbewust groot, met Gigabytes aan disk usage tot gevolg. Grote projecten checken node_modules in hun git om continue te monitoren van de gevolgen van upgrades. Met continue opschoonacties is het doenlijk om deze stap te nemen, wat voor langdurige (publieke) projecten erg waardevol is.

Wat zijn node_modules?

Voor degenen die geen frontend software development hebben gedaan, node_modules is een folder die JavaScript packages bevat die door andere developers in de NPM registry zijn gezet.

Er zijn heel veel packages op de NPM registry met een groot scala aan functionaliteit en diepgang.

Dit is inherent aan de JavaScript community, waar de programmeertaal van nature weinig "out-of-the-box" aanleverde. Er is actief gepromoot om kleine packages te publiceren die een oplossing bieden voor een specifiek probleem. Het heeft namelijk waarde als ieder package 1 specifiek probleem oplost en zich daar op focust.

Over tijd zagen we een trend: de node_modules folder in een regulier frontend project kan groot worden als gevolg van een gigantische dependency tree. Dit omdat er vaak lagen op lagen van dependencies worden gebruikt om zo tot een groter geheel te komen. Het is daarom gebruikelijk om Gigabytes aan disk space kwijt te zijn aan de node_modules van alle projecten waar je aan werkt. Hier zijn al vaker memes van gemaakt, dus deze blog post zal over deze trend niet verder uitwijden.

Meme over van de grootte van een node_modules folder. Source: /r/programmerhumor on Reddit

Een groot aantal dependencies resulteert in complexiteit

Als we naar aspecten als security en dependency management kijken, dan zijn dat grote opgaven voor frontend projecten.

De hoeveelheid transitive dependencies die (al dan niet onbewust) in een software applicatie terecht komen is dusdanig groot, dat analyse van al die packages lastig is.

notitie

Een transitive dependency is een dependency van een dependency (recursief gedefinieerd). Dit zijn dus alle dependencies die je in een project niet direct hebt gespecificeerd, maar die wel nodig zijn voor het geheel.

Tegelijkertijd is de kans op "dependency rot" ook groter naarmate meer dependencies worden meegenomen. De kans dat 1 van alle packages in een node_modules in het afgelopen jaar niets aan is veranderd is groot. Dit zorgt ervoor dat als er een transitive dependency in een stilstaande dependency zit, en deze package niet wordt geupdate, het ook in alle node_modules van de afnemers zit.

Het is lastig om dit overzichtelijk te houden, wat in de praktijk resulteert in het vergeten of actief ontwijken van het updaten van problematische dependencies.

En dat is ook menselijk, want het kost simpelweg veel tijd en energie om hier constant bovenop te zitten. Als de problemen dan een dermate grootte hebben dat het niet bij te benen is, dan haakt men af. Dit zorgt dus inherent voor dat er meer stilstaande packages komen en het probleem verergert.

Stilstand en tech debt

Deze stilstaande packages en resulterende tech debt zullen in elk project voorkomen, daar is ook niet per se iets verkeerds aan. Er moeten prioriteiten worden gesteld en met een naderende deadline zullen er scherpe keuzes worden gemaakt. Ik verwacht dan ook niet dat het schoonhouden van een dependency tree hoog op het prioriteitenlijstje zal staan.

Hier is wel een verschil in hoe in een private context en in de publieke sector mee om wordt gegaan. Waar in een private context er gekeken wordt of een product nog levensvatbaar is, heeft een publieke sector projecten die een stuk langer lopen uit (wettelijke) noodzaak. Als er een paar jaar later er dan vulnerabilities ergens in een transitive dependency tree zitten, kan dat lastiger worden voor deze langlopende projecten. Daarom is het uitermate belangrijk om kritisch te kijken naar all dependencies in een langlopend project.

Inzicht in node_modules

Tot nu toe hebben we het veel gehad over node_modules en de problemen, maar nog niks over waarom het dan in een git repository zou moeten worden gezet.

Het is geen doel op zich om het in een git repository te zetten, maar op basis van mijn eigen ervaring is het wel de makkelijkste manier om inzicht te verkrijgen. Vaak is het probleem wel bekend bij developers, maar het moment dat er een git add node_modules/ wordt gedaan zien developers pas echt hoe groot de schade is.

Dit heb ik zelf meegemaakt in enkele grote projecten, voornamelijk ook monorepositories waar er veel code bij elkaar op 1 plek staat. Hierdoor is de kans op opeenhoping groot. (Zelf ben ik een groot voorstander van monorepositories voor het overzicht, maar moet men zich wel bewust zijn van dit soort nadelen)

Zodra er een overzicht van de node_modules/ is, worden vaak direct verbeteringen gespot. Zaken als "we hebben 3 verschillende patch versions van dezelfde dependency" of "waarom trekt deze 1 package die we maar voor 1 regel code gebruiken er 500 extra mee". Hierbij zijn commando's als du -sh node_modules/* | sort -h en npm list <package> cruciaal. Het eerste commando geeft inzicht in welke folders de grootste zijn, de tweede de reden dat die package in de tree zit. Vaak is het zo dat het grootste package niet een direct dependency is, maar ergens via-via binnen sluipt.

Eenmalig versus continue audits

Deze schoonmaakacties zijn nodig, maar zijn vaak ook eenmalig. Misschien denk je bij het lezen van deze post wel "Hm, laat ik dat eens even checken" en kan je mooie winst behalen. Dat is uiteraard mooi (heel fijn zelfs!), maar in de toekomst is de kans dan groot dat het toch weer verslechtert en je terug bij af bent.

Hierom is continue monitoring vereist. In alle pogingen die ik in mijn loopbaan heb gezien om deze monitoring tot stand te laten komen is er maar 1 die ik op de langetermijn succesvol heb zien verlopen: in je git zetten. Git maakt het namelijk direct inzichtelijk, op het moment dat het verergert. Het is regelmatig voorgekomen dat er een dependency versie werd opgehoogd, maar dat er onverwacht een grote hoeveelheid duplicatie tot stand kwam. Dit omdat package management algoritmes niet simpel zijn en alle ins-en-outs weten hoe tot een optimale dependency tree te komen erg lastig is.

De waarde zit dus in het onverwachte: dat een package bump (of toevoeging) een onverwacht grote impact heeft. Door dit direct inzichtelijk te maken kan er ook direct ingegrepen worden. Het is dan oke om te zeggen "hm, laten we deze upgrade doen nadat we onze dependencies hebben opgeruimd". (Uiteraard met uitzondering van urgente bumps vanwege vulnerabilities.)

Chrome DevTools en zijn node_modules

Zelf heb ik aan Chrome DevTools gewerkt, een van de grootste JavaScript applicaties in de wereld. Naast dat het een van de grootste is, is het ook een langlopend project. Het bestaat sinds 2008 en bevat nog steeds code geschreven in die tijd. Hoewel het uit de private sector komt, heeft het wel met de problematiek te maken waar langdurige en waardevolle projecten tegen aan lopen.

Let wel: de versie van DevTools vandaag verschilt op grote lijnen weinig met de architectuur van de versie uit 2008. Er zijn uiteraard veel verbeteringen en iteraties op het product gedaan, maar is er nooit een DevTools 2.0 geschreven. Dat komt grotendeels omdat Chromium dat ook nooit heeft gehad. Grote delen van de browser zijn stuk voor stuk herschreven, maar er heeft nooit een complete rewrite plaatsgevonden.

De node_modules van DevTools staan in git: devtools-frontend/node_modules. Ik raad ook zeker aan om de history van deze folder te bekijken en hoe er mee om wordt gegaan. Toen ik aan het product werkte deed ik ongeveer een keer per maand een dependency bump van packages en nog steeds is dat (grotendeels) de gang van zaken.

Het toevoegen van de node_modules gaf ons direct inzicht in problematische packages en ik heb toen zelf met de open source community samengewerkt om packages kleiner te maken.

Het verkleinen van ESLint

Op basis van analyse van DevTools opende ik een issue op de ESLint repository: Reduce disk churn for ESLint upgrades. Zoals je kan lezen is dit een uitgebreide analyse van dependencies geworden, ook al waren de initiele reacties terughoudend. Dit had wel degelijk impact op andere afnemers, naast DevTools. NodeJS checkt ook zelf node_modules in git en bij de ESLint upgrade werd deze significant kleiner: Removing Lodash from ESLint removed 44k lines in NodeJS.

Er was ook langdurige impact op de architectuur van ESLint. In een nieuwe major version besloten de ESLint maintainers om verscheidene formatters in aparte NPM packages te stoppen: Remove all formatters except stylish, html, json, and json-with-meta. Deze formatters trokken namelijk onbewust veel dependencies met zich mee, waardoor ESLint een "knoop" in de dependency tree werd.

Dit is een voorbeeld van de "continue opschoning" waar ik het eerder over had. We hebben ervoor gezorgd dat het niet bij een eenmalige schoonmaak bleef, maar een continue proces die zelfs tot architecturele verbeteringen leidde.

"Mijn node_modules zijn te groot om in git te zetten"

Hoewel inzicht belangrijk is, moet het wel doenlijk zijn. Als ik deze gedachtegang uitleg aan anderen, dan is de eerste reactie "interessant idee, maar dat gaat niet werken voor die van ons, want die is al zo groot". In de praktijk klopt dat vaak, ze zijn ook erg groot en dat kan ik niet ontkennen. Het is daarom ook een gewetensvraag: hoe groot moet het dan zijn voordat je het wel aandurft.

Vaak is het starten met analyseren het belangrijkste: run du -sh node_modules/* | sort -h plus npm list <package> en bekijk de huidige status. Deze stappen geven de eerste concrete resultaten, waardoor je beter weet hoe je verder kan doorpakken. Als je dit vaker aanpakt, dan zal je op een gegeven moment concluderen dat het doenlijk is. Dit kan als het enkele honderden Megabytes is, git vindt dat geen probleem.

Conclusie

Zorg er dan voor dat je frequent (ik raad maandelijks aan), packages up-to-date houdt. Voor langdurige projecten is het nieuwste van het nieuwste namelijk niet altijd nodig en in mijn ervaring is maandelijks een goede termijn om het bij te benen.

Zie je bij een van die maandelijkse acties onverwacht grote impact? Mooi, dan kan je gelijk ingrijpen.