pnpm: Por qué deberías migrar de npm hoy mismo

Si aún usas npm en tus proyectos, tus node_modules pesan el triple de lo que deberían y tu flujo de instalación es un 70% más lento de lo que podría ser. Y lo peor: npm te está dejando usar dependencias que ni siquiera declaraste, mientras el ecosistema sufre la peor ola de ataques a la cadena de suministro de su historia.

El dilema del package manager moderno

Durante más de una década, npm fue el estándar indiscutible en el ecosistema JavaScript. Viene con Node, tiene el registry más grande del mundo, y «funciona». Pero en 2026, los proyectos JavaScript han escalado en complejidad, y lo que antes era aceptable hoy es un lastre.

pnpm —»Performant npm»— nació para resolver los problemas estructurales que npm arrastra desde su versión 3: el árbol de dependencias aplanado, el desperdicio masivo de disco, la ausencia de aislamiento entre paquetes y, cada vez más crítico, la falta de defensas reales contra ataques a la cadena de suministro.

Si trabajas con monorepos, microfrontends o simplemente tienes más de un proyecto en tu máquina, pnpm no es una opción: es una necesidad.

El problema de npm que nadie quiere admitir

El árbol aplanado y sus consecuencias

Desde npm v3, el instalador aplana el árbol de dependencias. En teoría esto reduce duplicados; en la práctica genera problemas sutiles pero graves:

  • Dependencias fantasma: Tu código puede require('express') aunque no esté en tu package.json solo porque otra dependencia lo trajo. Cuando esa dependencia deja de usarlo o lo actualiza con breaking changes, tu código explota sin razón aparente.
  • Duplicación innecesaria: El algoritmo de flattening no es perfecto. Muchos paquetes terminan duplicados en distintos niveles del árbol.
  • Acceso no autorizado: Cualquier paquete en tu node_modules puede acceder a cualquier otro, incluso si no lo declaraste. Es un agujero de seguridad silencioso.

Cómo pnpm resuelve el problema

El store global con content-addressable

pnpm usa un enfoque radicalmente distinto: en lugar de copiar dependencias a cada proyecto, las almacena una sola vez en un store global en disco, identificadas por su contenido (hash). Cada proyecto accede a ellas mediante hard links y symlinks.

La estructura de node_modules con pnpm se ve así:

node_modules/
├── .pnpm/           ← Hard links al store global
│   ├── express@4.18.2/
│   │   ├── node_modules/
│   │   │   ├── debug -> ../../debug@2.6.9/node_modules/debug
│   │   │   └── express -> (hard link al store)
│   └── debug@2.6.9/
└── express -> .pnpm/express@4.18.2/node_modules/express

Las ventajas de esta arquitectura son profundas.

Ahorro de disco: 50-70% menos espacio

Si tienes 10 proyectos que usan React, con npm cada uno tiene su propia copia de React, sus 23 dependencias transitorias y todo el árbol duplicado. Con pnpm, React se almacena una vez. Siempre.

En equipos grandes o con múltiples proyectos, esto se traduce directamente en decenas de gigabytes recuperados.

Instalaciones 3-7x más rápidas

Dado que las dependencias ya existen en el store global, la mayoría de las instalaciones son simples operaciones de linkeo. Los benchmarks muestran que pnpm es consistentemente más rápido que npm y compite directamente con Yarn PnP:

Operación npm pnpm Mejora
install (cold cache) 45s 15s 3x
install (warm cache) 12s 2s 6x
install (con lockfile) 8s 1.2s 7x
add [package] 4s 0.8s 5x

Aislamiento estricto de dependencias

Con pnpm, tu código solo puede acceder a lo que declaraste explícitamente en package.json. Las dependencias transitorias viven dentro del árbol interno de .pnpm y no están accesibles desde tu código. Esto elimina de raíz el problema de las dependencias fantasma y hace que los errores sean predecibles y rastreables.

Monorepos: donde pnpm realmente brilla

Si trabajas con un monorepo —ya sea con Turborepo, Nx, Lerna o pnpm Workspaces—, este es el punto donde npm se queda corto.

