Vælg din package manager før den vælger dig

Vælg din package manager før den vælger dig

Det korte svar er at du skal vælge én package manager, skrive den ned og holde dig stædigt til den. Men det længere svar er mere interessant.

Jeg landede selv i et projekt hvor nogle brugte npm, andre sværgede til yarn, og én havde læst på Twitter at pnpm var fremtiden. Resultatet var tre forskellige lockfiles, en node_modules-mappe på størrelse med min søns Minecraft-world og en CI der fejlede tilfældigt. Det var her jeg besluttede at få styr på, hvad forskellen egentlig er, og hvordan man vælger uden at fortryde om tre måneder.

Hvad der faktisk ændrer sig mellem npm, yarn og pnpm

Jeg starter altid med det samme lille eksperiment, når jeg tester værktøjer: opret et nyt projekt, installer et par pakker og se hvad der sker på disken.

mkdir test-pm
cd test-pm
npm init -y # eller yarn init -y / pnpm init -y
npm install react react-dom # tilsvarende for yarn/pnpm

Hvis du kører det med hver af de tre, får du tre ret forskellige verdener.

npm: standarden, som alle kender

Fordele:

  • Følger med Node, alle forstår det
  • Lockfile: package-lock.json er standard i rigtig mange projekter
  • Nyere npm-versioner har fornuftig performance og caching

Ulemper:

  • node_modules kan blive meget stor
  • NPM har haft lidt svingende adfærd mellem store versionshop

I praksis er npm fint til alt fra små hobbyprojekter til mellemstore apps. Det er også det tryggeste valg hvis du ikke gider forklare kolleger noget som helst.

Yarn: hurtigt barn af frustration over npm

Yarn kom som svar på gamle npm-problemer: langsomme installs og uforudsigelige dependency-træer.

Fordele:

  • Lockfile: yarn.lock er stabil og nem at committe
  • God performance, især i v1 (Classic)
  • Brugt af mange store open source projekter

Ulemper:

  • Der er forskel på Yarn v1 (Classic) og Yarn Berry (v2+)
  • Zero-install og PnP kan forvirre, hvis teamet ikke er med

Når du ser en yarn.lock i repoet, er det typisk fordi nogen på holdet tidligere har haft ondt i npm. Yarn er stadig solidt, men nyere npm har indhentet meget af forskellen.

pnpm: disk-sparer med strengere regler

pnpm ændrer én ting radikalt: hvordan node_modules er bygget.

  • Lockfile: pnpm-lock.yaml
  • Global content-adresseret cache (pakker gemmes én gang, linkes ud)
  • Strammere node_modules-struktur, så utilsigtet dependency hoisting afsløres

Det gør to ting:

  • Diskforbruget falder markant, især på monorepos
  • Skjulte dependency-fejl kommer frem, fordi pnpm ikke automatisk “hjælper” dig med for brede imports

Hvis du vil have en hurtig fornemmelse, så kig på pnpm’s egen forklaring. De viser meget visuelt, hvad der sker med links og cache.

Beslutningsmatrix – svar på fem spørgsmål og vælg

Jeg begyndte først at få ro i maven, da jeg skrev mit valg ned som en lille tjekliste. Den ser sådan her ud i dag.

1. Er I et team, eller sidder du alene?

  • Solo-projekt: vælg det du selv forstår bedst. Typisk npm eller pnpm.
  • Team: vælg det flest allerede kender, med mindre der er tung vægt imod.

Hvis halvdelen af teamet allerede har brugt pnpm og resten er grøntsager på package manager-fronten, så vælg pnpm. Omvendt: hvis alle tænker npm install helt automatisk, så lad være med at kæmpe imod bare for sport.

2. Har du (eller får du) et monorepo?

Monorepo: flere pakker / apps i samme repository. Fx apps/web, apps/api, packages/ui.

  • Ingen monorepo-planer: npm er fint, Yarn er fint, pnpm er fint.
  • Monorepo nu eller senere: pnpm eller Yarn Workspaces vinder typisk.

Pnpm har en naturlig monorepo-historie. Yarn Workspaces er også stærkt. Npm har workspaces nu, men tooling og vaner halter lidt efter.

3. Hvor følsom er du over for disk og install-tid?

  • Mange projekter på samme maskine: pnpm sparer dig for mange GB.
  • Lille setup, få projekter: forskellen er til at leve med, vælg efter simpelt workflow.

På min egen laptop kunne jeg se forskel, da jeg gik fra blandet npm/yarn til primært pnpm. Færre kaffepauser mens node_modules pakkes ud.

