Tree shaking 🌳
Tags: javascript, frontend, vite, react
Published: 2023-03-29 18:22:47

Para introducir este contenido te presento el siguiente problema:

Tienes que importar dos componentes llamados `<Button/>` y `<TextField/>` desde la librería MUI de una forma en que el tamaño del proyecto sea el menor posible una vez lo pasemos a productivo. ¿Cuál sería la forma óptima de hacerlo?

Consideraciones:
- Tienes un builder para este proceso (Vite -Se pronuncia vít!-, Webpack, Rollup, Turbopack, etc.).
- Olvidemos la -semántica- del código.
- Olvidemos la duración del proceso de build.
- Estamos en el front, vamos a usar ESM.

Aqui las alternativas:
1) Default import
```js
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
```
_Traernos componente por componente referenciando directamente a lo que estamos buscando._

2) Named import
```js
import { Button, TextField } from '@mui/material'
```
_Hacer una -destructuración- de objetos de MUI y extraer solo el Button y el TextField._

3) ImSoLazyToMakeGoodCode import (Namespace import)
```js
import * as mat from '@mui/material'
```
_Traernos todos los componentes de MUI y guardarlos en mui para luego referenciarlo como mui.Button y mui.TextField)_

Si dijiste la opción 1... Estas en lo correcto!
Si dijiste la opción 2... Tambien estas en lo correcto!
Si dijiste la opción 3... TAMBIEN ESTAS EN LO CORRECTO!

¿Y cómo es esto posible, si en la opción 3 le estamos diciendo explícitamente a Javascript que se traiga TODO MUI a nuestro proyecto? Aqui es donde entra en juego el concepto llamado "Tree shaking" del cual les quiero hablar en este artículo.

## Una analogía hecha por OpenAI
> Imagina un frondoso árbol en otoño, lleno de hojas de varios colores. A medida que el viento sopla, las hojas secas se desprenden y caen al suelo, dejando solo las hojas vivas y sanas en las ramas. Este proceso natural ayuda al árbol a mantenerse limpio y a conservar energía para las temporadas venideras.
> De manera similar, en el mundo del desarrollo, el "tree shaking" es un proceso de optimización que elimina el código no utilizado, o "muerto", de los archivos y paquetes del proyecto. Al igual que las hojas secas que caen de un árbol, el código no utilizado es identificado y eliminado, permitiendo que la aplicación final sea más liviana, rápida y eficiente.

## Dejemos las cosas claras...

Aclaremos que el concepto "Tree shaking" (Que si lo llevamos a nuestro idioma sería "Agitar el arbol") no es un concepto propio de Javascript si no que es un concepto informático y consiste en remover todo el código que no se está utilizando en nuestro proyecto al momento de transpilar, compilar y/o empaquetar, para así optimizar el tamaño y tiempo de carga del mismo de cara a levantarlo en un ambiente productivo.

Tampoco es un concepto nuevo, de hecho hacía mucho más sentido hacer "tree shaking" en el pasado (donde tenías que buscar meter la mayor cantidad de código en un disquete de 712KB) que ahora, donde los computadores tienen discos que, en tamaño, son mucho mas grandes de lo que te ofrecen en los servicios cloud.

## Como funciona el Tree shaking en Javascript?
Para que podamos realizar un proceso automatizado de tree shaking en nuestro proyecto necesitamos dos ingredientes:
- Realizar las importaciones de elementos mediante `import` y `export `(ES6)
- Un builder (Vite, Webpack, Rollup, etc.) 

El paso a paso de este flujo sería así:
- Le pedimos al builder que nos genere nuestro paquete de distribución. (Comúnmente lo encontraras como script de tu archivo package.json `npm run build`)
- El builder realizará un proceso denominado "análisis estático" de nuestro código en el cual se determina que dependencias está requiriendo nuestro proyecto y cuáles no.
- El builder, una vez finalizado este "análisis estático", procede a eliminar todo el código y dependencias que hayamos importado pero que no estemos usando. Este paso lo encontrarás documentado en muchos lugares como "DCE" (Por las siglas en ingles "Dead-Code elimination").
- Finalmente, del resultado de este proceso se realizará un minificado del código fuente el cual nos ayudará a reducir aún más el tamaño de nuestro producto. (Pero de este paso de minificación no trata este articulo pero lo menciono ya que en el ejemplo que les daré a continuación lo vamos a poder observar).

