Yarn: Un nuevo gestor de paquetes para JavaScript – Facebook Engineering

En la comunidad de JavaScript, los ingenieros compartimos cientos de miles de piezas de código para evitar reescribir componentes básicos, bibliotecas o frameworks propios. Cada pieza de código puede depender a su vez de otras piezas de código, y estas dependencias son gestionadas por gestores de paquetes. El gestor de paquetes de JavaScript más popular es el cliente npm, que proporciona acceso a más de 300.000 paquetes en el registro npm. Más de 5 millones de ingenieros utilizan el registro npm, que ve hasta 5 mil millones de descargas cada mes.

Hemos utilizado el cliente npm con éxito en Facebook durante años, pero a medida que el tamaño de nuestra base de código y el número de ingenieros creció, nos encontramos con problemas de consistencia, seguridad y rendimiento. Después de intentar resolver cada problema a medida que surgía, nos propusimos construir una nueva solución que nos ayudara a gestionar nuestras dependencias de forma más fiable. El producto de ese trabajo se llama Yarn – un cliente npm alternativo rápido, fiable y seguro.

Nos complace anunciar el lanzamiento de código abierto de Yarn, una colaboración con Exponent, Google y Tilde. Con Yarn, los ingenieros siguen teniendo acceso al registro de npm, pero pueden instalar paquetes más rápidamente y gestionar las dependencias de forma consistente entre máquinas o en entornos seguros sin conexión. Yarn permite a los ingenieros moverse más rápido y con confianza cuando utilizan código compartido para que puedan centrarse en lo que importa: construir nuevos productos y características.

La evolución de la gestión de paquetes de JavaScript en Facebook

En los días anteriores a los gestores de paquetes, era habitual que los ingenieros de JavaScript confiaran en un pequeño número de dependencias almacenadas directamente en sus proyectos o servidas por una CDN. El primer gestor de paquetes de JavaScript importante, npm, se construyó poco después de que se introdujera Node.js, y rápidamente se convirtió en uno de los gestores de paquetes más populares del mundo. Se crearon miles de nuevos proyectos de código abierto y los ingenieros compartieron más código que nunca.

Muchos de nuestros proyectos en Facebook, como React, dependen del código del registro npm. Sin embargo, a medida que escalamos internamente, nos enfrentamos a problemas con la consistencia al instalar las dependencias a través de diferentes máquinas y usuarios, la cantidad de tiempo que tomó para tirar de las dependencias, y tenía algunas preocupaciones de seguridad con la forma en que el cliente npm ejecuta el código de algunas de esas dependencias automáticamente. Intentamos construir soluciones en torno a estos problemas, pero a menudo plantearon nuevos problemas.

Intentos de escalar el cliente npm

Inicialmente, siguiendo las mejores prácticas prescritas, sólo comprobamos en package.json y pedimos a los ingenieros que ejecutaran manualmente npm install. Esto funcionó lo suficientemente bien para los ingenieros, pero se rompió en nuestros entornos de integración continua, que necesitan ser sandbox y estar aislados de Internet por razones de seguridad y fiabilidad.

La siguiente solución que implementamos fue comprobar todo node_modules en el repositorio. Aunque esto funcionaba, dificultaba bastante algunas operaciones sencillas. Por ejemplo, la actualización de una versión menor de babel generaba un commit de 800.000 líneas que era difícil de aterrizar y desencadenaba reglas de lint para secuencias de bytes utf8 no válidas, finales de línea de windows, imágenes no aplastadas por png, y más. Fusionar los cambios en node_modules a menudo llevaba a los ingenieros un día entero. Nuestro equipo de control de fuentes también señaló que nuestra carpeta node_modules registrada era responsable de una enorme cantidad de metadatos. El package.json de React Native lista actualmente solo 68 dependencias, pero después de ejecutar npm install el directorio node_modules contiene 121.358 archivos.

