pnpm por dentro: arquitetura, melhorias e por que usar
Uma análise técnica do store endereçado por conteúdo, node_modules estrito, workspaces, segurança da cadeia de suprimentos e migração para pnpm.
Trocar npm install por pnpm install parece uma otimização pequena. Não é.
O pnpm muda a forma como dependências são armazenadas, ligadas ao projeto e
validadas. O resultado costuma ser menos uso de disco, instalações repetidas
mais rápidas e uma classe inteira de erros descoberta antes do deploy.
A melhor razão para adotar pnpm não é velocidade isolada. É fazer o
node_modulesrepresentar com mais fidelidade o grafo declarado nopackage.json.
O problema que o pnpm ataca
Gerenciadores tradicionais tendem a produzir uma árvore achatada em
node_modules. O hoisting move pacotes transitivos para níveis superiores,
onde o código da aplicação também consegue importá-los.
Imagine que a aplicação importe kleur, mas somente vitest declare essa
dependência. O projeto pode funcionar hoje porque kleur foi elevado até a
raiz. Uma atualização do Vitest pode alterar a árvore, remover esse acesso
acidental e quebrar a aplicação sem que seu código tenha mudado.
Essa é uma dependência fantasma: o código usa um pacote que não declarou.
O pnpm reduz esse problema ao expor na raiz apenas as dependências diretas do
projeto. Se o import não está no package.json, a falha aparece cedo.
Como o armazenamento funciona
O pnpm separa a instalação em duas ideias: um store global endereçado por conteúdo e uma estrutura local que materializa o grafo do projeto.
1. Store endereçado por conteúdo
Os arquivos baixados são identificados pelo hash do próprio conteúdo e ficam em um store compartilhado pela máquina. Se dez projetos usam exatamente o mesmo arquivo, ele precisa ser armazenado uma vez.
Quando uma nova versão de um pacote muda apenas parte dos arquivos, somente o conteúdo novo precisa ser acrescentado ao store. Isso é mais granular do que manter uma cópia completa de cada pacote em cada projeto.
Você pode localizar e inspecionar esse store com:
pnpm store path
pnpm store status
pnpm store prune
prune deve ser usado como manutenção eventual, não depois de toda instalação:
remover versões ainda úteis elimina parte do ganho das instalações futuras.
2. Virtual store e links
Dentro do projeto, o diretório node_modules/.pnpm funciona como um virtual
store. Os arquivos dos pacotes são ligados ao store global com hard links. Em
seguida, links simbólicos montam as relações entre dependências.
Uma versão simplificada fica assim:
node_modules/
├── react -> .pnpm/react@19.0.0/node_modules/react
└── .pnpm/
├── react@19.0.0/
│ └── node_modules/react
└── scheduler@0.25.0/
└── node_modules/scheduler
A profundidade física permanece praticamente constante mesmo quando o grafo lógico cresce. Isso evita os caminhos gigantes de uma árvore ingenuamente aninhada e continua compatível com a resolução de módulos do Node.js, que considera o caminho real por trás do link simbólico.
Peer dependencies também entram na identidade do pacote dentro do virtual store. Duas instâncias da mesma biblioteca podem coexistir quando cada uma foi resolvida contra peers diferentes, sem fingir que representam o mesmo ambiente.
Por que instalações repetidas ficam rápidas
O processo é dividido em resolução, busca e ligação. Essas etapas podem avançar com sobreposição, e pacotes já presentes no store não precisam ser baixados de novo. Na prática, o maior benefício aparece em máquinas com vários projetos, worktrees e branches que compartilham boa parte do grafo.
Isso não significa que o pnpm vencerá todo benchmark. Uma instalação fria ainda depende de rede, latência do registry, antivírus, sistema de arquivos e scripts nativos. Em CI efêmero, restaurar um cache enorme também pode custar mais do que baixar os pacotes. A comparação correta usa o repositório e a infraestrutura da equipe, medindo instalação fria, instalação com store aquecido e tamanho do cache.
As melhorias que vão além da performance
Workspaces como recurso central
O suporte a monorepos é nativo. Um pnpm-workspace.yaml define os projetos e um
único lockfile pode representar todo o workspace:
packages:
- "apps/*"
- "packages/*"
O protocolo workspace: garante que uma dependência interna seja resolvida
localmente. Se a versão esperada não existir no repositório, a instalação falha
em vez de buscar silenciosamente um pacote homônimo no registry.
{
"dependencies": {
"@acme/design-system": "workspace:^"
}
}
Filtros permitem executar uma tarefa em uma parte do grafo. Os três pontos incluem também as dependências do pacote selecionado:
pnpm --filter @acme/web... build
pnpm --filter "...[origin/main]" test
Isso não substitui ferramentas de cache de tarefas como Turborepo ou Nx, mas oferece a base correta para elas: seleção de pacotes, ordem topológica e dependências locais explícitas.
Catálogos de versões
Em um monorepo, repetir a versão de React, TypeScript ou ESLint em dezenas de arquivos aumenta o custo de atualização. Os catálogos centralizam essas faixas:
catalog:
react: ^19.0.0
react-dom: ^19.0.0
typescript: ^5.7.0
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:"
}
}
Na publicação de um pacote, o protocolo catalog: é convertido para uma faixa
semver normal. O consumidor não precisa usar pnpm. Para equipes grandes, isso
reduz versões divergentes, alterações repetitivas e conflitos de merge.
Segurança por padrão
Scripts de instalação de dependências são uma superfície crítica: um
postinstall malicioso executa código durante o simples ato de instalar. Desde
a versão 10, o pnpm não executa automaticamente esses scripts em dependências.
Versões atuais permitem manter uma lista explícita de pacotes autorizados:
allowBuilds:
esbuild: true
core-js: false
Há outras políticas úteis no pnpm-workspace.yaml:
# Aguarda 24 horas antes de resolver uma versão recém-publicada.
minimumReleaseAge: 1440
# Impede dependências transitivas vindas de Git ou URLs arbitrárias.
blockExoticSubdeps: true
# Falha quando a evidência de confiança piora entre publicações.
trustPolicy: no-downgrade
Na versão 11, a espera de 24 horas já é o padrão. Ela reduz a janela de exposição entre a publicação de um pacote comprometido e sua detecção pelo ecossistema. Não elimina o risco: lockfile revisado, registry confiável, auditoria e menor número de dependências continuam necessários.
Lockfile e CI mais previsíveis
O pnpm-lock.yaml registra resoluções e integridade do grafo. Em ambiente de CI,
o pnpm ativa o comportamento de lockfile congelado automaticamente. Ainda assim,
vale deixar a intenção explícita no pipeline:
pnpm install --frozen-lockfile
pnpm test
pnpm build
Fixe também a versão do gerenciador no package.json. Use a versão exata adotada
pela equipe para evitar que uma mudança de major reescreva o lockfile:
{
"packageManager": "pnpm@11.22.0"
}
Docker com cache mais estável
pnpm fetch baixa o grafo usando o lockfile, sem depender dos manifests de cada
pacote. Isso cria uma camada Docker que só é invalidada quando as dependências
mudam:
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm fetch --prod
COPY . .
RUN pnpm install --offline --frozen-lockfile --prod
Em monorepos, essa abordagem evita copiar manualmente cada package.json antes
da instalação. Hard links não atravessam o limite entre host e container durante
o build; nesse cenário, cache mounts do BuildKit ou camadas baseadas no lockfile
são as otimizações relevantes.
O que melhorou nas versões recentes
O pnpm deixou de ser apenas o gerenciador "rápido e econômico". A evolução das
versões 10 e 11 adicionou controles de cadeia de suprimentos, catálogos,
aprovação granular de builds, políticas de confiança e configuração central no
pnpm-workspace.yaml.
No momento desta publicação, a versão 12 está em release candidate e reescreve o executável em Rust. A distribuição passa a ser um binário nativo e o uso do gerenciador permanece essencialmente igual. É uma direção técnica promissora, mas não é motivo para colocar um RC em produção sem validar compatibilidade. A arquitetura de store, links, lockfile e workspaces já entrega os benefícios na versão estável.
Limites e custos da adoção
O modelo é melhor, mas não gratuito:
- Ferramentas antigas podem assumir um
node_modulestotalmente achatado ou não lidar bem com symlinks.nodeLinker: hoistedexiste como escape, com perda de parte da rigidez. - A primeira migração costuma revelar dependências fantasmas. Isso é trabalho adicional, embora seja exatamente o tipo de problema que convém corrigir.
- Bibliotecas que precisam de
postinstall, como binários nativos, exigem aprovação explícita. A equipe precisa revisar a lista, não liberar tudo. - Em CI descartável, o store só ajuda entre jobs se houver uma estratégia de cache. O cache deve ser gravável apenas por jobs confiáveis.
- pnpm gerencia o workspace, mas não resolve sozinho versionamento e changelog de múltiplos pacotes. Changesets ou outra ferramenta continuam necessários.
Migração segura a partir do npm
Para um projeto existente, faça a troca em uma branch isolada:
# Instala o pnpm atual.
npx get-pnpm
# Converte o package-lock.json para pnpm-lock.yaml.
pnpm import
# Instala e valida o novo grafo.
pnpm install
pnpm test
pnpm build
Depois:
- Corrija imports de dependências não declaradas em vez de ativar hoisting global imediatamente.
- Revise avisos de peer dependencies e scripts de build bloqueados.
- Fixe a versão no campo
packageManager. - Remova o
package-lock.jsonsomente após revisar e versionar opnpm-lock.yaml. - Troque os comandos da CI por
pnpm install --frozen-lockfile. - Meça tempo e cache antes e depois; não dependa de benchmarks genéricos.
Quando vale a pena usar
Eu adotaria pnpm como padrão em projetos novos e, principalmente, quando houver:
- vários projetos Node.js na mesma máquina;
- monorepo com pacotes internos;
- CI ou builds Docker sensíveis a reinstalações;
- problemas recorrentes com dependências fantasmas;
- necessidade de controlar scripts de instalação e risco de supply chain.
Para um repositório pequeno, estável e sem gargalo de instalação, a migração não é urgente. npm e Yarn também evoluíram e podem ser escolhas válidas. Mas, quando o grafo cresce, o pnpm combina eficiência de armazenamento, correção estrutural e políticas operacionais em uma ferramenta única. É esse conjunto, e não apenas alguns segundos a menos no install, que justifica a adoção.
Referências
Leia também
mcp-inspecto: auditando servidores MCP de ponta a ponta
Uma ferramenta que inspeciona qualquer servidor MCP e entrega um relatório técnico detalhado de qualidade, compatibilidade, performance e segurança.
Expondo serviços com Docker e Cloudflare Tunnel
Como servir um container nginx na porta 80 usando um túnel Cloudflare, sem abrir portas no firewall.