Contexto
Todo npm install (ou pnpm install) esconde uma decisão de arquitetura que a maioria dos desenvolvedores nunca para para pensar: onde, no disco, os pacotes baixados realmente vivem. npm, desde a reescrita da sua árvore de dependências há alguns anos, instala pacotes numa estrutura plana (hoisted): a maioria das dependências, diretas e transitivas, acaba num único nível dentro de node_modules, o que resolve conflitos de versão duplicada às custas de permitir que um pacote acesse outro que nunca declarou como dependência. pnpm ataca o mesmo problema por outro ângulo: mantém uma store global de conteúdo endereçável no disco, com um hash por versão de pacote, e monta o node_modules de cada projeto usando hardlinks e symlinks para essa store — o que economiza espaço quando o mesmo pacote é usado por vários projetos, e produz uma árvore não-plana onde só as dependências realmente declaradas ficam acessíveis.
A vantagem estrutural de npm é a disponibilidade: ele já vem embutido em toda instalação do Node.js, então não existe passo extra de setup. A vantagem estrutural do pnpm é o quanto ele escala melhor conforme o número de projetos ou o tamanho de um monorepo cresce, tanto em uso de disco quanto em tempo de instalação, porque pacotes já baixados uma vez para a store nunca precisam ser baixados nem copiados de novo.
Quando escolher pnpm
pnpm compensa quando o time mantém múltiplos projetos no mesmo disco, ou um monorepo com muitos pacotes internos, e sente o custo de disco e tempo que uma estrutura plana com cópias completas por projeto impõe. Workspaces e filtros são nativos:
pnpm install
pnpm --filter ./packages/api build
O --filter permite rodar comandos só nos pacotes afetados de um monorepo, sem depender de uma ferramenta de orquestração externa para isso. Do lado da correção, a estrutura não-plana da store tem um efeito colateral bem-vindo: um pacote não consegue importar silenciosamente outro pacote que nunca foi declarado como dependência direta, porque ele simplesmente não existe no node_modules local do projeto — um bug de dependência fantasma que a estrutura plana do npm permite passar despercebido até quebrar em outro ambiente. A contrapartida é que pnpm precisa ser instalado separadamente — via corepack, um script de instalação próprio ou o gerenciador de pacotes do sistema operacional — antes de qualquer pnpm install funcionar.
Quando escolher npm
npm compensa quando o time quer o caminho de menor atrito possível: baixar o Node.js já traz o npm junto, sem nenhuma decisão adicional de ferramenta a tomar:
npm install
npm run build --workspaces
Os workspaces nativos do npm cobrem o caso comum de monorepo — várias pastas de pacotes gerenciadas por um único lockfile na raiz — sem precisar instalar nada além do que já está disponível. Para projetos pequenos ou médios, sem múltiplos repositórios compartilhando as mesmas dependências no mesmo disco, o custo extra de espaço da estrutura plana raramente é perceptível no dia a dia, e a ausência de uma etapa de instalação separada é uma fonte a menos de atrito em onboarding de novos desenvolvedores ou em ambientes de CI que já vêm com Node.js pré-instalado.
Veredito
Num monorepo grande, ou numa máquina de desenvolvimento com dezenas de projetos JavaScript, o custo de disco e o tempo de instalação do modelo plano do npm aparecem rápido. E é nesse cenário que trocar de gerenciador de pacotes deixa de ser preferência de time e passa a ser uma decisão de infraestrutura. Um projeto único, com poucos colaboradores e sem outros repositórios compartilhando as mesmas dependências no disco, dificilmente sente essa diferença, já que os dois gerenciadores produzem uma árvore funcional na esmagadora maioria dos projetos. O que pesa mais, então, é em que ponto dessa curva de crescimento o seu ambiente de desenvolvimento está.