Hicimos un último intento de escalar el cliente npm para que funcionara con el número de ingenieros de Facebook y la cantidad de código que necesitamos instalar. Decidimos comprimir toda la carpeta node_modules y subirla a una CDN interna para que tanto los ingenieros como nuestros sistemas de integración continua pudieran descargar y extraer los archivos de forma consistente. Esto nos permitió eliminar cientos de miles de archivos del control de fuentes, pero hizo que los ingenieros necesitaran acceso a Internet no solo para extraer el nuevo código, sino también para construirlo.

También tuvimos que solucionar problemas con la función shrinkwrap de npm, que utilizábamos para bloquear las versiones de las dependencias. Los archivos shrinkwrap no se generan por defecto y se desincronizan si los ingenieros se olvidan de generarlos, así que escribimos una herramienta para verificar que el contenido del archivo shrinkwrap coincide con lo que hay en node_modules. Sin embargo, estos archivos son enormes bloques JSON con claves sin clasificar, por lo que los cambios en ellos generarían confirmaciones masivas y difíciles de revisar. Para mitigar esto, tuvimos que añadir un script adicional para ordenar todas las entradas.

Por último, la actualización de una sola dependencia con npm también actualiza muchas otras no relacionadas en base a las reglas de versionado semántico. Esto hace que cada cambio sea mucho más grande de lo previsto, y tener que hacer cosas como confirmar node_modules o subirlo a un CDN hizo que el proceso fuera menos ideal para los ingenieros.

Construyendo un nuevo cliente

En lugar de seguir construyendo la infraestructura alrededor del cliente npm, decidimos tratar de ver el problema de manera más holística. ¿Qué pasaría si, en lugar de eso, intentáramos construir un nuevo cliente que abordara los problemas centrales que estábamos experimentando? Sebastian McKenzie, de nuestra oficina de Londres, empezó a trabajar en esta idea y rápidamente nos entusiasmamos con su potencial.

Mientras trabajábamos en esto, empezamos a hablar con ingenieros de todo el sector y descubrimos que se enfrentaban a una serie de problemas similares y que habían intentado muchas de las mismas soluciones, a menudo centradas en resolver un solo problema a la vez. Era obvio que si colaborábamos en el conjunto de problemas a los que se enfrentaba la comunidad, podríamos desarrollar una solución que funcionara para todos. Con la ayuda de los ingenieros de Exponent, Google y Tilde, creamos el cliente Yarn y probamos y validamos su rendimiento en todos los principales marcos JS y en casos de uso adicionales fuera de Facebook. Hoy, estamos encantados de compartirlo con la comunidad.

Presentación de Yarn

Yarn es un nuevo gestor de paquetes que reemplaza el flujo de trabajo existente para el cliente npm u otros gestores de paquetes, al tiempo que sigue siendo compatible con el registro npm. Tiene el mismo conjunto de características que los flujos de trabajo existentes a la vez que opera de forma más rápida, segura y fiable.

La función principal de cualquier gestor de paquetes es instalar algún paquete -una pieza de código que sirve para un propósito concreto- desde un registro global al entorno local de un ingeniero. Cada paquete puede o no depender de otros paquetes. Un proyecto típico podría tener decenas, cientos o incluso miles de paquetes dentro de su árbol de dependencias.

Estas dependencias son versionadas e instaladas en base al versionado semántico (semver). Semver define un esquema de versionado que refleja los tipos de cambios en cada nueva versión, ya sea que un cambio rompa una API, agregue una nueva característica o corrija un error. Sin embargo, semver depende de que los desarrolladores de paquetes no cometan errores – los cambios que rompen o los nuevos errores pueden encontrar su camino en las dependencias instaladas si las dependencias no están bloqueadas.

Arquitectura

En el ecosistema Node, las dependencias se colocan dentro de un directorio node_modules en su proyecto. Sin embargo, esta estructura de archivos puede diferir del árbol de dependencias real, ya que las dependencias duplicadas se fusionan. El cliente npm instala las dependencias en el directorio node_modules de forma no determinista. Esto significa que según el orden en que se instalan las dependencias, la estructura de un directorio node_modules podría ser diferente de una persona a otra. Estas diferencias pueden causar bugs de «funciona en mi máquina» que toman mucho tiempo en ser descubiertos.

