Me acuerdo una vez, estaba en un proyecto de cliente y, por un descuido, terminamos subiendo a producción una cantidad de `devDependencies` que no tenían nada que ver. El bundle pesaba el doble y el tiempo de despliegue se alargó. Aunque funcionaba, me quemó ver esa ineficiencia. Ahí me di cuenta de lo crítico que es entender bien qué va en cada lado del `package.json`.
La documentación a veces es muy técnica y no te da el contexto del porqué, y eso es lo que realmente importa para un developer.
Dependencies: lo que tu código necesita para vivir
Estas son las dependencias que tu aplicación o paquete necesita para funcionar en producción. Si tu proyecto es una app web, aquí van frameworks como React, librerías de UI, o lo que sea esencial para que el usuario final pueda usar tu software. Si es una librería, son las otras librerías que tu código llama directamente.
- Se instalan siempre: Cuando haces `npm install` en tu proyecto, y también si alguien instala tu paquete desde npm (ej. `npm install mi-paquete`).
- Son transitivas: Si tu paquete A depende de B, y B depende de C, entonces C se instala automáticamente. Sin C, B no funciona, y por ende A tampoco. Es una cadena de necesidades.
DevDependencies: para tu trabajo, no para el producto final
Aquí es donde ponemos todo lo que necesitas para desarrollar, testear o construir tu proyecto, pero que no son parte del código que corre en producción. Piensa en herramientas de testing (Jest, Vitest), linters (ESLint), transpiladores (TypeScript, Babel), o empaquetadores (Webpack, Vite).
- Se instalan en desarrollo: Cuando haces `npm install` en tu proyecto local.
- No se instalan en producción: Ojo que esto es clave. Si instalas tu paquete con el flag `--production`, o si la variable de entorno `NODE_ENV` está en `production`, estas dependencias no se instalan. Esto ayuda a mantener tus despliegues ligeros y rápidos.
npm install --production
En mi caso, el problema del que te hablé al inicio fue justo por no tener esto claro. Las `devDependencies` son para tu entorno de trabajo, no para el cliente.
PeerDependencies: cuando esperas que otro tenga la dependencia
Esta es la que más confusión genera y, de hecho, a mí me costó entenderla bien. Las `peerDependencies` se usan cuando tu paquete (por ejemplo, un plugin de React o una extensión de alguna librería) espera que el proyecto que lo usa ya tenga instalada una dependencia específica, en una versión compatible.
No son algo que tu paquete instale directamente. Más bien, es una advertencia o una expectativa. Imagina que construyes un plugin para React. No querrías que tu plugin instale su propia versión de React, porque el proyecto principal ya lo tiene. Eso causaría dos versiones de React corriendo, lo que es un desastre.
- No se instalan por tu paquete: NPM no las instala automáticamente (antes de npm 7 esto era diferente y causaba errores si faltaban, generando mucha frustración).
- Advertencias o instalación automática (npm 7+): Con npm 7 y versiones posteriores, si una `peerDependency` falta o hay un conflicto irresoluble, NPM intentará instalarla automáticamente o al menos te dará una advertencia clara para que lo manejes tú. Antes, te tiraba un error y tenías que arreglarlo a mano.
- Uso típico: Librerías de componentes UI, plugins de frameworks, herramientas que extienden otras librerías.
La clave es que `peerDependencies` comunican a los usuarios de tu paquete: "Hey, para que esto funcione bien, tú necesitas tener esta dependencia instalada".
Entender la evolución de `peerDependencies` en NPM y cómo su comportamiento cambió entre versiones me costó tiempo y varios bugs a las 2 AM. Ojalá hubiera sabido desde el principio que su propósito principal es evitar duplicidades y conflictos en el árbol de dependencias, y que su gestión es más una coordinación que una instalación directa.