En temas anteriores hablamos de las importaciones y exportaciones de los módulos ESM en Javascript, utilizando las palabras clave export e import. Este tipo de mecanismo nos permite exportar datos desde un fichero a otro, reutilizando contenido, haciendo el código más modular y organizando mejor nuestras aplicaciones o webs.
Esta forma de importar se denomina estática y tiene algunas limitaciones. Por ejemplo, los import se deben escribir al inicio de nuestro fichero y no se puede realizar un import a mitad o al final de nuestro código. Además, tampoco podemos incluir import dentro de sentencias de control, como por ejemplo un if.
Import dinámico
Por esa razón, vamos a analizar otro tipo de importaciones, denominada import dinámico donde ciertas característiscas cambian o son diferentes.
El operador import()
Como hemos mencionado, los import que hemos utilizado hasta ahora se conocen como import estáticos. Se colocan en la parte superior del fichero Javascript y son algo similar a lo siguiente:
import { name } from "./module.js";
Sin embargo, existe otro tipo de importación en Javascript, popularmente conocida como import dinámico (dynamic import), que tiene el siguiente aspecto, ligeramente diferente al import anterior:
// Utilizando promesas
import("./module.js").then(data => { /* ... */ });
// Utilizando async/await
const data = await import("./module.js");
En este segundo caso, el import() tiene unos paréntesis que lo diferencian del anterior, y al ser asíncrono, sabemos que nos devuelve una promesa.
Diferencia import vs import()
Los import estáticos son muy útiles, pero tienen algunas desventajas si se presentan ciertos casos específicos. Veamos algunos de estos casos, donde los import() dinámicos quizás sean más interesantes:
- 1️⃣ Queremos importar un módulo si se cumple una determinada condición (en un
if) - 2️⃣ Queremos importar un módulo interpolando variables o constantes
- 3️⃣ Queremos importar un módulo dentro de un ámbito específico
- 4️⃣ Queremos importar un módulo desde un script normal (sin type="module")
- 5️⃣ Queremos importar un fichero javascript (sin módulo) y ejecutarlo bajo demanda
En cada uno de estos casos, no se puede utilizar el import estático, pero si que podemos utilizar un import dinámico:
// Opción 1: Se carga functions.js si se cumple la condición
if (number > 42) {
import("./functions.js")
.then(module => module.func());
}
// Opción 2: Se carga functions.js interpolando la constante
const filename = "functions";
import(`./${filename}.js`)
.then(module => module.func());
// Opción 3: Se carga aditional.js sólo si el usuario hace click en el botón
const button = document.querySelector("button.info");
button.addEventListener("click", () => import("aditional.js"), { once: true });
Como se puede ver, el import dinámico nos permite indicar entre los paréntesis del import el nombre del archivo Javascript. A diferencia del import estático, este fichero no se cargará desde el principio, sino que sólo lo hará cuando se llegue a esta parte del código, siendo posible incluirla dentro de condicionales, funciones o lógica diversa. Debido a esto, es asíncrono y hay que manejarlo con promesas o async/await.
Si el archivo
.jsimportado es un módulo, al trabajar con la promesa que devuelve simplemente accedemos a las propiedades o métodos que necesitemos. Por otro lado, si el archivo.jscargado no es un módulo, simplemente se ejecutará su contenido.
Esto nos proporciona una característica muy interesante de cara a optimización y rendimiento, y es que es posible no importar (ni por lo tanto, procesar) ficheros con código Javascript hasta que ocurra una cierta condición, evento o acción, retrasando así la descarga, procesamiento y ejecución de código Javascript por parte del navegador hasta que sea necesario (o incluso nunca si no es necesario).
Code splitting en bundlers
En el ecosistema Javascript, existen ciertas herramientas denominadas automatizadores o bundlers. Dichas herramientas, entre otras cosas, suelen tener como objetivo generar un sólo archivo .js final donde se guardará todo el código Javascript de nuestra web o aplicación, revisado y procesado, y es el que leerá el navegador.
Esto tiene un significado y razones históricas (cuando no existían los módulos en Javascript), pero aún en la actualidad tiene ciertas ventajas y se utiliza mucho en frameworks SPA o librerías como React, Vue o similares.
Sin embargo, si estamos utilizando una de estas herramientas, al realizar dicha empaquetación en un sólo archivo .js, podemos pensar que estamos «rompiendo» esa característica deseable de separar el código y sólo ejecutarlo cuando sea necesario (cuando el usuario haga click en un botón, cuando ocurra cierto evento, etc...). Esta característica se denomina Code Splitting (separación de código).
Herramientas como Webpack o Rollup separan los archivos importados con imports dinámicos en ficheros aislados (chunks), que generalmente tienen el nombre del módulo fichero javascript importado, junto a un hash aleatorio. De esta forma, el código de dicho fichero o módulo no será incluído en ese fichero .js general, evitando que se cargue siempre por parte del navegador.