Yarn resuelve estos problemas en torno a la versión y el no-determinismo mediante el uso de lockfiles y un algoritmo de instalación que es determinista y confiable. Estos lockfiles bloquean las dependencias instaladas a una versión específica, y aseguran que cada instalación resulta en la misma estructura de archivos en node_modules en todas las máquinas. El lockfile escrito utiliza un formato conciso con claves ordenadas para asegurar que los cambios sean mínimos y la revisión sea sencilla.

El proceso de instalación se divide en tres pasos:

  1. Resolución: Yarn comienza a resolver las dependencias haciendo peticiones al registro y buscando recursivamente cada dependencia.
  2. Fetching: A continuación, Yarn busca en un directorio de caché global para ver si el paquete necesario ya ha sido descargado. Si no lo ha hecho, Yarn obtiene el tarball del paquete y lo coloca en la caché global para poder trabajar sin conexión y no tener que descargar las dependencias más de una vez. Las dependencias también pueden ser colocadas en el control de fuentes como tarballs para instalaciones completas sin conexión.
  3. Enlace: Por último, Yarn enlaza todo copiando todos los archivos necesarios de la caché global al directorio local node_modules.
  4. Al desglosar estos pasos de forma limpia y tener resultados deterministas, Yarn es capaz de paralelizar las operaciones, lo que maximiza la utilización de recursos y hace que el proceso de instalación sea más rápido. En algunos proyectos de Facebook, Yarn redujo el proceso de instalación en un orden de magnitud, de varios minutos a sólo segundos. Yarn también utiliza un mutex para asegurar que múltiples instancias de CLI en ejecución no colisionen y se contaminen entre sí.

    A lo largo de todo este proceso, Yarn impone estrictas garantías en torno a la instalación de paquetes. Usted tiene el control sobre qué scripts del ciclo de vida se ejecutan para qué paquetes. Las sumas de comprobación de los paquetes también se almacenan en el archivo de bloqueo para garantizar que se obtiene el mismo paquete cada vez.

    Características

    Además de hacer que las instalaciones sean mucho más rápidas y fiables, Yarn tiene características adicionales para simplificar aún más el flujo de trabajo de gestión de dependencias.

  • Compatibilidad con los flujos de trabajo de npm y bower y soporta la mezcla de registros.
  • Capacidad para restringir las licencias de los módulos instalados y un medio para la salida de la información de la licencia.
  • Expone una API JS pública estable con el registro abstraído para el consumo a través de herramientas de construcción.
  • Salida CLI legible, mínima y bonita.

Yarn en producción

En Facebook ya estamos usando Yarn en producción, y ha estado trabajando muy bien para nosotros. Impulsa la gestión de dependencias y paquetes para muchos de nuestros proyectos de JavaScript. Con cada migración hemos permitido a los ingenieros construir fuera de línea y hemos ayudado a acelerar su flujo de trabajo. You can see how install times for Yarn and npm compare on React Native under different conditions, which you can find here.

Getting started

The easiest way to get started is to run:

npm install -g yarn

yarn

The yarn CLI replaces npm in your development workflow, either with a matching command or a new, similar command:

  • npm installyarn

    With no arguments, the yarn command will read your package.json, fetch packages from the npm registry, and populate your node_modules folder. It is equivalent to running npm install.

  • npm install --save <name>yarn add <name>

    We removed the «invisible dependency» behavior of npm install <name> and split the command. Running yarn add <name> is equivalent to running npm install --save <name>.

Future

Many of us came together to build Yarn to solve common problems, and we knew that we wanted Yarn to be a true community project that everyone can use. Yarn is now available on GitHub and we’re ready for the Node community to do what it does best: Use Yarn, share ideas, write documentation, support each other, and help build a great community to care for it. We believe that Yarn is already off to a great start, and it can be even better with your help.