4. Hvad bruger jeres CI/CD lige nu?

  • CI kører npm: behold npm, med mindre du får lov at rydde op i pipelines.
  • CI er allerede sat op til Yarn: behold Yarn, med mindre du har en god migreringsplan.
  • Ny pipeline: så er pnpm et stærkt bud, især med cache.

At skifte package manager uden at kigge på CI er den sikre vej til “det virker lokalt”-helvede. Det er også her mange lockfile-fejl starter.

5. Har du constraints udefra? (hosting, skabeloner, frameworks)

  • Nogle frameworks forventer npm (f.eks. simple create-*-skabeloner)
  • Andre har pnpm eller Yarn som anbefalet default

Jeg plejer at holde mig til frameworkets recommendation, med mindre jeg har en meget konkret grund til noget andet. Det gør debugging nemmere, når du googler fejl og ser samme værktøj i eksemplerne.

Opsummeret valg

Hvis jeg starter i dag, og jeg må vælge frit:

  • Lille hobbyapp alene: npm (mindst friktion, standardværktøj)
  • Større projekt eller monorepo: pnpm
  • Eksisterende team med Yarn: behold Yarn, ryd op og standardiser

Og så skriver jeg det ned i README som en regel, ikke som en løs idé.

Lockfiles – hvad de låser, og hvorfor de skaber støj

Lockfiles er årsagen til en stor del af forvirringen, men også grunden til at dine builds ikke eksploderer hver tirsdag.

Hvad låser de?

Når du har en package.json med fx:

{
  "dependencies": {
    "react": "^18.2.0"
  }
}

så siger du: “giv mig en eller anden version af React der er kompatibel med 18.2.0″. Det er et interval.

Lockfilen siger: “brug præcis denne version, hentet fra præcis denne URL, med denne checksum”.

  • package-lock.json til npm
  • yarn.lock til Yarn
  • pnpm-lock.yaml til pnpm

Det gør at:

  • Din kollega får samme dependency-træ som dig
  • CI kan reproducere builds

Hvorfor de giver merge-konflikter

Forestil dig to grene:

  • Gren A: du tilføjer axios
  • Gren B: din kollega tilføjer zustand

Begge kører npm install (eller Yarn/pnpm) og committer lockfilen. Nu har I to forskellige lockfiles. Når de merges, vil Git ofte vise konflikter.

Det er irriterende, men ikke farligt. Den typiske løsning er:

  1. Behold én sides lockfile ved merge (eller løs konflikten mekanisk)
  2. Kør npm install (eller tilsvarende) igen
  3. Commit den opdaterede lockfile

Fejlen er ikke at lockfile ændrer sig. Fejlen er at nogen forsøger at redigere den i hånden. Lad værktøjet gøre arbejdet.

Typisk fejl: flere lockfiles i samme repo

Hvis du har både package-lock.json og yarn.lock i roden, er du allerede i problemer.

  • Npm ignorerer yarn.lock, Yarn ignorerer package-lock.json
  • CI kan bruge én af dem, din laptop bruger en anden
  • Du ender med “works on my machine” hver gang en dependency opdateres

Derfor: én package manager, én type lockfile. Slet resten.

Corepack – skriv din package manager ind i projektet

Node har fået et værktøj, som hjælper med at standardisere: Corepack.

Corepack gør to ting:

  • Installerer og wrapper npm, Yarn og pnpm
  • Læser packageManager-feltet i package.json

Sådan slår du Corepack til

# globalt på din maskine
corepack enable

Nu kan Corepack sørge for de rette versioner. Du fortæller projektet hvad du vil bruge:

{
  "name": "mit-projekt",
  "packageManager": "pnpm@9.1.0"
}

Når en ny udvikler kloner projektet og kører pnpm install, vil Corepack hente og bruge den rigtige pnpm-version.

Fordel i teams og CI

Det giver tre konkrete gevinster:

  • Mindst én kilde til sandhed for package manager-version
  • CI og udviklermaskiner kan matche uden ekstra opsætning
  • Mindre “hos mig er det npm 8, hos dig er det npm 10”-forvirring

Hvis du allerede arbejder med fx Node 18 eller 20, giver det mening at gøre Corepack til en del af din standard setup, fx beskrevet i din README sammen med andre ting som migrations-strategi.

Typiske fejl og hurtige fixes

Her er de fejl jeg selv støder på oftest, når package manageren ikke er aftalt, eller når nogen lige “tester” et andet værktøj lokalt.

