Importando módulos en Javascript, lo estamos haciendo bien?
Tags: javascript, typescript, es6, treeshaking
Published: 2021-08-31 15:24:17

Todos los desarrolladores Javascript usamos librerías en nuestro día a día, facilita mucho nuestro trabajo. Hoy en día lo hacemos mayormente de esta manera:

```javascript
import * as lib from 'super-lib'
import { func1 } from 'super-lib'
```

Si analizamos rápidamente esto, en la primera sentencia estamos importando todo y asignándolo a la variable `lib`, en el segundo, estamos importando todo nuevamente pero usando destructuring obtenemos solo lo que queremos; qué sucede con todo el código que no se está usando?

<br>

> ## _¿Todo el código que no se está usando terminará en el bundle final haciendo mi aplicación innecesariamente pesada?_

<br>

Hoy, aprenderemos cómo mejorar el tamaño de un bundle simplemente cambiando la manera en que importamos. Después de leer esto, serás capaz de detectar una simple oportunidad de optimización para el bundle.

---
<br>

## TL; DR

Verifica si la libería tiene soporte para ES6 y serás capaz de importar como quieras, siempre obtendrás el mejor resultado 🙆‍♂️. Si no lo soporta ⚠️, tendrás que importar usando cherry-picking.

<br>

---

## ¿Podemos importar de cualquier manera sin consecuencias?