## Pongámoslo en práctica

Vamos a demostrar en la práctica que la respuesta al ejercicio que hicimos más arriba es la correcta, para esto vamos a inicializar un proyecto utilizando vite (No me metan a mi en la polémica de que React debería ser usado con un framework!)

Paso 1: Crear el proyecto (En mi caso use create-vite)
`npx create-vite@latest tree-shaking-demo`

Paso 2: Selecciona el framework de tu agrado (Vanilla, Vue, React, Preact, Svelte, etc...). En mi caso, voy por React porque es el más popular y lo tengo dentro de mis tags para traer mas gente :D.

Paso 3: Selecciona una variante (Javascript, Typescript, Javascript + SWC o Typescript + SWC). En mi caso me ire por Typescript con SWC (Speedy Web Compiler) por sobre Babel. ¿Por qué? porque una de las cosas que odio de javascript es el tipado débil que subsanaremos con typescript y si estamos hablando de mejorar performance, SWC está por sobre Babel.

Paso 4: Sigue las instrucciones en pantalla:
`cd tree-shaking-demo
npm install
npm run dev`

Paso 5: Con estos pasos anteriores ya tenemos un proyecto base para comenzar a agitar el arbol... PERO ANTES, traigamos a nuestro proyecto una librería que podamos importar... En mi caso, y acorde al ejercicio, me traeré MUI con una instalación de manual:
`npm install @mui/material @emotion/react @emotion/styled`

Paso 6: Abramos nuestro archivo src/App.tsx, borremos todo tu contenido (Trust me, i’m an engineer!) y reemplacémoslo por el siguiente código:

```js
function App() {
   return (
    <div>
      <TextField label="Lorem ipsum" variant="outlined" />
      <Button variant="outlined">Dolor</Button>
    </div>
  )
}
export default App
```

Tu navegador WEB y tu IDE probablemente se volvieron locos con errores porque, básicamente, estamos usando componentes que no hemos importado (aún), pero tranquilo... Con esto ahora sí que estamos listos para probar nuestro tree shaking.


### Demo 1: Importación mediante "default import"
Al principio de tu archivo src/App.tsx agrega las siguientes líneas:

```js
import Button from "@mui/material/Button"
import TextField from "@mui/material/TextField"
```

Con estas dos líneas hemos implementado los componentes Button y TextField mediante default import y todos los errores deberían desaparecer.

Ahora, ejecuta el step de build y confirmemos el tamaño del build:
`npm run build`
Mi resultado fue el siguiente (294.13kb... Enfoquémonos en el chunk de javascript)

![Resultado del proceso build mediante default import](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjce7rx8obabq19500v8.png)

### Demo 2: Importación mediante "named import"
Reemplacemos las dos líneas que agregamos anteriormente por lo siguiente:

```js
import { Button, TextField } from "@mui/material"
```

Y volvamos a buildear el proyecto
`npm run build`

Mi resultado fue el siguiente (294.14kb)

### Demo 3: Importación global de dependencias
Para este ejemplo nos vamos a traer completamente MUI y lo guardaremos en una variable llamado mui, para esto, reemplazaremos TODO El código anterior por lo siguiente:

```js
import * as mui from "@mui/material"

function App() {

  return (
    <div>
      <mui.TextField label="Lorem ipsum" variant="outlined" />
      <mui.Button variant="outlined">Dolor</mui.Button>
    </div>
  )
}

export default App
```

Hagamos un build del proyecto
`npm run build`

El resultado? 294.14kb

## Conclusión
Bits más, bits menos, hemos logrado demostrar con unos simples ejemplos que los builders de javascript, mediante el tree shaking, nos facilitan inmensamente la vida al momento de compactar el tamaño de nuestros proyectos y que, aunque le indiquemos explícitamente que queremos traernos todas las dependencias de MUI este de igual forma aplica eficiencia en nuestro código y nos compacta solamente lo necesario para que nuestro proyecto funcione.

Ahora, ¿Por qué no todos los métodos de importación pesan los mismos 294.14kb? ¿Por qué nuestra importación tipo default import pesa 294.13kb? Aquí es donde entra en juego el "minimizado de Código". Pero eso será motivo de un siguiente articulo ;).

### Código fuente del ejemplo:
https://github.com/AdrianGomezCL/tree-shaking-demo