wandres.dev
PACKAGE MANAGERS · npm, yarn, pnpm

pnpm: store direccionable por contenido, hard links y symlinks

El rediseño del layout desde primeros principios: un único store global direccionable por contenido donde cada archivo vive una sola vez, hard links que dan a node_modules copias virtuales de coste cero, y un árbol de symlinks estricto y no plano que solo expone lo que declaraste. Así pnpm 11.5 reduce la duplicación en disco casi a cero y hace la dependencia fantasma imposible por construcción, no por convención.

⏱ 17 min

pnpm ataca las tres patologías de la lección anterior en su raíz, reconstruyendo el layout de instalación desde primeros principios en lugar de parchear el de npm. La receta tiene tres piezas encajadas: un único almacén global direccionable por contenido, enlaces duros (hard links) en vez de copias, y un árbol de enlaces simbólicos (symlinks) estricto que solo deja ver lo que declaraste. El resultado no es una mejora incremental sino un cambio de categoría: duplicación en disco casi nula y dependencias fantasma vueltas imposibles por la forma del árbol, no por disciplina del programador.

🎯 Al terminar esta lección sabrás
  • Entender el store global direccionable por contenido y su clave de hash.
  • Distinguir hard link de symlink y el papel de cada uno en el layout de pnpm.
  • Ver por qué el árbol simbólico estricto vuelve irrepresentable la dependencia fantasma.
  • Situar pnpm 11.5 y su almacén en el ecosistema de 2026.

El store direccionable por contenido

pnpm mantiene un único almacén global por máquina —en Linux, típicamente bajo ~/.local/share/pnpm/store— indexado por el hash del contenido de cada archivo. “Direccionable por contenido” (content-addressable) significa que la clave de un archivo es su hash: dos archivos idénticos, aunque vengan de paquetes distintos, comparten una sola entrada física. Cada versión de cada paquete que descargas en tu vida entra al store una vez y nunca más se vuelve a bajar ni a duplicar.

store/
  v10/
    files/
      00/1a2b...   # un archivo, direccionado por su hash sha512
      7f/9c3d...   # otro archivo unico

Frente al modelo de npm, donde cada proyecto es una isla con su copia completa, aquí hay un mar compartido: la unidad de deduplicación baja del paquete al archivo individual, y el ámbito sube del proyecto a la máquina entera.

Los archivos dentro del node_modules de un proyecto no son copias del store: son hard links a él. Un hard link es una segunda entrada de directorio que apunta al mismo bloque físico de datos en disco —el mismo inodo—, con un contador de referencias. No duplica bytes: solo añade un nombre. Diez proyectos que usan la misma versión de React comparten una única copia física; sus diez node_modules la enlazan sin ocupar disco extra.

proyecto-a/node_modules/.pnpm/react@19.1.0/node_modules/react/index.js  ┐
proyecto-b/node_modules/.pnpm/react@19.1.0/node_modules/react/index.js  ├─ mismo inodo
store/v10/files/3a/9f...                                                ┘

La deduplicación entre proyectos es automática y a coste cero: el ahorro crece con cada proyecto que comparte una versión, hasta volver casi irrelevante el tamaño de node_modules en disco.

💡
Los hard links no cruzan sistemas de archivos

Un hard link solo puede apuntar a datos del mismo volumen, así que el store y tu node_modules deben vivir en el mismo sistema de archivos. Si trabajas en varios discos —un SSD de proyectos y otro de trabajo, o contenedores con volúmenes montados—, configura un store-dir por volumen. Cuando pnpm no puede enlazar, cae a una copia o a un reflink, y ahí pierdes parte del ahorro. Es la única pieza de fontanería del modelo de store que conviene tener presente.

Aquí está la clave que cierra la fantasma. El node_modules de pnpm no es plano. Los archivos reales de cada paquete viven bajo node_modules/.pnpm/nombre@version/node_modules/nombre —el virtual store—, enlazados por hard link al store global. En el tope de node_modules pnpm coloca solo enlaces simbólicos a tus dependencias directas; las dependencias de cada paquete se enlazan, a su vez, junto a él dentro de .pnpm.

node_modules/
  express/                              -> symlink a .pnpm/express@5.1.0/node_modules/express
  .pnpm/
    express@5.1.0/node_modules/
      express/                          (hard links al store global)
      debug/                            -> symlink a .pnpm/debug@4.4.0/node_modules/debug
    debug@4.4.0/node_modules/
      debug/                            (hard links al store global)