pnpm soporta workspaces nativos de forma impecable:

# Un solo comando instala TODO el monorepo
pnpm install

# Ejecuta un script en todos los workspaces
pnpm -r run build

# Filtra workspaces específicos
pnpm --filter @myapp/web run dev

Los workspaces con pnpm:

  • No duplican dependencias compartidas entre paquetes del monorepo
  • Linkean automáticamente paquetes locales entre sí
  • Respetan el aislamiento entre workspaces (cada uno solo ve lo que declaró)

Seguridad: el aspecto más crítico en 2026

Si lo anterior no te convenció, esto debería hacerlo. En 2025-2026, la cadena de suministro de npm sufrió los ataques más devastadores de su historia, y la arquitectura de npm no tiene defensas estructurales contra ellos.

Shai-Hulud: el worm que infectó 500+ paquetes

En septiembre de 2025, Palo Alto Networks Unit 42 descubrió Shai-Hulud, un worm automatizado que comprometió cientos de paquetes npm mediante una campaña de phishing dirigida a mantenedores. Los atacantes suplantaban a npm solicitando «actualizar la autenticación multifactor» y, una vez dentro, inyectaban código malicioso que:

  • Escaneaba archivos .npmrc en busca de tokens de acceso
  • Extraía variables de entorno y credenciales de la nube (AWS, GCP, Azure)
  • Se propagaba automáticamente comprometiendo más paquetes

La gravedad fue tal que la CISA (Cybersecurity and Infrastructure Security Agency) emitió una alerta oficial. Empresas como Trend Micro, Splunk y Huntress documentaron el ataque en detalle. El desarrollador Josh Junon (qix), mantenedor de axios, fue uno de los blancos del phishing que derivó en la compromiso de 18 paquetes npm populares, incluyendo dependencias críticas usadas por miles de proyectos.

Mini Shai-Hulud y Shai-Hulud 2.0

El ataque no terminó ahí. Para noviembre de 2025 surgió Shai-Hulud 2.0, una variante que:

  • Infectó más de 25,000 repositorios de GitHub asociados a ~350 cuentas distintas
  • Ejecutaba el payload en la fase de pre-instalación, sin necesidad de interacción humana
  • Incluía un mecanismo de autodestrucción que podía borrar el directorio home del desarrollador
  • Exfiltraba credenciales a repositorios públicos de GitHub

En 2026, la campaña continuó con Mini Shai-Hulud, expandiéndose a otros ecosistemas (PyPI, Packagist) y usando hooks de pre-instalación y import-time para ejecutar un runtime de Bun empaquetado que robaba secretos de CI/CD.

Por qué npm es vulnerable por diseño

Estos ataques explotan vulnerabilities estructurales de npm:

  1. Ejecución de scripts post-instalación por defecto — cualquier paquete comprometido puede ejecutar código arbitrario en tu máquina
  2. Árbol plano de node_modules — un paquete infectado puede leer y modificar cualquier otro paquete en el mismo proyecto
  3. Sin retraso de publicación — las versiones maliciosas se instalan inmediatamente, antes de que la comunidad las detecte
  4. Dependencias desde orígenes exóticos — paquetes que dependen de URLs de git o tarballs son difíciles de auditar

Cómo pnpm te protege donde npm no puede

1. Aislamiento estructural

La arquitectura de pnpm con hard links y el directorio .pnpm significa que un paquete comprometido no puede acceder a otros paquetes fuera de su subárbol. En npm, cualquier dependencia puede require() cualquier otra. En pnpm, cada paquete solo ve lo que tiene declarado.

2. pnpm 11: defensas por defecto

