yarn.lock
, me hicieron la misma pregunta varias veces. Sonaba así: “¿Por qué entonces dejar el soporte package-lock.json
? ¿Por qué no usar solo yarn.lock
?
La respuesta breve a esta pregunta es: “Porque no satisface completamente las necesidades de npm. Si confía únicamente en él, afectará la capacidad de npm para formar esquemas óptimos de instalación de paquetes y la capacidad de agregar nuevas funcionalidades al proyecto ". Una respuesta más detallada se presenta en este material.
yarn.lock
Estructura básica del archivo yarn.lock
Un archivo
yarn.lock
es una descripción de la correspondencia entre los calificadores de dependencia del paquete y los metadatos que describen la resolución de estas dependencias. Por ejemplo:
mkdirp@1.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.2.tgz#5ccd93437619ca7050b538573fc918327eba98fb"
integrity sha512-N2REVrJ/X/jGPfit2d7zea2J1pf7EAR5chIUcfHffAZ7gmlam5U65sAm76+o4ntQbSRdTjYf7qZz3chuHlwXEA==
En este pasaje se informa lo siguiente: "Cualquier dependencia de se
mkdirp@1.x
debe resolver exactamente a lo que se indica aquí". Si dependen varios paquetes mkdirp@1.x
, entonces todas estas dependencias se resolverán de la misma manera.
En npm 7, si existe un archivo en el proyecto
yarn.lock
, npm usará los metadatos contenidos en él. Los valores de campo resolved
le dirán a npm de dónde debe descargar los paquetes, y los valores de campo integrity
se usarán para verificar lo que se recibe para asegurarse de que coincida con lo que se esperaba recibir. Si se agregan o eliminan paquetes del proyecto, los contenidos se actualizan en consecuencia yarn.lock
.
Al mismo tiempo, npm, como antes, crea un archivo
package-lock.json
. Si este archivo está presente en el proyecto, se utilizará como fuente autorizada de información sobre la estructura (formulario) del árbol de dependencias.
La pregunta aquí es: "Si
yarn.lock
es lo suficientemente bueno para el administrador de paquetes de Yarn, ¿por qué npm no puede usar este archivo?"
Resultados deterministas de la instalación de dependencias
Se garantiza que los resultados de la instalación de paquetes con Yarn serán los mismos cuando se utiliza el mismo archivo
yarn.lock
y la misma versión de Yarn. El uso de diferentes versiones de Yarn puede hacer que los archivos de paquetes se ubiquen de manera diferente en el disco.
El archivo
yarn.lock
garantiza la resolución determinista de las dependencias. Por ejemplo, si está foo@1.x
permitido foo@1.2.3
, entonces, dado el uso del mismo archivo yarn.lock
, esto siempre sucederá en todas las versiones de Yarn. ¡Pero esto (al menos en sí mismo) no es equivalente a garantizar el determinismo de la estructura del árbol de dependencias!
Considere el siguiente gráfico de dependencia:
root -> (foo@1, bar@1)
foo -> (baz@1)
bar -> (baz@2)
Aquí hay un par de diagramas de árbol de dependencias, cada uno de los cuales puede considerarse correcto.
Árbol número 1:
root
+-- foo
+-- bar
| +-- baz@2
+-- baz@1
Árbol número 2:
+-- foo
| +-- baz@1
+-- bar
+-- baz@2
El archivo
yarn.lock
no puede decirnos qué árbol de dependencia usar. Si root
se ejecuta un comando en el paquete require(«baz»)
(que es incorrecto, ya que esta dependencia no se refleja en el árbol de dependencias), el archivo yarn.lock
no garantiza la ejecución correcta de esta operación. Esta es una forma de determinismo que un archivo puede dar package-lock.json
, pero no yarn.lock
.
En la práctica, por supuesto, desde Yarn, en el archivo
yarn.lock
, existe toda la información necesaria para seleccionar la versión adecuada de una dependencia, la elección es determinista siempre que todos usen la misma versión de Yarn. Esto significa que la elección de la versión siempre se realiza de la misma manera. El código no cambia hasta que alguien lo cambie. Cabe señalar que Yarn es lo suficientemente inteligente como para no verse afectado por las discrepancias con respecto al tiempo de carga del manifiesto del paquete al crear el árbol de dependencias. De lo contrario, el determinismo de los resultados no podría garantizarse.
Dado que esto está determinado por las características de los algoritmos de Yarn y no por las estructuras de datos disponibles en el disco (sin identificar el algoritmo que se utilizará), esta garantía de determinismo es básicamente más débil que la garantía que proporciona
package-lock.json
Una descripción completa de la estructura del árbol de dependencias almacenado en el disco.
En otras palabras, la forma exacta en que Yarn construye el árbol de dependencias se ve afectada por el archivo
yarn.lock
y la implementación de Yarn. Y en npm, solo el archivo afecta el aspecto del árbol de dependencias package-lock.json
. Esto package-lock.json
hace que sea más difícil romper accidentalmente la estructura del proyecto como se describe en diferentes versiones de npm. Y si se realizan cambios en el archivo (tal vez, por error o intencionalmente), estos cambios serán claramente visibles en el archivo al agregar su versión modificada al repositorio del proyecto, que utiliza el sistema de control de versiones.
Dependencias anidadas y deduplicación de dependencias
Además, hay toda una clase de situaciones que implican trabajar con dependencias anidadas y deduplicar dependencias, cuando el archivo
yarn.lock
no puede reflejar con precisión el resultado de la resolución de dependencia, que, en la práctica, será utilizada por npm. Además, esto es cierto incluso para aquellos casos en que npm utiliza yarn.lock
metadatos como fuente. Mientras que npm lo usa yarn.lock
como una fuente confiable de información, npm no considera que este archivo sea la fuente autorizada de información sobre restricciones de versión de dependencia.
En algunos casos, Yarn crea un árbol de dependencia con un nivel muy alto de duplicación de paquetes, y no lo necesitamos. Como resultado, resulta que seguir exactamente el algoritmo de Yarn en tales casos está lejos de ser ideal.
Considere el siguiente gráfico de dependencia:
root -> (x@1.x, y@1.x, z@1.x)
x@1.1.0 -> ()
x@1.2.0 -> ()
y@1.0.0 -> (x@1.1, z@2.x)
z@1.0.0 -> ()
z@2.0.0 -> (x@1.x)
El proyecto
root
depende de los 1.x
paquetes de versiones x
, y
y z
. El paquete y
depende de x@1.1
y sigue z@2.x
. El paquete de la z
versión 1 no tiene dependencias, pero el paquete de la versión 2 sí x@1.x
.
Según esta información, npm genera el siguiente árbol de dependencias:
root (x@1.x, y@1.x, z@1.x) <-- x@1.x
+-- x 1.2.0 <-- x@1.x 1.2.0
+-- y (x@1.1, z@2.x)
| +-- x 1.1.0 <-- x@1.x 1.1.0
| +-- z 2.0.0 (x@1.x) <-- x@1.x
+-- z 1.0.0
El paquete
z@2.0.0
depende de x@1.x
lo mismo se puede decir root
. El archivo se yarn.lock
asigna a x@1.x
c 1.2.0
. Sin embargo, la dependencia del paquete z
, donde también se indica x@1.x
, se resolverá x@1.1.0
.
Como resultado, a pesar de que la dependencia
x@1.x
se describe en yarn.lock
donde se indica que debe resolverse en la versión del paquete 1.2.0
, hay una segunda resolución x@1.x
en la versión del paquete 1.1.0
.
Si ejecuta npm con un indicador
--prefer-dedupe
, el sistema irá un paso más allá e instalará solo una instancia de la dependencia x
, lo que conducirá a la formación del siguiente árbol de dependencia:
root (x@1.x, y@1.x, z@1.x)
+-- x 1.1.0 <-- x@1.x 1.1.0
+-- y (x@1.1, z@2.x)
| +-- z 2.0.0 (x@1.x)
+-- z 1.0.0
Esto minimiza la duplicación de dependencias, el árbol de dependencias resultante se confirma en el archivo
package-lock.json
.
Como el archivo
yarn.lock
captura solo el orden en que se resuelven las dependencias, y no el árbol de paquetes resultante, Yarn generará el siguiente árbol de dependencias:
root (x@1.x, y@1.x, z@1.x) <-- x@1.x
+-- x 1.2.0 <-- x@1.x 1.2.0
+-- y (x@1.1, z@2.x)
| +-- x 1.1.0 <-- x@1.x 1.1.0
| +-- z 2.0.0 (x@1.x) <-- x@1.1.0 , ...
| +-- x 1.2.0 <-- Yarn , yarn.lock
+-- z 1.0.0
Un paquete
x
, cuando se usa Yarn, aparece tres veces en el árbol de dependencias. Cuando se usa npm sin configuraciones adicionales, 2 veces. Y cuando se usa el indicador --prefer-dedupe
, solo una vez (aunque el árbol de dependencias no es la versión más nueva ni la mejor versión del paquete).
Los tres árboles de dependencia resultantes pueden considerarse correctos en el sentido de que cada paquete recibirá aquellas versiones de dependencias que cumplan con los requisitos establecidos. Pero no queremos crear paquetes de árboles con demasiados duplicados. Piense en lo que sucedería si
x
se trata de un gran paquete que tiene muchas dependencias propias.
Como resultado, hay una única forma en que npm puede optimizar el árbol de paquetes al tiempo que admite la creación de árboles de dependencia deterministas y reproducibles. Este método consiste en usar un archivo de bloqueo, cuyo principio de formación y uso difiere en un nivel fundamental
yarn.lock
.
Arreglando los resultados de implementar las intenciones del usuario
Como ya se mencionó, en npm 7, el usuario puede usar el indicador
--prefer-dedupe
para aplicar el algoritmo de generación del árbol de dependencias, en el que se da prioridad a la deduplicación de dependencias, y no al deseo de instalar siempre las últimas versiones del paquete. El uso de un indicador suele --prefer-dedupe
ser ideal en situaciones donde la duplicación de paquetes necesita ser minimizada.
Si se usa esta bandera, el árbol resultante para el ejemplo anterior se verá así:
root (x@1.x, y@1.x, z@1.x) <-- x@1.x
+-- x 1.1.0 <-- x@1.x 1.1.0
+-- y (x@1.1, z@2.x)
| +-- z 2.0.0 (x@1.x) <-- x@1.x
+-- z 1.0.0
En este caso, npm ve que aunque
x@1.2.0
es la última versión del paquete que cumple con el requisito x@1.x
, puede elegir en su lugar x@1.1.0
. Seleccionar esta versión dará como resultado una menor duplicación de paquetes en el árbol de dependencias.
Si no arregló la estructura del árbol de dependencias en un archivo de bloqueo, entonces cada programador que trabaje en un proyecto en un equipo tendría que configurar su entorno de trabajo de la misma manera que otros miembros del equipo lo configuran. Solo esto le permitirá obtener el mismo resultado que los demás. Si la "implementación" del mecanismo de construcción del árbol de dependencias puede modificarse de esta manera, les da a los usuarios de npm una oportunidad seria de optimizar las dependencias para sus propias necesidades específicas. Pero, si los resultados de la creación del árbol dependen de la implementación del sistema, esto hace imposible crear árboles de dependencia deterministas. Esto es exactamente a lo que conduce el uso del archivo
yarn.lock
.
Aquí hay algunos ejemplos más de cómo los ajustes adicionales de npm pueden resultar en la creación de diferentes árboles de dependencia:
--legacy-peer-deps
, una bandera que obliga a npm a ignorar por completopeerDependencies
.--legacy-bundling
, una bandera que le dice a npm que ni siquiera debería intentar aplanar el árbol de dependencias.--global-style
, el indicador por el cual todas las dependencias transitivas se instalan como dependencias anidadas en las carpetas de dependencias de nivel superior.
Capturar y corregir los resultados de la resolución de dependencias y la expectativa de que se utilizará el mismo algoritmo para generar el árbol de dependencias no funciona en condiciones cuando les damos a los usuarios la capacidad de personalizar el mecanismo para construir el árbol de dependencias.
La fijación de la estructura del árbol de dependencias terminado nos permite poner a disposición de los usuarios tales oportunidades y, al mismo tiempo, no interrumpir el proceso de construcción de árboles de dependencias deterministas y reproducibles.
Rendimiento y completitud de datos
El archivo es
package-lock.json
útil no solo cuando necesita garantizar el determinismo y la reproducibilidad de los árboles de dependencia. Además, confiamos en este archivo para rastrear y almacenar los metadatos del paquete, ahorrando significativamente tiempo que de otro modo, usando solo package.json
, habría llevado a trabajar con el registro npm. Dado que las capacidades del archivo son yarn.lock
muy limitadas, no hay metadatos que necesitemos descargar constantemente.
En npm 7, el archivo
package-lock.json
contiene todo lo que npm necesita para construir completamente el árbol de dependencia del proyecto. En npm 6, estos datos no se almacenan de manera tan conveniente, por lo que cuando encontramos un viejo archivo de bloqueo, tenemos que cargar el sistema con trabajo adicional, pero esto se hace, para un proyecto, solo una vez.
Como resultado, incluso si en
yarn.lock
y la información sobre la estructura del árbol de dependencias ha sido registrada, tenemos que usar otro archivo para almacenar metadatos adicionales.
Oportunidades futuras
Lo que hemos estado hablando aquí puede cambiar drásticamente si tiene en cuenta los diversos enfoques nuevos para colocar dependencias en los discos. Estos son pnpm, hilo 2 / berry y PnP Yarn.
Nosotros, trabajando en npm 8, vamos a explorar un enfoque para construir árboles de dependencia basados en un sistema de archivos virtual. Esta idea se inspiró en Tink, y se confirmó que el concepto funcionaría en 2019. También estamos discutiendo la idea de pasar a algo como la estructura utilizada por pnpm, aunque esto es, en cierto sentido, un cambio aún más dramático que usar un sistema de archivos virtual.
Si todas las dependencias están en algún repositorio central, y las dependencias anidadas están representadas solo por enlaces simbólicos o un sistema de archivos virtual, modelar la estructura del árbol de dependencias no sería un problema tan importante para nosotros. Pero aún necesitamos más metadatos de los que puede proporcionar el archivo
yarn.lock
. Como resultado, actualizar y racionalizar el formato de archivo existente tiene más sentido package-lock.json
, en lugar de una transición completa a yarn.lock
.
Este no es un artículo que podría llamarse "Sobre los peligros de yarn.lock"
Me gustaría señalar que, por lo que sé, Yarn genera de manera confiable árboles de dependencia de proyecto correctos. Y, para una versión específica de Yarn (en el momento de la redacción, esto se aplica a todas las versiones nuevas de Yarn), estos árboles son, como con npm, completamente deterministas.
El archivo es
yarn.lock
suficiente para crear árboles de dependencia deterministas utilizando la misma versión de Yarn. Pero no podemos confiar en mecanismos que dependen de la implementación del administrador de paquetes dado el uso de mecanismos similares en muchas herramientas. Esto es aún más cierto cuando considera que la implementación del formato de archivoyarn.lock
no está documentado formalmente en ninguna parte. (Este no es un problema exclusivo de Yarn; npm es la misma situación. Documentar formatos de archivo es un trabajo bastante grande). La
mejor manera de garantizar la confiabilidad de construir árboles de dependencia altamente deterministas es, a la larga, registrar los resultados de la resolución de dependencia. No confíe en la creencia de que las implementaciones futuras del administrador de paquetes, al resolver dependencias, seguirán el mismo camino que las implementaciones anteriores. Este enfoque limita nuestra capacidad de construir árboles de dependencia optimizados.
Las desviaciones de la estructura inicialmente fija del árbol de dependencias deberían ser el resultado de un deseo claramente expresado por el usuario. Dichas desviaciones deberían documentarse a sí mismas, realizando cambios en los datos registrados previamente en la estructura del árbol de dependencias.
Solamente
package-lock.json
, o un mecanismo similar a este archivo puede dar a npm tales oportunidades.
¿Qué gestor de paquetes utiliza en sus proyectos de JavaScript?