Cuando compilamos aplicaciones front-end, hay un proceso que Webpack aplica llamado [Tree Shaking](https://webpack.js.org/guides/tree-shaking/). Básicamente, es eliminación de código, el código que no está siendo usado por nadie. Esto es un proceso que previene que código muerto termine en nuestro bundle final, haciéndolo más liviano así las aplicaciones van a cargar más rápidamente para nuestros usuarios!.

Analicemos esto:

```javascript
import * as lib from 'amazing-lib'
import { foo } from 'amazing-lib'
```

En ambos casos todo el contenido de la librería está siendo importado, en la primera línea es donde se observa más claramente, todo el contenido está siendo asignado a la variable `lib`, en la segunda línea estamos simplemente aplicando destructuring al contenido de la librería para obtener lo que queremos. Gracias a Tree Shaking todo el código que no es usado no termina en el bundle final.

Así que, gracias a Tree Shaking tengo un pase libre a importar como yo quiera y todo el código muerto importado de la librería será removido automágicamente?

**No siempre es el caso**

Hay un escenario cuando Tree Shaking no será capaz de detectar qué es código muerto, teniendo como consecuencia que no se removerá nada.

## Escenarios

### ES6

La Sintaxis de Modulo ECMAScript 2015(también conocido como ES6); suena complejo, pero es algo bastante popular en estos días. Es solo una sintaxis para importar un modulo Javascript, luce así:

```javascript
import { foo } from 'super-lib'
import { bar } from '../utils'
import * as lib from '../utils'

export const justAConst = 'foobar'
```

Cuando usas una librería que ofrece soporte para para sintaxis ES6, no hay de qué preocuparse, importa libremente, Tree Shaking estará ahí 😉. De hecho, es con la única sintaxis de modulo que Tree Shaking soporta. Miremos a la [documentación](https://webpack.js.org/guides/tree-shaking/):

> *Tree Shaking* es un término comúnmente usado en el contexto Javascript para la eliminación del código muerto. Él se apoya en la estructura estática de la sintaxis de modulo ES2015, esto es `import` y `export`...
>
> El lanzamiento de Webpack 2 vino con soporte para los módulos ES2015 (también conocido como módulos de armonía) junto a la detección de módulos exportados que no están siendo usados...

###### Si eres completamente nuevo acerca de los 👉 [Módulos JS](https://developer.mozilla.org/es/docs/Web/JavaScript/Guide/Modules)

### Sin sintaxis de modulo ES6

Una librería puede ser empaquetada con otro sistema de módulos diferente a ES6, un proceso de compilación pudo haberse implementado para soportar solo [CommonJS](https://nodejs.org/api/modules.html#modules_modules_commonjs_modules) por ejemplo. Los proyectos escritos usando Javascript puro (sin procesos de transpiración (Babel, TypeScript)) que usan unicamente CommonJs para gestionar sus módulos es otro ejemplo.

Así que, sin sintaxis ES6 significa no Tree Shaking. La única manera de tener un bundle saludable cuando se lidia con librerías sin soporte para la sintaxis de módulo ES6 es importar usando una técnica llamada cherry-picking, se necesita especificar la ruta absoluta al archivo que contiene la información que necesitamos.

```javascript
import { small } from 'common-js-lib/small';
```

#### Inconvenientes del cherry-picking

- Es necesario saber la ruta al modulo necesitado. (Tu IDE puede ayudar en esto)
- Es necesario especificar uno a uno los módulos requeridos, ej:

    ```javascript
    import has from 'lodash/has';
    import capitalize from 'lodash/capitalize';
    import lastIndexOf from 'lodash/lastIndexOf';
    ```

- Como autor de una librería, probablemente querrás un sistema de carpetas fácil de usar para detectar rápidamente algo en tu librería.
- Puedes olvidar hacerlo y sin querer se hace el bundle innecesariamente más pesado. [EsLint](https://eslint.org/docs/rules/no-restricted-imports) puede ayudarte a importar correctamente.

## Test de Performance

Teniendo la teoría aprendida decidí probar todo esto. Lo que hice fue crear algunas librerías con soporte para diferentes módulos, crear algunas aplicaciones front-end con Angular y React[^1] para probar si Tree Shaking estaba realmente haciendo su trabajo.

Las librerías creadas fueron simples, ellas exportan dos variables `small` y `big`. `small` contiene un perro 🐕 (`small = '🐕'`), pero `big` tiene 1646400 perros (`big = '🐕🐕🐕🐕🐕🐕🐕🐕🐕...'`). Esto hará que `big` pese 6.3 megabytes.

Únicamente será usado `small` todo el tiempo, así que si `big` logra escabullirse hasta el bundle se podrá notar de inmediato.

### Bundle Saludable

Así es como un bundle saludable se ve:
![bundle saludable, de 211.78KB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0kar9uiffp7oi7pot3w.png)

### Bundle Oloroso 🤢

El bundle oloroso! Puedes notar una gran caja blanca que representa el 96.7% de la aplicación: ![Bundle Oloroso, bundle de 6.49MB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpvw6wwvwfjpbjhf8fjp.png)

### Los Resultados

Los resultados fueron los esperados, si la libería tenía para ofrecer la sintaxis de módulos de ES6, Tree Shaking hará su trabajo. Si no, cherry-picking es el único camino hacia el bundle saludable.

Aquí está el repositorio si son de los curiosos [dianjuar/how-to-import](https://github.com/dianjuar/how-to-import). Todo esto fue creado en un monorepo usando [Nx](https://nx.dev/), para simular la publicación del paquete en NPM se usó [yalc](https://github.com/wclr/yalc). El análisis del bundle fue hecho con [source-map-explorer](https://www.npmjs.com/package/source-map-explorer).

Yo también quise aplicar este test a algunas librerías conocidas y esto fue lo que encontré al importar de la siguiente manera `import { whatINeed } from 'popular-lib'`

| Librería                                                                                      | Bundle Saludable |
| :-------------------------------------------------------------------------------------------- | :--------------: |
| [lodash](https://www.npmjs.com/package/lodash)                                                |        ❌         |
| [moment](https://www.npmjs.com/package/moment)                                                |        ❌         |
| [rxjs](https://www.npmjs.com/package/rxjs)                                                    |        ✅         |
| [lodash-es](https://www.npmjs.com/package/lodash-es)                                          |        ✅         |
| [date-fns](https://www.npmjs.com/package/date-fns)                                            |        ✅         |
| [@angular/core](https://www.npmjs.com/package/@angular/core)                                  |        ✅         |
| [@angular/material](https://www.n-pmjs.com/package/@angular/material)                         |        ✅         |
| [react](http://www.npmjs.com/package/react)                                                   |        ✅         |
| [react-dom](http://www.npmjs.com/package/react-dom)                                           |        ✅         |
| [@material-ui/core](http://www.npmjs.com/package/@material-ui/core)                           |        ✅         |
| [@fortawesome/react-fontawesome](http://www.npmjs.com/package/@fortawesome/react-fontawesome) |        ✅         |

## Herramientas Útiles

Durante este experimento estuve usando la extensión de VsCode [Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost) y fue precisa paralelamente con los resultados. Con esta extensión podrán ver de manera inmediata cuánto costará el import en el bundle. No dirá directamente si estás teniendo un bundle saludable o no, pero te darás cuenta cuándo un import es sospechoso.

![Import Cost Live](https://camo.githubusercontent.com/08af5550483d9e9ccee846ebdbcb74332bdb523a738be4cb5aec8e4ba7df49e8/68747470733a2f2f636974772e6465762f5f6e6578742f696d6167653f75726c3d253246706f737473253246696d706f72742d636f73742532463171756f76335446706747327572376d79434c477473412e67696626773d3130383026713d3735)
> ###### Gif extraído del [README](https://github.com/wix/import-cost#import-cost--) de Cost Import

## Conclusión

Tree Shaking te tiene cubierto! Puedes importar como desees y siempre tendrás un bundle saludable **si y solo si** la librería tiene soporte para la sintaxis de módulos de ES6 (`import` y `export`).

Puedes hacer el bundle innecesariamente pesado si no haces cherry-pick al importar en librerías sin soporte de módulos ES6, como [lodash](https://www.npmjs.com/package/lodash).

[Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost) te puede ayudar a detectar un import que necesite algún refinamiento.

---

[^1]: El experimento es agnóstico al framework o librería usada, si webpack está encargado de generar el bundle el resultado sería el mismo.