1. “Cannot find module” efter skift

Symptom: Lokalt virker det, CI fejler, eller omvendt.

Årsag: node_modules er bygget med én package manager, men du installerer eller kører scripts med en anden.

Løsning:

rm -rf node_modules
rm package-lock.json yarn.lock pnpm-lock.yaml # behold kun den rigtige type
npm install # eller yarn install / pnpm install

Commit ny lockfile, og skriv i README hvilken package manager der er valgt.

2. Peer dependency warnings bliver til runtime-fejl

Symptom: Du får lange advarsler om peer dependencies, især når du bruger React, bundlere eller UI-biblioteker.

Årsag: Pakker forventer bestemte versioner af andre pakker. Forskellige package managers håndterer det en smule forskelligt.

Løsning: Installer de manglende dependencies eksplicit.

# eksempel
pnpm add react@18 react-dom@18 --save

Kig i warnings, og ryd systematisk op. Pnpm er god til at afsløre de steder, hvor du har været lidt løs i kanterne.

3. Checksum mismatch i CI

Symptom: CI fejler med noget ala “checksum mismatch” eller “integrity check failed”.

Typiske årsager:

  • Korrupt cache i CI
  • Lockfile committet, men ændret manuelt
  • Forskellig package manager-version mellem lokal og CI

Løsning:

  1. Ryd cache i CI (fx slet ~/.npm, pnpm store eller lignende mappen i CI-konfigurationen)
  2. Sørg for at CI bruger samme package manager-version, evt. via Corepack
  3. Kør en frisk install lokalt og commit opdateret lockfile

4. CI bruger npm, repoet bruger pnpm

Symptom: Lokalt kører du pnpm install, men i CI står der npm ci i workflow-filen.

Mønster: Projektet er startet med npm, senere er nogen skiftet til pnpm uden at ændre CI.

Løsning: Gør det samme i CI som du gør lokalt.

# eksempel med GitHub Actions
- name: Install dependencies
  run: pnpm install --frozen-lockfile

Og slet evt. package-lock.json, så der ikke er tvivl.

Migration uden at vælte alt: npm ↔ pnpm ↔ yarn

Jeg har efterhånden migreret et par projekter, både alene og i små teams. Grundreglen er: gør det så kedeligt som muligt.

Fra npm til pnpm

  1. Installer pnpm (gerne via Corepack)
corepack enable
corepack prepare pnpm@latest --activate
  1. Slet gamle artefakter
rm -rf node_modules package-lock.json
  1. Installer med pnpm
pnpm install
  1. Opdater scripts i package.json
{
  "scripts": {
    "dev": "pnpm run start", // eller hvad du bruger
    "build": "pnpm run build"
  },
  "packageManager": "pnpm@9.1.0"
}

Ofte kan du beholde scripts som "dev": "next dev", det er kun i dokumentation og README at du skriver pnpm i stedet for npm.

Fra Yarn til pnpm

Samme idé, bare med yarn.lock i stedet.

rm -rf node_modules yarn.lock
pnpm install

Opdater README, CI workflows og evt. skabeloner til at bruge pnpm. Tjek at monorepo-opsætning (workspaces) matcher pnpm’s forventninger.

Fra pnpm tilbage til npm

Nogle gange er det omvendt: du sidder i et projekt hvor pnpm skaber forvirring, fordi resten af organisationen kun kender npm.

rm -rf node_modules pnpm-lock.yaml
npm install

Npm vil generere en package-lock.json, som du committer. Husk at fjerne packageManager-feltet eller sætte det til npm i package.json:

{
  "packageManager": "npm@10.8.0"
}

Og så igen: opdater CI og dokumentation.

Mine standardvalg til et nyt projekt

Når jeg starter et nyt Node/JS-projekt i dag, gør jeg typisk noget i den her stil.

1. Init med npm, vælg package manager bagefter

mkdir ny-app
cd ny-app
npm init -y

Herfra beslutter jeg mig:

  • Hvis det er lille solo-projekt: jeg bliver på npm
  • Hvis det kan vokse, eller jeg vil genbruge kode: jeg skifter til pnpm med det samme

2. Sæt engines og packageManager

I package.json skriver jeg noget a la:

{
  "engines": {
    "node": ">=20.0.0"
  },
  "packageManager": "pnpm@9.1.0"
}

På den måde får nye udviklere en fejl, hvis de bruger meget gammel Node, og Corepack kan hente den rigtige version af pnpm.