La versión 11 de pnpm, lanzada en mayo de 2026, incorpora protecciones que npm simplemente no ofrece:

  • minimumReleaseAge: 1440 — Ninguna versión publicada hace menos de 24 horas se instalará. Esto cierra la ventana de los ataques que publican paquetes maliciosos y esperan que la gente los instale antes de ser detectados. El Shai-Hulud original se detectó en horas; con este retardo, la mayoría de las víctimas potenciales quedan protegidas automáticamente.
  • blockExoticSubdeps: true — Bloquea dependencias transitivas desde fuentes no estándar (repositorios git, tarballs URL). Los ataques de Shai-Hulud 2.0 usaban precisamente este mecanismo para introducir payloads.
  • Nuevo modelo allowBuilds — Reemplaza el antiguo dangerouslyAllowAllBuilds. Ahora puedes auditar y aprobar explícitamente qué dependencias tienen permiso para ejecutar scripts de instalación.
  • trustPolicy: no-downgrade — Impide instalar una versión si su nivel de confianza ha disminuido (por ejemplo, si perdió la firma de proveniencia). Evita que reemplaces una versión legítima por una comprometida.

3. Sin dependencias fantasma

Como pnpm no aplana el árbol, ningún paquete puede acceder a dependencias que no declaró. Esto hace que el vector de ataque de «dependencia fantasma infectada» simplemente no exista en pnpm. Un paquete malicioso en tu árbol no puede arrastrar a otros.

4. Hooks de ciclo de vida auditables

pnpm te permite controlar explícitamente qué scripts de ciclo de vida (preinstall, postinstall) se ejecutan y cuáles no. npm los ejecuta todos sin preguntar.

Comparativa de seguridad

Aspecto npm pnpm
Aislamiento entre paquetes ❌ Árbol plano, acceso total ✅ Cada paquete aislado en .pnpm
Retardo de publicación ❌ No existe ✅ 24h por defecto (configurable)
Control de scripts post-instalación ❌ Todos se ejecutan ✅ Allow list explícita
Bloqueo de fuentes exóticas ❌ No disponible blockExoticSubdeps
Política de confianza ❌ No existe trustPolicy con proveniencia
Dependencias fantasma ❌ Comunes y peligrosas ✅ Imposibles por diseño

Migrar es casi indoloro

Pasarse a pnpm no requiere reescribir nada. La migración se resume en:

# Instalar pnpm globalmente
npm install -g pnpm

# Importar package-lock.json a pnpm-lock.yaml
pnpm import

# Eliminar node_modules y reinstalar
rm -rf node_modules
pnpm install

Eso es todo. pnpm import analiza tu package-lock.json existente y genera el equivalente en pnpm-lock.yaml. Tus scripts de package.json funcionan igual, y los comandos más comunes (pnpm add, pnpm remove, pnpm update) son análogos a los de npm.

Posibles gotchas

  • Peer dependencies: pnpm es más estricto con peer dependencies que npm. Si migras un proyecto grande, podrías encontrar algunos errores de resolución. Son fáciles de arreglar (solo debes declarar lo que realmente usas). Esto es una feature, no un bug.
  • Plugins de Webpack/gulp: Muy raramente, herramientas que asumen la estructura plana de npm pueden romperse. La solución es usar node-linker=hoisted en .npmrc para emular el comportamiento de npm como parche temporal.

¿Cuándo NO usar pnpm?

Hay casos legítimos donde npm sigue siendo la opción pragmática:

  • Proyectos legacy enormes con scripts de instalación altamente personalizados que dependen del árbol aplanado.
  • Entornos muy restrictivos donde no puedes instalar binarios globales (aunque pnpm se puede usar via npx).
  • Equipos donde npm es un requisito corporativo —pasa, pero es raro.

Para el 99% de los proyectos nuevos y el 90% de los existentes, pnpm es superior.

Conclusión

pnpm no es una moda pasajera ni una alternativa de nicho. Es la evolución natural del package manager en JavaScript. Sus ventajas son medibles y tangibles: menos disco, instalaciones más rápidas, dependencias aisladas y un modelo de seguridad que npm simplemente no puede ofrecer porque está roto en su arquitectura.

Los ataques Shai-Hulud demostraron que la cadena de suministro de npm es vulnerable por diseño. pnpm es la respuesta a ese problema.

El costo de migrar es bajo. El costo de no hacerlo es seguir arrastrando un node_modules inflado, instalaciones lentas y, lo peor, exposición a ataques que la próxima vez podrían no perdonarte.

Migra hoy. Tus futuros pnpm install te lo agradecerán.

Add a comment

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Prev