La semana pasada, los desarrolladores de Yarn (el administrador de paquetes para Javascript) anunciaron una nueva característica : la instalación Plug'n'Play. Esta característica le permite ejecutar proyectos Node.js sin usar la carpeta node_modules, en la que las dependencias del proyecto generalmente se instalan antes de comenzar. La descripción de la característica declara que ya no se necesitarán módulos_nodo; los módulos se cargarán desde la memoria caché general del administrador de paquetes.
Al mismo tiempo, los desarrolladores de NPM también anunciaron su solución similar al problema.
Echemos un vistazo más de cerca a estas soluciones e intentemos probarlas en proyectos reales.
Historial de problemas
Inicialmente, el sistema modular NodeJS estaba completamente basado en el sistema de archivos. Cualquier llamada a require()
asigna al sistema de archivos. Para organizar módulos de terceros, se inventó la carpeta node_modules, en la que se deben descargar e instalar módulos y bibliotecas reutilizables. Por lo tanto, cada proyecto recibió su propio conjunto de dependencias, desperdiciando espacio en disco de manera ineficiente.
La instalación de dependencia ocupa la mayor parte del tiempo de construcción en los sistemas CI, por lo que acelerar este paso afectará favorablemente el tiempo de construcción en su conjunto.
Simplificado, la instalación de módulos consta de los siguientes pasos:
- La versión específica del módulo se calcula a partir del intervalo válido.
- Todos los módulos de las versiones requeridas se descargan del repositorio y se almacenan en la memoria caché local.
- Los módulos del caché local se copian en la carpeta del proyecto node_modules
Si los dos primeros pasos ya están lo suficientemente optimizados y se llevan a cabo rápidamente, cuando ya tiene módulos en caché, entonces el tercer paso permanece casi sin cambios en comparación con las primeras versiones de node y npm.
El nuevo enfoque propone deshacerse del tercer paso y reemplazar la copia real de los archivos con la creación de una tabla que asigne los módulos solicitados a sus copias en la memoria caché local.
Usando enlaces simbólicos
En lugar de copiar módulos en realidad, puede agregar un enlace simbólico a su ubicación en el caché. Este enfoque se implementa en PNPM , otro administrador de paquetes alternativo. El enfoque puede funcionar bien, pero con los enlaces simbólicos hay muchos problemas asociados con la ubicación dual del archivo, la búsqueda de módulos adyacentes, etc. Además, crear enlaces simbólicos es una operación de archivo que me gustaría evitar en una forma ideal de trabajo.
Probar hilo PNP
Puede leer más sobre esta característica en la descripción oficial . Este párrafo contiene su breve recuento.
La versión de Yarn habilitada para PNP ahora está en feature-branch yarn-pnp .
Clonamos el repositorio localmente con la rama deseada
git clone git@github.com:yarnpkg/yarn.git --branch yarn-pnp
La instrucción de ensamblaje de hilo está aquí , el conjunto de pasos es muy trivial.
Una vez completada la compilación, agregue un alias a la versión personalizada de hilo y comience a trabajar con ella:
alias yarn-local="node $PWD/lib/cli/index.js"
Plug'n'play se habilita de dos maneras: a través del indicador: yarn --pnp
, o mediante una configuración adicional en package.json
: "installConfig": {"pnp": true}
.
Como ejemplo, los desarrolladores de Yarn ya han preparado un proyecto de demostración . Tiene Webpack, Babel y otras herramientas típicas de un front-end moderno. Intentemos establecer sus dependencias de diferentes maneras y obtengamos los siguientes resultados:
- Instalación típica de
yarn
: años 19 - Instalación vía
yarn --pnp
: 3s
Antes de la medición, se realizó una instalación en frío para que todos los módulos necesarios ya estuvieran en el caché.
Veamos como funciona. Después de una instalación pnp, se crea un archivo .pnp.js
adicional en la raíz del proyecto que contiene una anulación de la lógica nativa en la clase Módulo integrada en Node.js. Al cargar este archivo en nuestro código, le damos a la función require()
la capacidad de obtener módulos del caché global y no mirar node_modules
. Todos los comandos de hilo integrados, como el yarn start
o la yarn test
, precargan este archivo de manera predeterminada, por lo que no tendrá que hacer ningún cambio en su código si ya usó Yarn antes.
Además de los módulos de mapeo, pnp.js realiza una validación de dependencia adicional. Si intenta llamar a require('test')
, sin una dependencia declarada en package.json
, obtendrá el siguiente error: Error: You cannot require a package ("test") that is not declared in your dependencies
. Esta mejora debería aumentar la fiabilidad y la previsibilidad del código.
Entre las deficiencias del nuevo enfoque, vale la pena señalar que se requerirá una integración adicional para las herramientas que trabajaron directamente con el directorio node_modules sin los mecanismos de Nodo incorporados. Por ejemplo, Webpack y otros creadores front-end necesitarán complementos adicionales para que puedan encontrar los archivos necesarios para la agrupación.
En el proyecto de demostración hay bocetos de resolvers para Eslint, Jest, Rollup y Webpack.
En mi experimento, aún existían problemas con Typecript, que está muy relacionado con la presencia de node_modules y no hay una manera fácil de anular la estrategia de búsqueda del módulo.
También habrá problemas con los scripts posteriores a la instalación. Dado que el módulo permanece en el caché, los scripts posteriores a la instalación que cambian su estado (por ejemplo, cargar archivos adicionales) pueden dañar el caché y romper otros proyectos que dependen de él. Los desarrolladores de Yarn recomiendan deshabilitar la ejecución de scripts con el indicador --ignore-scripts
. Ya habían experimentado con activar esta bandera de forma predeterminada para todos los proyectos dentro de Facebook y no encontraron ningún problema serio. A la larga, abandonar los scripts posteriores a la instalación parece un buen paso en vista de los problemas de seguridad conocidos.
Intentando con Tink NPM
El equipo de NPM también anunció su solución alternativa. Su nueva herramienta, tink, viene con un módulo separado, independiente de NPM. Tink recibe el archivo package-lock.json
como package-lock.json
, que se genera automáticamente cuando se npm install
. Según el archivo de bloqueo, tink genera un archivo node_modules/.package-map.json
, que almacena la proyección de los módulos locales en su ubicación real en la memoria caché.
A diferencia de Yarn, no hay un archivo de gancho que se pueda cargar previamente en su proyecto para requerir el parche. En su lugar, se sugiere que utilice el comando tink
lugar de node
para obtener el entorno correcto. Este enfoque es menos ergonómico porque requerirá modificaciones en su código para que funcione. Sin embargo, como prueba de concepto servirá.
Traté de comparar la velocidad de instalación de los módulos con los npm ci
y tink
, pero tink fue aún más lento, por lo que no daré los resultados. Obviamente, este proyecto es mucho más crudo en comparación con Yarn y no está optimizado en absoluto. Bueno, esperaremos nuevos lanzamientos.
Conclusión
Rechazar el directorio node_modules es un paso lógico, dada la experiencia de otros idiomas, donde este enfoque no estaba allí inicialmente. Esto afectará favorablemente la velocidad de compilación con los sistemas CI, donde es posible guardar la caché del paquete entre compilaciones. Además, si transfiere la caché del paquete y el archivo .pnp.js
de una computadora a otra, puede reproducir el entorno sin siquiera iniciar Yarn. Esto puede ser útil en los sistemas de compilación de contenedores: monte el directorio con el caché, coloque el archivo .pnp.js
e inmediatamente puede ejecutar las pruebas.
El nuevo enfoque parece inusual y rompe algunas prácticas establecidas basadas en el hecho de que todos los módulos están siempre disponibles en node_modules. Pero el archivo .pnp.js
ofrece una API que le permite abstraerse de la posición real de los archivos y trabajar con el árbol virtual. Además, como último recurso, hay un comando de yarn unplug --persist
que extraerá un módulo del caché y lo colocará localmente en node_modules
.
En cualquier caso, aún no se ha finalizado nada, incluso la solicitud de extracción en Yarn aún no se ha vertido, deberíamos esperar cambios. Pero fue interesante para mí probar la versión alfa de la característica en la práctica y probarla en un par de mis proyectos personales y asegurarme de que este enfoque realmente funcione, haciendo que la instalación sea más rápida.
Referencias