3. Scripts der ikke låser sig til én CLI

Jeg prøver at holde scripts neutrale:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "lint": "eslint ."
  }
}

Så kører jeg dem med valgt package manager:

pnpm dev
# eller
npm run dev

Hvis jeg en dag skulle skifte værktøj, er der mindre at rette.

Det samme princip bruger jeg på andre områder, fx feature flags, som jeg skrev om i artiklen om at shippe mindre ad gangen. Jo færre hårde afhængigheder i kernen, jo nemmere at skifte uden drama.

Hvornår du ikke skal skifte package manager

Alt det her kan hurtigt lyde som en opfordring til at skifte alt til pnpm i morgen. Det synes jeg faktisk ikke.

Behold det der ikke skaber problemer

Hvis du har et projekt der:

  • Bruger npm
  • Har én lockfile
  • Kører stabilt i CI
  • Ikke sluger hele disken

så vil jeg oftest lade det være. Din energi er bedre brugt på at få styr på tests, logging eller f.eks. mere stabil JavaScript-CI.

Skift først, når du har ét tydeligt problem

Jeg skifter kun package manager i eksisterende projekter hvis mindst én af de her er sand:

  • Diskforbrug og install-tid er reelt et problem
  • Monorepo-struktur er bøvlet med nuværende værktøj
  • Teamet er allerede forvirret over blandede værktøjer og lockfiles

Og så laver jeg det som en dedikeret ændring, ikke som en sideeffekt af “jeg skulle lige opdatere en dependency”.

Det handler i virkeligheden om det samme som med database-migrationer og andre “usynlige” systemvalg: jo mere bevidst du tager beslutningen, jo mindre tid spilder du senere på mærkelige fejl, som ingen helt kan forklare.

Om et par år tror jeg vi vil se tilbage på de her værktøjsvalg lidt som valg af brætspil: reglerne er forskellige, men det vigtigste er at alle ved hvilket spil I spiller, og at ingen pludselig begynder at spille noget andet midt i runden.

Pin manager-versionen i projektet og dokumenter det tydeligt i README. Brug Corepack (til Yarn/PNPM) eller packageManager-feltet i package.json, tilføj en preinstall-check (fx tjek npm_config_user_agent) der afbryder ved forkert manager, og konfigurer CI til kun at køre den valgte install-kommando med frozen-lockfile så build fejler hvis noget ændrer sig.
Slet node_modules og alle gamle lockfiles, tilføj/konfigurer den nye manager (fx packageManager i package.json eller kør pnpm init), kør en frisk install for at generere nyt lockfile, kør tests og CI, og commit kun det nye lockfile. Opdater CI-scripts og dokumentationen, og informer teamet om ændringen.
Cache package managerens lokale cache/store og brug lockfilens checksum i cache-nøglen så cachen invalideres ved dependency-ændringer. Eksempel: cache ~/.npm for npm, ~/.cache/yarn for Yarn og pnpm-store-placeringen for pnpm, og geninstaller med --frozen-lockfile eller tilsvarende for at få stabile builds.

Jonas Kirkeby har skrevet kode siden han som teenager forsøgte at lave en helt simpel hjemmeside til sin fars lille vvs-firma – og endte med at sidde oppe hele natten for at få en knap til at skifte farve. Siden da har han lært sig det meste ved at prøve sig frem, kopiere andres eksempler, ødelægge dem og langsomt forstå, hvorfor tingene virker, som de gør.

Til daglig arbejder han slet ikke med IT, men bruger aftener og morgener på små projekter: en lille side til en forening, et simpelt værktøj til at holde styr på familiens madplan eller et Python-script, der rydder op i rodede filer. Det er den slags konkrete hverdags-behov, der har formet hans måde at tænke kodning på – hvad kan jeg bygge nu, som faktisk hjælper mig eller nogen, jeg kender?

På Coding Class deler Jonas de guides, han selv ville ønske, han havde haft: korte, konkrete forløb, hvor du kan se noget på skærmen efter få minutters læsning. Han viser hele vejen fra idé til færdig løsning, inklusive de typiske fejl og små snubletråde på vejen, så du ikke kun får den pæne, polerede version.

Hans mål er, at du som begynder eller let øvet hurtigt får følelsen af: “Det her kan jeg faktisk selv finde ud af” – uanset om du vil bygge din første lille hjemmeside, forstå JavaScript-funktioner eller bruge Python til at automatisere en kedelig opgave.

Send kommentar

You May Have Missed