Lockfiles lyver ikke, men de er heller ikke på din side
Du sidder i en PR-review og tænker: “Hvorfor i alverden er der 800 ændrede linjer i package-lock.json for én lille dependency?”
Du scroller. Git diff flimrer. CI fejler tilfældigt. Og alle siger “bare slet lockfilen og kør npm install igen”.
Lad os få styr på, hvad der faktisk sker, så din lockfile stopper med at føles som sort magi.
Hvad en lockfile låser (og hvad den ikke gør)
Først lige begreberne:
- package-lock.json: npm
- yarn.lock: Yarn (classic/berry har lidt forskellig struktur, men samme idé)
- pnpm-lock.yaml: pnpm
Alle tre gør i grove træk det samme: de låser dine konkrete versionsvalg for dependencies og deres dependencies (transitive deps).
Hvad lockfilen faktisk styrer
Lockfilen beskriver et konkret dependency-træ:
- Hvilke pakker du har installeret
- Præcise versionsnumre (selv hvis package.json siger ^1.2.0)
- Hvordan træet er “foldet” (hvem der bruger hvad)
Så når du installerer på en anden maskine, burde du få nøjagtig samme træ. Det kaldes ofte en deterministic install: samme input, samme output.
Hvad lockfilen ikke lover
Lockfilen beskytter dig ikke mod alt:
- Hvis en dependency udgiver den samme version igen med ændret indhold (skidt praksis, men det sker), kan du stadig blive ramt.
- Hvis du sletter lockfilen, er du tilbage til at stole på version-ranges i package.json.
- Hvis I blander flere package managers (npm og yarn på samme repo), taber du overblikket lynhurtigt.
Pointen: lockfilen er din snapshot-fil af dependency-verdenen. Men den er kun så stabil, som din praksis omkring den.
npm install vs npm ci – hvorfor det gør en forskel
Dette er et af de steder, hvor små ord giver store forskelle.
npm install: din dagligdags hammer
npm install gør flere ting på én gang:
- Læser package.json
- Bruger lockfilen som udgangspunkt, men kan godt opdatere den
- Kan tilføje nye pakker og skrive dem ind i både package.json og lockfilen
Den er fin til lokal udvikling. Men den kan ændre lockfilen, selv når du ikke lige havde tænkt dig det. Fx hvis npm beslutter sig for at rydde op i duplikerede pakker.
npm ci: din reproducerbare robot
npm ci er mere striks:
- Forventer at lockfilen allerede findes
- Ignorerer version-ranges i package.json og stoler kun på lockfilen
- Sletter node_modules og installerer kun det, der står i lockfilen
Det betyder: hvis lockfil og package.json ikke matcher, fejler den. Perfekt til CI. Dårligt egnet til at “lige prøve at installere noget”.
Yarn og pnpm har samme mønster
Yarn og pnpm har tilsvarende strikse kommandoer:
- Yarn classic:
yarn install --frozen-lockfile - Yarn berry:
yarn install --immutable - pnpm:
pnpm install --frozen-lockfile
Navnene er forskellige, men idéen er den samme: brug lockfilen som sandhed, rør den ikke, og fejle hvis noget ikke stemmer.
Hvorfor lockfile-merges går galt (og hvad “spøgelses-deps” er)
Lockfil-konflikter opstår næsten altid på grund af parallelle ændringer i dependency-træet.
Typisk scenarie
- Du laver en feature-branch, kører
npm install some-lib. Lockfil ændrer sig. - Din kollega laver en anden branch, installerer noget andet. Lockfil ændrer sig på en anden måde.
- I bliver begge merged ind i main. Git kan ikke automatisk flette den enorme JSON-diff fornuftigt.
Resultatet kan blive en lockfil, der teknisk set er valid JSON, men som ikke repræsenterer noget, nogen maskine nogensinde har installeret i ét hug.
“Spøgelses-deps”
Spøgelses-dependencies er pakker, der står i lockfilen, men som:
- ikke længere bruges af nogen
- eller findes i et mærkeligt miks af versioner, fordi to grene har kæmpet om samme underdependency
Du opdager dem tit sådan her:
- CI bygger fint på main, men en ny branch med rebase giver mystiske fejl
- Lokalt har du én version af en pakke, men CI har en anden
node_modulesindeholder pakker, du troede du havde fjernet for længst
Det føles random, men kommer ofte fra en lockfil, der er “hånd-flettet” forkert.
Regler for teams: én lockfile, én manager, én strategi
Hvis du vil slippe for 80 % af lockfile-kaos, kan du lave nogle få, ret kedelige regler. Kedelige er gode her.
1. Vælg én package manager pr. repo
Ikke både npm og yarn. Ikke “jeg bruger pnpm, fordi det er hurtigere”, mens resten af teamet bruger npm.
Regel:
- Brug enten npm + package-lock.json
- eller Yarn + yarn.lock
- eller pnpm + pnpm-lock.yaml
Og commit kun den lockfil, der hører til.
2. Lockfilen skal i git
Ja, du skal committe lockfilen. Også i libraries. Det gør det muligt at reproducere præcist det dependency-træ, du udviklede og testede med.
Uden lockfil får du “jeg installerede bare lige i går, og nu virker det ikke længere”-fejl.
3. Aftal, hvornår man må ændre lockfilen
Jeg plejer at bruge noget a la:
- Du ændrer lockfilen, når du
installer elleradder noget bevidst. - Du skal ikke committe lockfile-støj, der kommer fra “jeg prøvede bare noget lokalt”.
- Hvis din branch kun ændrer kode, og lockfilen er modified, så reset den.
Sådan undgår du tilfældige kosmetiske ændringer i hver anden PR.
4. CI bruger altid den strikse mode
På CI skal der kun være én sandhed: lockfilen. Nogle eksempler:
- npm:
npm ci - Yarn:
yarn install --frozen-lockfile - pnpm:
pnpm install --frozen-lockfile
Hvis nogen har glemt at committe lockfilen, skal buildet fejle. Hellere der end i produktion.
Sådan løser du en lockfile-merge konflikt sikkert
Lad os tage den situation, du nok allerede har stået i: Git skriger på konflikt i package-lock.json.
Trin 1: Afgør, om lockfil-ændringen er meningsfuld
Kig på din egen PR:
- Har du selv kørt
npm install something,npm uninstalleller tilsvarende? - Eller har du kun ændret kode?
Hvis svaret er “kun kode”, så er din lockfil-ændring støj. I så fald:
git checkout --ours package-lock.json
# eller --theirs, alt efter hvad du vil beholde
Og bagefter kører du en frisk install lokalt for at sikre, at alt stadig spiller.
Trin 2: Hvis begge branches har ændret dependencies
Her er det fristende at åbne lockfilen i en editor og prøve at flette i hånden. Lad være. Den fil er genereret af et program, så lad programmet vinde.
Jeg plejer at gøre noget i den her stil (npm-eksempel):
- Vælg én sides lockfil i konflikten (typisk main/theirs):
git checkout --theirs package-lock.json - Gem og markér konflikten som løst:
git add package-lock.json - Kør install igen for at få alle nye deps med i lockfilen:
npm install - Commit den nye lockfil.
Idéen er: du tager udgangspunkt i én konsistent version og lader package manageren regne resten ud.
Variant: hård reset og regen
Hvis lockfilen er blevet helt absurd, kan du også:
- Slette lockfil og node_modules:
rm package-lock.json
rm -rf node_modules
- Køre
npm installigen - Committe den friske lockfil
Det giver et stort diff, men du ved i det mindste, at træet svarer til noget, en maskine faktisk har bygget.
CI/CD: caching, clean installs og tidlig drift-detektion
Hvis du vil fange “drift” mellem udviklingsmiljø og CI tidligt, kan du stramme skruerne en smule.
Brug cache uden at ofre determinisme
Caching handler om at gemme node_modules eller npm/yarn/pnpm caches mellem builds for fart. Men du skal koble den til lockfilen.
Eksempel i GitHub Actions (npm):
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
Når lockfilen ændrer sig, ændrer nøglen sig også, og du får et frisk install.
Byg altid fra rent miljø
Du kan godt cache, men undgå at lave incremental installs oven på et halvgammelt node_modules.
Med npm er det netop npm ci, der løser det: den sletter node_modules først.
Med Yarn/pnpm kan du også slette node_modules eksplicit før install, hvis du vil være sikker.
Opdag drift mellem lokal og CI
En lille øvelse, når I har sat tingene op:
- Kør
npm ci(eller frozen/immutable) lokalt. - Commit alt, så working directory er rent.
- Kør samme kommando igen.
Hvis lockfilen ændrer sig på trin 3, er der noget, der ikke er deterministisk. Så skal du kigge i npm/yarn/pnpm docs og evt. i deres issue trackers. Du vil have, at samme kommando på samme repo ikke laver nye ændringer.
Når det først gælder, står du stærkere, hvis du også har styr på dine database-migrationer i teams. Det er samme type “reproducerbarhed”-problem, bare for data.
Monorepos og workspaces – ekstra sjov med lockfiles
Hvis du har et monorepo med flere pakker, kan lockfilen dække hele træet på én gang.
Én root-lockfil, mange pakker
Fx:
- Yarn workspaces med én yarn.lock i roden
- pnpm workspaces med én pnpm-lock.yaml
- npm workspaces med én package-lock.json
Det er fint og normal praksis. Men det betyder også, at en install i pakke A kan påvirke dependency-træet i pakke B.
Regler jeg selv bruger i monorepos
- Ingen lokale
node_modulesoverrides i de enkelte pakker. - Kun root-kommandoer til install:
npm installfra roden, ikke nede i pakkerne. - Lockfil-ændringer må gerne være store, men PR-beskrivelsen skal nævne, hvad der er installeret/ændret.
Og så hjælper det at have en dedikeret “dependency onderhouds-PR” en gang imellem, hvor man bevidst opdaterer ting. Lidt som at vande planter struktureret i stedet for at hælde vand, når de allerede hænger.
Mini-øvelser: forstå din egen lockfile bedre
Hvis du vil have lidt mere styr på din egen opsætning, kan du prøve nogle hurtige øvelser.
Øvelse 1: Hvad bruger I egentlig?
- Find din lockfil.
- Tjek, hvilken package manager repoet faktisk er bygget til. Kig efter:
- “packageManager” felt i package.json (npm/pnpm med version)
- Yarn-specifikke felter (fx
"yarnPath"i .yarnrc.yml)
Hvis du finder flere spor (både npm og yarn), er første opgave at rydde op og vælge én.
Øvelse 2: Simuler CI lokalt
Prøv at køre samme kommando som din CI gør, lokalt. Fx:
rm -rf node_modules
npm ci
Hvis det fejler, har du allerede fundet en forskel, før den rammer build-pipelinen.
Øvelse 3: Tjek for gamle “spøgelses-deps”
Nogle værktøjer kan hjælpe dig med at spotte ubrugte dependencies, fx depcheck i Node-land.
Du kan også bare:
- Fjerne en mistænkt dependency fra package.json
- Køre install
- Køre tests og build
Det er lidt brutalt, men effektivt. Det minder ret meget om at pille en hylde ned og se, hvad der faktisk stod på den.
Tjekliste: dependency-hygiejne på et kvarter
Hvis du vil lave en hurtig oprydning i dag, kan du bruge den her lille tjekliste.
1. Vælg én package manager
- Slet lockfiler, der ikke matcher den, I har valgt.
- Tilføj evt. en note i README om, hvad der er “den rigtige”.
2. Sørg for at lockfilen er committed
- Tjek
.gitignorefor at sikre, at lockfilen ikke bliver ignoreret. - Commit den aktuelle lockfil til main.
3. Sæt CI op til deterministisk install
- npm:
npm ci - Yarn:
yarn install --frozen-lockfile - pnpm:
pnpm install --frozen-lockfile
Hvis du er i tvivl om hvordan, så kig f.eks. på mønstrene i artiklen om JavaScript-CI der fejler på ting, der virker lokalt.
4. Aftal et lille sæt team-regler
- Hvornår må man ændre lockfilen?
- Hvem har ansvaret for dependency-opdateringer (alle, roterende, én person)?
- Hvordan håndterer I merge-konflikter (regen vs. vælge én side)?
5. Lav én “maintenance”-PR
- Kør en clean install (ci/frozen/immutable).
- Commit ændringer i lockfilen.
- Sørg for at tests og build kører igennem.
Det svarer lidt til at tømme skuffen med gamle opladere: det er irriterende én gang, men føles meget bedre bagefter.
Det vigtigste råd
Behandl lockfilen som en build-artifact du kontrollerer med regler, ikke som støj du ignorerer, så forsvinder 9 ud af 10 “mystiske” dependency-fejl af sig selv.









Send kommentar
Du skal være logget ind for at skrive en kommentar.