Índice
- ¿Qué significa realmente sacudir los árboles?
- Módulos ES frente a CommonJS
- Alcance y efectos secundarios
- Optimización del paquete web
- Webpack versión 3 y siguientes
- Tiempos de elevación y compilación del alcance
- Evite la transpilación prematura
- Lista de verificación para sacudir árboles
- Autoría de paquetes
- Terminando
- Recursos útiles
La “sacudida de árboles” es una optimización del rendimiento imprescindible al agrupar JavaScript. En este artículo, profundizamos en cómo funciona exactamente y cómo las especificaciones y la práctica se entrelazan para hacer que los paquetes sean más ágiles y eficaces. Además, obtendrá una lista de verificación que hará temblar los árboles para usar en sus proyectos.
Antes de comenzar nuestro viaje para aprender qué es la sacudida de árboles y cómo prepararnos para tener éxito con ella, debemos comprender qué módulos hay en el ecosistema de JavaScript.
Desde sus inicios, los programas JavaScript han aumentado en complejidad y en la cantidad de tareas que realizan. Se hizo evidente la necesidad de compartimentar tales tareas en ámbitos cerrados de ejecución. Estos compartimentos de tareas, o valores, son lo que llamamos módulos . Su objetivo principal es evitar la repetición y aprovechar la reutilización. Por lo tanto, las arquitecturas se idearon para permitir tipos de alcance tan especiales, exponer sus valores y tareas y consumir valores y tareas externos.
Para profundizar en qué son los módulos y cómo funcionan, recomiendo " Módulos ES: una inmersión profunda en dibujos animados ". Pero para comprender los matices de la sacudida de árboles y el consumo de módulos, la definición anterior debería ser suficiente.
¿Qué significa realmente sacudir los árboles?
En pocas palabras, la sacudida de árboles significa eliminar el código inalcanzable (también conocido como código inactivo) de un paquete. Como indica la documentación de Webpack versión 3:
“Puedes imaginar tu aplicación como un árbol. El código fuente y las bibliotecas que realmente utilizas representan las hojas verdes y vivas del árbol. El código muerto representa las hojas marrones muertas del árbol que se consumen en el otoño. Para deshacerse de las hojas muertas, hay que sacudir el árbol, provocando que caiga”.
El término fue popularizado por primera vez en la comunidad front-end por el equipo Rollup . Pero los autores de todos los lenguajes dinámicos han estado luchando con el problema desde mucho antes. La idea de un algoritmo de sacudida de árboles se remonta al menos a principios de la década de 1990.
En el ámbito de JavaScript, la agitación de árboles ha sido posible desde la especificación del módulo ECMAScript (ESM) en ES2015, anteriormente conocido como ES6. Desde entonces, la agitación de árboles se ha habilitado de forma predeterminada en la mayoría de los paquetes porque reducen el tamaño de salida sin cambiar el comportamiento del programa.
La razón principal de esto es que los MEA son estáticos por naturaleza. Analicemos lo que eso significa.
Módulos ES frente a CommonJS
CommonJS es anterior a la especificación ESM por algunos años. Surgió para abordar la falta de soporte para módulos reutilizables en el ecosistema JavaScript. CommonJS tiene una require()
función que recupera un módulo externo según la ruta proporcionada y lo agrega al alcance durante el tiempo de ejecución.
Como require
cualquier function
otro en un programa, hace que sea bastante difícil evaluar el resultado de su llamada en tiempo de compilación. Además de eso, está el hecho de que require
es posible agregar llamadas en cualquier parte del código, envueltas en otra llamada de función, dentro de declaraciones if/else, en declaraciones switch, etc.
Con el aprendizaje y las luchas que han resultado de la amplia adopción de la arquitectura CommonJS, la especificación ESM se ha decidido por esta nueva arquitectura, en la que los módulos se importan y exportan mediante las respectivas palabras clave import
y export
. Por lo tanto, no más llamadas funcionales. Los ESM también se permiten solo como declaraciones de nivel superior; no es posible anidarlos en ninguna otra estructura, ya que son estáticos : los ESM no dependen de la ejecución en tiempo de ejecución.
Alcance y efectos secundarios
Sin embargo, existe otro obstáculo que la sacudida de árboles debe superar para evitar la hinchazón: los efectos secundarios. Se considera que una función tiene efectos secundarios cuando se altera o se apoya en factores externos al ámbito de ejecución. Una función con efectos secundarios se considera impura . Una función pura siempre producirá el mismo resultado, independientemente del contexto o del entorno en el que se ejecute.
const pure = (a:number, b:number) = a + bconst impure = (c:number) = window.foo.number + c
Los paquetes cumplen su propósito al evaluar el código proporcionado tanto como sea posible para determinar si un módulo es puro. Pero la evaluación del código durante el tiempo de compilación o de agrupación solo puede llegar hasta cierto punto. Por lo tanto, se supone que los paquetes con efectos secundarios no se pueden eliminar adecuadamente, incluso cuando sean completamente inalcanzables.
Debido a esto, los paquetes ahora aceptan una clave dentro del package.json
archivo del módulo que permite al desarrollador declarar si un módulo no tiene efectos secundarios. De esta manera, el desarrollador puede optar por no participar en la evaluación del código y dar pistas al paquete; El código dentro de un paquete en particular se puede eliminar si no hay una importación accesible o require
una declaración que lo vincule. Esto no sólo hace que el paquete sea más ágil, sino que también puede acelerar los tiempos de compilación.
{ "name": "my-package", "sideEffects": false}
Por lo tanto, si es desarrollador de paquetes, utilícelo concienzudamente sideEffects
antes de publicarlo y, por supuesto, revíselo en cada lanzamiento para evitar cambios importantes inesperados.
Además de la sideEffects
clave raíz, también es posible determinar la pureza archivo por archivo, anotando un comentario en línea, /*@__PURE__*/
en la llamada al método.
const x = */@__PURE__*/eliminated_if_not_called()
Considero que esta anotación en línea es una vía de escape para el desarrollador consumidor, que debe realizarse en caso de que un paquete no se haya declarado sideEffects: false
o en caso de que la biblioteca realmente presente un efecto secundario en un método en particular.
Optimización del paquete web
Desde la versión 4 en adelante, Webpack ha requerido cada vez menos configuración para que las mejores prácticas funcionen. La funcionalidad de un par de complementos se ha incorporado al núcleo. Y como el equipo de desarrollo se toma muy en serio el tamaño del paquete, han facilitado la tarea de sacudir los árboles.
Si no eres muy hábil con los retoques o si tu aplicación no tiene casos especiales, entonces cambiar tus dependencias es cuestión de una sola línea.
El webpack.config.js
archivo tiene una propiedad raíz denominada mode
. Siempre que el valor de esta propiedad sea production
, sacudirá el árbol y optimizará completamente sus módulos. Además de eliminar el código inactivo con TerserPlugin
, mode: 'production'
habilitará nombres deterministas alterados para módulos y fragmentos, y activará los siguientes complementos:
- uso de dependencia de bandera,
- la bandera incluía trozos,
- concatenación de módulos,
- no emitir errores.
No es casualidad que el valor de activación sea production
. No querrás que tus dependencias estén completamente optimizadas en un entorno de desarrollo porque hará que los problemas sean mucho más difíciles de depurar. Por lo tanto, sugeriría abordarlo con uno de dos enfoques. Videos de incestos y xxx gratis
Por un lado, podrías pasar una mode
bandera a la interfaz de línea de comando de Webpack:
# This will override the setting in your webpack.config.jswebpack --mode=production
Alternativamente, puedes usar la process.env.NODE_ENV
variable en webpack.config.js
:
mode: process.env.NODE_ENV === 'production' ? 'production' : development
En este caso, debe recordar pasar --NODE_ENV=production
su canal de implementación.
Ambos enfoques son una abstracción además de los muy conocidos definePlugin
de Webpack versión 3 e inferiores. La opción que elijas no hace ninguna diferencia.
Webpack versión 3 y siguientes
Vale la pena mencionar que es posible que los escenarios y ejemplos de esta sección no se apliquen a las versiones recientes de Webpack y otros paquetes. Esta sección considera el uso de UglifyJS versión 2 , en lugar de Terser . UglifyJS es el paquete del que se bifurcó Terser, por lo que la evaluación del código puede diferir entre ellos.
Debido a que la versión 3 y posteriores de Webpack no admiten la sideEffects
propiedad en package.json
, todos los paquetes deben evaluarse completamente antes de eliminar el código. Esto por sí solo hace que el enfoque sea menos efectivo, pero también se deben considerar varias advertencias.
Como se mencionó anteriormente, el compilador no tiene forma de descubrir por sí mismo cuándo un paquete está alterando el alcance global. Pero esa no es la única situación en la que se salta la sacudida de árboles. Hay escenarios más confusos.
Tome este ejemplo de paquete de la documentación de Webpack:
// transform.jsimport * as mylib from 'mylib';export const someVar = mylib.transform({ // ...});export const someOtherVar = mylib.transform({ // ...});
Y aquí está el punto de entrada de un paquete de consumo:
// index.jsimport { someVar } from './transforms.js';// Use `someVar`...
No hay forma de determinar si mylib.transform
provoca efectos secundarios. Por tanto, no se eliminará ningún código.
Aquí hay otras situaciones con un resultado similar:
- invocar una función desde un módulo de terceros que el compilador no puede inspeccionar,
- reexportar funciones importadas de módulos de terceros.
Una herramienta que podría ayudar al compilador a hacer funcionar la sacudida de árboles es babel-plugin-transform-imports . Dividirá todas las exportaciones de miembros y con nombre en exportaciones predeterminadas, lo que permitirá que los módulos se evalúen individualmente.
// before transformationimport { Row, Grid as MyGrid } from 'react-bootstrap';import { merge } from 'lodash';// after transformationimport Row from 'react-bootstrap/lib/Row';import MyGrid from 'react-bootstrap/lib/Grid';import merge from 'lodash/merge';
También tiene una propiedad de configuración que advierte al desarrollador que evite declaraciones de importación problemáticas. Si tiene Webpack versión 3 o superior y ha realizado su debida diligencia con la configuración básica y ha agregado los complementos recomendados, pero su paquete aún parece inflado, le recomiendo que pruebe este paquete.
Tiempos de elevación y compilación del alcance
En la época de CommonJS, la mayoría de los paquetes simplemente envolvían cada módulo dentro de otra declaración de función y los asignaban dentro de un objeto. Eso no es diferente a cualquier objeto de mapa que exista:
(function (modulesMap, entry) { // provided CommonJS runtime})({ "index.js": function (require, module, exports) { let { foo } = require('./foo.js') foo.doStuff() }, "foo.js": function(require, module, exports) { module.exports.foo = { doStuff: () = { console.log('I am foo') } } }}, "index.js")
Además de ser difícil de analizar estáticamente, esto es fundamentalmente incompatible con los ESM, porque hemos visto que no podemos ajustar import
las export
declaraciones. Entonces, hoy en día, los paquetes elevan cada módulo al nivel superior:
// moduleA.jslet $moduleA$export$doStuff = () = ({ doStuff: () = {}})// index.js$moduleA$export$doStuff()
Este enfoque es totalmente compatible con los ESM; Además, permite la evaluación del código para detectar fácilmente módulos que no se llaman y eliminarlos. La advertencia de este enfoque es que, durante la compilación, lleva mucho más tiempo porque toca cada declaración y almacena el paquete en la memoria durante el proceso. Ésa es una de las principales razones por las que el rendimiento de los paquetes se ha convertido en una preocupación aún mayor para todos y por la que los lenguajes compilados se están aprovechando en herramientas para el desarrollo web. Por ejemplo, esbuild es un paquete escrito en Go y SWC es un compilador de TypeScript escrito en Rust que se integra con Spark, un paquete también escrito en Rust.
Para comprender mejor el aumento del alcance, recomiendo encarecidamente la documentación de Parcel versión 2 .
Evite la transpilación prematura
Hay un problema específico que, lamentablemente, es bastante común y puede resultar devastador para la sacudida de árboles. En resumen, sucede cuando trabajas con cargadores especiales, integrando diferentes compiladores a tu paquete. Las combinaciones comunes son TypeScript, Babel y Webpack, en todas las permutaciones posibles.
Tanto Babel como TypeScript tienen sus propios compiladores y sus respectivos cargadores permiten al desarrollador usarlos para una fácil integración. Y ahí reside la amenaza oculta.
Estos compiladores llegan a su código antes de la optimización del mismo. Y ya sea por defecto o por una mala configuración, estos compiladores a menudo generan módulos CommonJS, en lugar de ESM. Como se mencionó en una sección anterior, los módulos CommonJS son dinámicos y, por lo tanto, no se pueden evaluar adecuadamente para eliminar el código muerto.
Este escenario se está volviendo aún más común hoy en día, con el crecimiento de aplicaciones "isomorfas" (es decir, aplicaciones que ejecutan el mismo código tanto en el lado del servidor como en el del cliente). Debido a que Node.js aún no tiene soporte estándar para ESM, cuando los compiladores se dirigen al node
entorno, generan CommonJS.
Por lo tanto, asegúrese de verificar el código que recibe su algoritmo de optimización .
Lista de verificación para sacudir árboles
Ahora que conoce los entresijos de cómo funcionan la agrupación y la agitación de árboles, elaboremos una lista de verificación que puede imprimir en algún lugar útil para cuando revise su implementación actual y su base de código. Con suerte, esto le ahorrará tiempo y le permitirá optimizar no solo el rendimiento percibido de su código, ¡sino tal vez incluso los tiempos de compilación de su canalización!
- Utilice ESM, y no solo en su propia base de código, sino que también prefiera paquetes que generen ESM como consumibles.
- Asegúrese de saber exactamente cuáles (si las hay) de sus dependencias no se han declarado
sideEffects
o no se han configurado comotrue
. - Utilice anotaciones en línea para declarar llamadas a métodos que sean puras cuando consuman paquetes con efectos secundarios.
- Si está generando módulos CommonJS, asegúrese de optimizar su paquete antes de transformar las declaraciones de importación y exportación.
Autoría de paquetes
Con suerte, a estas alturas todos estamos de acuerdo en que los ESM son el camino a seguir en el ecosistema de JavaScript. Sin embargo, como siempre ocurre en el desarrollo de software, las transiciones pueden ser complicadas. Afortunadamente, los autores de paquetes pueden adoptar medidas continuas para facilitar una migración rápida y fluida para sus usuarios.
Con algunas pequeñas adiciones a package.json
, su paquete podrá indicar a los paquetes los entornos que admite el paquete y cómo se admiten mejor. Aquí hay una lista de verificación de Skypack :
- Incluya una exportación ESM.
- Agregar
"type": "module"
. - Indique un punto de entrada a través de
"module": "./path/entry.js"
(una convención comunitaria).
Y aquí hay un ejemplo que resulta cuando se siguen todas las mejores prácticas y desea admitir entornos web y Node.js:
{ // ... "main": "./index-cjs.js", "module": "./index-esm.js", "exports": { "require": "./index-cjs.js", "import": "./index-esm.js" } // ...}
Además de esto, el equipo de Skypack ha introducido un puntaje de calidad del paquete como punto de referencia para determinar si un paquete determinado está configurado para longevidad y mejores prácticas. La herramienta es de código abierto en GitHub y se puede agregar como devDependency
a su paquete para realizar las comprobaciones fácilmente antes de cada lanzamiento.
Terminando
Espero que este artículo te haya sido útil. Si es así, considere compartirlo con su red. Espero interactuar con usted en los comentarios o en Twitter.
Recursos útiles
Artículos y documentación
- “ Módulos ES: una inmersión profunda en dibujos animados ”, Lin Clark, Mozilla Hacks
- “ Sacudida de árboles ”, paquete web
- “ Configuración ”, paquete web
- “ Optimización ”, paquete web
- “ Scope Hoisting ”, documentación de Parcel versión 2
Proyectos y Herramientas
- Tersero
- babel-plugin-transform-importaciones
- paquete aéreo
- paquete web
- Parcela
- Enrollar
- construir
- SWC
- Verificación de paquete
(vf, il, al)Explora más en
- Guías
- paquete web
- javascript
- Mejoramiento
Tal vez te puede interesar:
- ¿Deberían abrirse los enlaces en ventanas nuevas?
- 24 excelentes tutoriales de AJAX
- 70 técnicas nuevas y útiles de AJAX y JavaScript
- Más de 45 excelentes recursos y repositorios de fragmentos de código
Sacudir árboles: una guía de referencia
¿Qué significa realmente sacudir los árboles?Módulos ES frente a CommonJSAlcance y efectos secundariosOptimización del paquete webWebpack versión 3 y sig
programar
es
https://aprendeprogramando.es/static/images/programar-sacudir-arboles-una-guia-de-referencia-1097-0.jpg
2025-01-15

Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente