O package-lock existe por uma razão, e não é a que parece
A explicação habitual é que o package-lock.json serve para fixar versões. Está certa e é a menos importante — as versões já estão no package.json, e quem quiser fixá-las pode simplesmente tirar o ^.
O que ele faz de facto são três coisas, e nenhuma delas se consegue de outra maneira.
1. Fixa a árvore inteira, não as suas dependências
O package.json deste site tem cerca de quarenta dependências. O package-lock.json tem milhares de entradas.
A diferença são as dependências das dependências — aquelas que ninguém escolheu e que ninguém lê. São a maioria esmagadora do que corre em produção, e é sobre elas que o package.json não diz absolutamente nada.
Sem o ficheiro de bloqueio, cada instalação resolve tudo outra vez. Duas máquinas, no mesmo dia, com o mesmo package.json, podem acabar com árvores diferentes — porque uma delas correu depois de um autor qualquer, três níveis abaixo, ter publicado uma correção.
2. Verifica que os bytes são os mesmos
Esta é a parte que quase ninguém conhece, e é a mais valiosa. Cada entrada traz um resumo criptográfico do pacote:
"node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AqRPM4QIhMKbd+i7ldYP1MdZ4S9nwE0jsNqe..."
}Na instalação, o npm descarrega o ficheiro, calcula o resumo e compara. Se não bater certo, falha.
É uma defesa concreta contra o cenário que mais preocupa quem pensa nisto: alguém conseguir substituir o conteúdo de uma versão já publicada, ou um intermediário na rede trocar o que é servido. A versão continua a chamar-se 7.6.0, mas os bytes já não são os mesmos — e a instalação recusa-se a continuar.
3. Guarda a forma, não só os números
O npm achata a árvore: sobe pacotes para o nível de cima sempre que pode, para não repetir. Duas resoluções com exatamente as mesmas versões podem produzir arrumações diferentes — e onde cada pacote fica muda quem resolve o quê.
O ficheiro de bloqueio também guarda isso.
4. E de graça, velocidade
Resolver restrições de versões é trabalho a sério. Com a árvore já escrita, o npm deixa de ter de a calcular e passa a descarregar o que está na lista. É metade da razão pela qual o npm ci é bastante mais rápido — a outra metade está no artigo sobre o npm ci.
5. Conflitos: nunca à mão
Dois ramos mexeram em dependências e o resultado são centenas de linhas em conflito. Resolver aquilo à mão dá sempre um ficheiro inválido.
git checkout --theirs package-lock.json
npm install
git add package-lock.jsonEscolhe-se um dos lados, deixa-se o npm regenerar a partir do package.json já resolvido, e comita-se o resultado. É o único caminho fiável.
6. Uma exceção que se costuma ignorar
Numa biblioteca publicada, o ficheiro de bloqueio não serve a quem a instala: quem depende dela resolve as versões com o ficheiro dele. Continua a valer a pena comitá-lo, mas por outra razão — para que a integração contínua da biblioteca seja reproduzível.
É uma confusão comum: publicar uma biblioteca com versões fixas no ficheiro de bloqueio não impõe nada a ninguém. Quem impõe é o package.json, e é por isso que os intervalos lá dentro merecem atenção — o que está no artigo sobre o ^.
O resumo
O ficheiro de bloqueio não existe para fixar o que se escolheu; existe para fixar o que não se escolheu — os milhares de pacotes que entraram por arrasto — e para garantir que o que chega é byte a byte o que chegou da primeira vez. Por isso se comita, e por isso não se edita à mão.