Hace dos años, escribí sobre una
técnica que ahora se llama comúnmente patrón de módulo / módulo. Su aplicación le permite escribir código JavaScript usando las capacidades de ES2015 +, y luego usar paquetes y transpiladores para crear dos versiones de la base de código. Uno de ellos contiene una sintaxis moderna (se carga usando una estructura como
<script type="module">
, y el segundo es la sintaxis ES5 (se carga usando
<script nomodule>
). El patrón módulo / nomodule permite enviar a navegadores que admiten módulos, mucho menos código que los navegadores que no admiten esta función, ahora este patrón es compatible con la mayoría de los marcos web y las herramientas de línea de comandos.

Anteriormente, incluso teniendo en cuenta la capacidad de enviar código JavaScript moderno a producción, y aunque la mayoría de los navegadores admitían módulos, recomendé recopilar el código en paquetes.
Por qué Principalmente porque tenía la sensación de que cargar módulos en el navegador era lento. Aunque los protocolos recientes, como HTTP / 2, soportaban teóricamente la carga eficiente de múltiples archivos, todos los estudios de rendimiento en ese momento concluyeron que usar
paquetes es aún más eficiente que usar módulos.
Pero debe admitirse que esos estudios fueron incompletos. Los casos de prueba que utilizaron los módulos que se estudiaron en ellos consistieron en archivos de código fuente no optimizados y no minimizados que se implementaron en producción. No hubo comparaciones del paquete optimizado con módulos con el script clásico optimizado.
Sin embargo, para ser honesto, no había una forma óptima de implementar los módulos en ese momento. Pero ahora, gracias a algunas mejoras modernas en las tecnologías de agrupación, es posible implementar código de producción en forma de módulos ES2015 utilizando comandos de importación estáticos y dinámicos, y al mismo tiempo recibir un mayor rendimiento del que se puede lograr utilizando las opciones disponibles, en las cuales Los módulos no se utilizan.
Cabe señalar que en el
sitio en el que se publica el material original, la primera parte de la traducción que publicamos hoy, los módulos se han utilizado en producción durante varios meses.
Ideas erróneas sobre los módulos
Muchas personas con las que hablé rechazan por completo los módulos, ni siquiera los consideran una de las opciones para aplicaciones de producción a gran escala. Muchos de ellos citan el mismo
estudio que ya he mencionado. Es decir, esa parte, que establece que los módulos no deben usarse en producción, a menos que se trate de "pequeñas aplicaciones web, que incluyen menos de 100 módulos que difieren en un árbol de dependencia relativamente" pequeño "(es decir, - uno cuya profundidad no supere los 5 niveles) ".
Si alguna vez buscó en el directorio
node_modules
de cualquiera de sus proyectos, entonces probablemente sepa que incluso una pequeña aplicación puede tener más de 100 módulos de dependencia. Quiero ofrecerle un vistazo a cuántos módulos están disponibles en algunos de los paquetes npm más populares.
Aquí es donde se basa la idea errónea principal con respecto a los módulos. Los programadores creen que cuando se trata de usar módulos en producción, solo tienen dos opciones. El primero es desplegar todo el código fuente en su forma existente (incluido el directorio
node_modules
). El segundo es no usar módulos en absoluto.
Sin embargo, si observa detenidamente las recomendaciones del estudio citado anteriormente, encontrará que no hay nada que decir que cargar módulos es más lento que cargar scripts normales. No dice que los módulos no deberían usarse en absoluto. Solo habla del hecho de que si alguien implementa cientos de archivos de módulos no infectados en producción, Chrome no podrá cargarlos tan rápido como un solo paquete minificado. Como resultado, el estudio aconseja continuar usando paquetes, compiladores y minificadores.
¿Pero sabes que? El hecho es que puede usar todo esto y usar módulos en producción.
De hecho, los módulos son un formato al que debemos esforzarnos por convertir, ya que los navegadores ya saben cómo cargar módulos (y los navegadores que no pueden hacer esto pueden cargar una versión de repuesto del código utilizando el mecanismo de nomódulo). Si observa el código que generan los paquetes más populares, encontrará muchos fragmentos de plantilla cuyo propósito es solo cargar dinámicamente otro código y administrar dependencias. Pero todo esto no será necesario si solo usamos los módulos y las expresiones
import
y
export
.
Afortunadamente, al menos uno de los paquetes modernos populares (
Rollup ) admite módulos en forma de
datos de
salida . Esto significa que puede procesar el código con un paquete e implementar módulos en la producción (sin usar fragmentos de plantilla para cargar el código). Y, dado que Rollup tiene una excelente implementación del algoritmo de sacudimiento de árboles (el mejor que he visto en los paquetes), crear programas en forma de módulos que usan Rollup le permite obtener un código que es más pequeño que el tamaño del mismo código obtenido al aplicar otros mecanismos disponibles hoy.
Cabe señalar que
planean agregar soporte para módulos en la próxima versión de Parcel. Webpack aún no admite módulos como formato de salida, pero
aquí está : discusiones que se centran en este tema.
Otro concepto erróneo con respecto a los módulos es que algunas personas creen que los módulos solo pueden usarse si el 100% de las dependencias del proyecto usan módulos. Desafortunadamente (creo que es un gran arrepentimiento), la mayoría de los paquetes npm todavía se están preparando para su publicación utilizando el formato CommonJS (¡algunos módulos, incluso aquellos escritos con las funciones de ES2015, se traducen al formato CommonJS antes de ser publicados en npm)!
Aquí, de nuevo, quiero señalar que Rollup tiene un complemento (
rollup-plugin-commonjs ) que toma el código fuente de entrada escrito usando CommonJS y lo convierte en código ES2015. Definitivamente, sería
mejor si el formato de dependencia utilizado desde el principio utiliza el formato del módulo ES2015. Pero si algunas dependencias no son tales, esto no le impide implementar proyectos utilizando módulos en producción.
En las siguientes partes de este artículo, le mostraré cómo recopilo proyectos en paquetes que usan módulos (incluido el uso de importaciones dinámicas y separación de código), voy a hablar sobre por qué estas soluciones suelen ser más productivas que los scripts clásicos, y mostraré cómo funcionan con navegadores que no admiten módulos.
Estrategia de construcción de código óptima
El código de construcción para la producción es siempre un intento de equilibrar los pros y los contras de varias soluciones. Por un lado, el desarrollador quiere que su código se cargue y ejecute lo más rápido posible. Por otro lado, no quiere descargar código que no será utilizado por los usuarios del proyecto.
Además, los desarrolladores necesitan confiar en que su código es el más adecuado para el almacenamiento en caché. El gran problema de la agrupación de código es que cualquier cambio en el código, incluso una línea modificada, conduce a la invalidación de la memoria caché de todo el paquete. Si implementa una aplicación que consta de miles de pequeños módulos (presentados exactamente en la forma en que están presentes en el código fuente), puede realizar cambios menores en el código y al mismo tiempo saber que la mayor parte del código de la aplicación se almacenará en caché . Pero, como ya dije, este enfoque de desarrollo probablemente puede significar que cargar el código en la primera visita al recurso puede llevar más tiempo que cuando se usan enfoques más tradicionales.
Como resultado, nos enfrentamos a una tarea difícil, que es encontrar el enfoque correcto para dividir los paquetes en partes. Necesitamos encontrar el equilibrio adecuado entre la velocidad de carga de materiales y su almacenamiento en caché a largo plazo.
La mayoría de los paquetes, por defecto, utilizan técnicas de división de código basadas en comandos de importación dinámica. Pero diría que dividir el código solo con un enfoque en la importación dinámica no permite dividirlo en fragmentos suficientemente pequeños. Esto es especialmente cierto para sitios con muchos usuarios recurrentes (es decir, en situaciones donde el almacenamiento en caché es importante).
Creo que el código debe dividirse en fragmentos tan pequeños como sea posible. Vale la pena reducir el tamaño de los fragmentos hasta que su número crezca tanto que afectará la velocidad de descarga del proyecto. Y aunque definitivamente recomiendo que todos hagan su propio análisis de la situación, si cree en las estimaciones aproximadas hechas en el estudio que mencioné, al cargar menos de 100 módulos, no hay una desaceleración notable en la carga. Un estudio separado sobre el
rendimiento de HTTP / 2 no reveló una desaceleración notable del proyecto al descargar menos de 50 archivos. Sin embargo, allí probamos solo opciones en las que el número de archivos era 1, 6, 50 y 1000. Como resultado, probablemente 100 archivos son el valor que puede navegar fácilmente sin temor a perder la velocidad de descarga.
Entonces, ¿cuál es la mejor manera de dividir el código de manera agresiva, pero no demasiado agresiva? Además de dividir el código en función de los comandos de importación dinámica, le aconsejaría que eche un vistazo más de cerca a la división del código en paquetes npm. Con este enfoque, lo que se importa al proyecto desde la carpeta
node_modules
cae en un fragmento separado del código terminado en función del nombre del paquete.
Paquete de separación
Dije anteriormente que algunas de las capacidades modernas de los agrupadores permiten organizar un esquema de alto rendimiento para implementar proyectos basados en módulos. Lo que estaba hablando está representado por dos nuevas características de Rollup. El primero es
la separación automática de código a través de comandos dinámicos de
import()
(agregado
en v1.0.0 ). La segunda opción es
la separación manual de código realizada por el programa basado en la opción
manualChunks
(agregado en
v1.11.0 ).
Gracias a estas dos características, ahora es muy fácil configurar el proceso de compilación, en el que el código se divide a nivel de paquete.
Aquí hay un ejemplo de configuración que usa la opción
manualChunks
, gracias a la cual cada módulo importado de
node_modules
cae en un fragmento de código separado cuyo nombre corresponde al nombre del paquete (técnicamente, el nombre del directorio del paquete en la carpeta
node_modules
):
export default { input: { main: 'src/main.mjs', }, output: { dir: 'build', format: 'esm', entryFileNames: '[name].[hash].mjs', }, manualChunks(id) { if (id.includes('node_modules')) {
La opción
manualChunk
acepta una función que acepta, como argumento único, la ruta al archivo del módulo. Esta función puede devolver un nombre de cadena. Lo que devuelve apuntará a un fragmento del ensamblaje al que se debe agregar el módulo actual. Si la función no devuelve nada, el módulo se agregará al fragmento predeterminado.
Considere una aplicación que importa los
cloneDeep()
,
debounce()
y
find()
del paquete
lodash-es
. Si aplica la configuración anterior al crear esta aplicación, cada uno de estos módulos (así como cada módulo
lodash
importado por estos módulos) se colocará en un único archivo de salida con un nombre como
npm.lodash-es.XXXX.mjs
(aquí
XXXX
es único hash de archivo de módulo en el fragmento
lodash-es
).
Al final del archivo, verá una expresión de exportación como la siguiente. Tenga en cuenta que esta expresión contiene solo comandos de exportación para módulos agregados al fragmento, y no todos los módulos
lodash
.
export {cloneDeep, debounce, find};
Luego, si el código en cualquiera de los otros fragmentos usa estos módulos
lodash
(quizás solo el método
debounce()
), en estos fragmentos, en su parte superior, habrá una expresión de importación que se verá así:
import {debounce} from './npm.lodash.XXXX.mjs';
Esperemos que este ejemplo aclare la cuestión de cómo funciona la separación manual de códigos en Rollup. Además, creo que los resultados de la separación de código utilizando las expresiones de
import
y
export
son mucho más fáciles de leer y comprender que el código de fragmentos, cuya formación utiliza mecanismos no estándar que solo se utilizan en un determinado paquete.
Por ejemplo, es muy difícil descubrir qué está pasando en el siguiente archivo. Este es el resultado de uno de mis proyectos anteriores que usaba webpack para dividir código. Casi todo en este código no es necesario en los navegadores que admiten módulos.
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["import1"],{ "tLzr": (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "import1", function() { return import1; }); var _dep_1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( "6xPP"); const import1 = "imported: " + _dep_1__WEBPACK_IMPORTED_MODULE_0__["dep1"]; }) }]);
¿Qué pasa si hay cientos de dependencias npm?
Como dije, creo que la separación a nivel de código a nivel de paquete generalmente permite al desarrollador ponerse en una buena posición cuando la separación de código es agresiva, pero no demasiado agresiva.
Por supuesto, si su aplicación importa módulos de cientos de paquetes npm diferentes, aún puede encontrarse en una situación en la que el navegador no puede cargarlos de manera efectiva.
Sin embargo, si realmente tiene muchas dependencias npm, no debe abandonar por completo esta estrategia por ahora. Recuerde que probablemente no descargará todas las dependencias de npm en cada página. Por lo tanto, es importante averiguar cuántas dependencias se cargan realmente.
Sin embargo, estoy seguro de que hay algunas aplicaciones reales que tienen tantas dependencias npm que estas dependencias simplemente no se pueden representar como fragmentos separados. Si su proyecto es solo eso, le recomendaría que busque una forma de agrupar paquetes donde el código en el que con alta probabilidad puede cambiar al mismo tiempo (como
react
y
react-dom
) ya que la
react-dom
caché de fragmentos con estos paquetes también se ejecutará al mismo tiempo Más adelante mostraré un ejemplo en el que todas las dependencias de React se agrupan en el mismo
fragmento .
Continuará ...
Estimados lectores! ¿Cómo aborda el problema de la separación de código en sus proyectos?