La consecuencia es exacta y demoledora para la fantasma: debug solo es alcanzable dentro de la carpeta de express, porque solo ahí está enlazado. Tu código, en la raíz del proyecto, solo ve lo que hay en el tope de node_modules, y ahí únicamente están tus dependencias directas. Un import "debug" desde tu código no resuelve, porque debug no existe en ningún lugar que el algoritmo de Node recorra desde tu archivo. La fantasma no está desaconsejada: está ausente del espacio de búsqueda.

flowchart LR
store[store global direccionable por contenido] -->|hard link| vs[virtual store punto pnpm express 5.1.0]
vs -->|symlink de dep directa| top[node_modules express en el tope]
app[tu codigo] -->|import express resuelve| top
app -.->|import debug no resuelve| x[debug no visible en el tope]
style store fill:#89b4fa,color:#11111b
style top fill:#a6e3a1,color:#11111b
style x fill:#f38ba8,color:#11111b
🧮

Store por contenido

Un almacén global por máquina, indexado por hash. Cada archivo entra una vez; los idénticos comparten entrada.

🔗

Hard link

node_modules enlaza al store por inodo. Muchas rutas, una sola copia física. Dedup entre proyectos gratis.

↪️

Symlink estricto

En el tope, solo enlaces a tus deps directas. Las transitivas viven aparte, junto a quien las declara.

🛡️

Fantasma imposible

Lo no declarado no está en el camino de resolución de Node. No hay que prohibirlo: no existe donde buscarlo.

pnpm 11.5 en 2026

En 2026 pnpm es un proyecto maduro. La versión 11.5 mantiene el formato de store v10, cachea los side effects de compilación de paquetes nativos y ofrece dedupe para consolidar el lockfile. La instalación es rápida no por fuerza bruta, sino por aritmética: si el archivo ya está en el store, no se descarga; si ya está enlazado, no se copia. El trabajo más barato es el que no se hace.

El caso más delicado que este layout resuelve con elegancia es el de los peerDependencies. Un mismo paquete puede tener que resolverse contra versiones distintas de su peer según quién lo use —un plugin que convive con React 18 en una parte del árbol y con React 19 en otra—. pnpm lo modela creando entradas separadas en el virtual store, una por cada combinación de peers, de modo que cada consumidor ve exactamente la resolución que le corresponde sin colisiones ni izados ambiguos. Cuando dudes de por qué está un paquete o contra qué se resolvió, el CLI te lo cuenta:

pnpm why react     # quien depende de react y con que version
pnpm store status  # comprueba la integridad del store global
Hacer los estados inválidos irrepresentables

La elegancia de pnpm no está en ser más rápido —que lo es—, sino en cómo logra la corrección. npm y su árbol plano gestionan las patologías a posteriori: deduplican lo que pueden, ofrecen lockfiles, y dejan que un linter o tu vigilancia cacen las fantasmas que se cuelan. pnpm las elimina a priori, eligiendo estructuras de datos que no permiten el estado malo. El store direccionable por contenido hace que la duplicación de un archivo sea, por definición de la clave, imposible: dos contenidos iguales son la misma entrada. El árbol de symlinks estricto hace que la fantasma sea irrepresentable: no hay una convención que diga “no importes lo que no declaraste”, hay una topología en la que lo no declarado no aparece en ninguna carpeta que el resolvedor visite. Este es un principio de diseño con nombre propio —hacer que los estados inválidos sean irrepresentables— que verás una y otra vez en buena ingeniería: un sistema de tipos que impide construir un valor incoherente, una API que no expone el método peligroso, un esquema de base de datos donde la restricción vive en la tabla y no en la aplicación. La diferencia entre “el sistema te avisa cuando haces algo mal” y “el sistema no te deja ni expresar lo malo” es la diferencia entre defensa y arquitectura. pnpm es la segunda, y por eso resolvió por rediseño lo que npm solo podía mitigar por parche. Interiorizar este principio te vuelve mejor diseñador de cualquier cosa, no solo mejor usuario de pnpm.

⚔️ Verifica la estrictitud con tus manos
  1. Instala un proyecto con pnpm install y explora node_modules/.pnpm; localiza el virtual store de una dependencia y las transitivas enlazadas junto a ella.
  2. Elige una transitiva conocida (por ejemplo la que arrastra tu framework) e intenta importarla desde tu código sin declararla; confirma que no resuelve.
  3. Compara du -sh node_modules de este proyecto con el equivalente instalado con npm; razona de dónde viene la diferencia.
  4. Con pnpm store status o inspeccionando el store-dir, comprueba que dos proyectos que comparten una versión enlazan al mismo contenido.
  5. Explica en un párrafo por qué la fantasma es “irrepresentable” y no meramente “desaconsejada” en este layout.