ManzDev
Perfil de Manz Dashboard de actividad
HTML5 Etiquetas HTML
CSS CSS vanilla
PostCSS Transformador CSS
Javascript Javascript ES2020+
NPM Node Package Manager
WebComponents Componentes web
Terminal Terminal de GNU/Linux
VueJS Framework VueJS 3
Automatizadores Empaquetadores Javascript
Youtube Canal de Youtube
Twitch Canal de Twitch
Telegram Canal de Telegram
Discord Servidor de Discord

Módulos ECMAScript (ESM)

Uno de los principales problemas que ha ido arrastrando Javascript desde sus inicios es la dificultad de organizar de una forma adecuada una aplicación grande, con muchas líneas de código. En muchos lenguajes, cuando un programa crece, se comienza a estructurar en funciones y, posteriormente, en clases. De esta forma organizamos de forma más lógica el código de nuestro programa.

Pero tener todo el código en un sólo fichero Javascript se vuelve confuso y complejo en cuanto el código crece. En la mayoría de los lenguajes de programación, el código se divide en ficheros diferentes de modo que, por ejemplo, cada clase está localizada en un fichero separado. De esta forma, todo queda mucho más organizado e intuitivo, y es fácil de localizar, aunque crezca.

Funciones, clases y ficheros (módulos)

En Javascript (el «lado del cliente»), esto se complica un poco, ya que presenta algunas problemáticas que no existen en otros lenguajes de programación. Hay que tener bien presente que cuando accedemos a una página o aplicación web, estamos accediendo a un servidor, desde donde se está descargando código HTML/CSS/Javascript (a nuestro navegador), y una vez descargado ahí, se ejecuta el código Javascript.

En un principio, y de forma nativa, la forma más extendida era incluir varias etiquetas <script> desde nuestra página HTML. De esta forma, podíamos tener varios ficheros Javascript separados, cada uno para una finalidad concreta. Sin embargo, este sistema termina siendo muy poco modular, ofrecía algunas desventajas y resultaba lento, ya que sobrecargaba al cliente con múltiples peticiones extra (desde el cliente al servidor).

Con el tiempo, se desarrollaron sistemas no oficiales que permitían separar en varios archivos, como CommonJS (utilizado en NodeJS) o RequireJS (AMD), cada uno con sus particularidades, virtudes y desventajas.

¿Qué son los módulos ES?

En ECMAScript se introduce una característica nativa denominada Módulos ES o ESM, que permite la importación y exportación de datos entre diferentes ficheros Javascript, eliminando las desventajas que teníamos hasta ahora y permitiendo trabajar de forma más flexible en nuestro código Javascript.

Para trabajar con módulos tenemos a nuestra disposición las siguientes palabras clave:

Declaración Descripción
export Exporta datos (variables, funciones, clases...) del fichero actual hacia otros que los importen.
import Importa datos (variables, funciones, clases...) desde otro fichero .js al actual.

Mediante la palabra clave export crearemos lo que se llama un módulo de exportación que contiene datos. Estos datos puede ser variables, funciones, clases u objetos más complejos (a partir de ahora, elementos). Si dicho módulo ya existe, podremos ir añadiendo más propiedades. Por otro lado, con la palabra clave import podremos leer dichos módulos desde otros ficheros y utilizar sus elementos en nuestro código.

Un ejemplo sencillo para ver el funcionamiento de import y export en su modo más básico:

// Fichero constants.js
export const magicNumber = 42;

// Fichero index.js
import { magicNumber } from "./constants.js";

console.log(magicNumber);   // 42

Exportación de módulos

Por defecto, un fichero Javascript no tiene módulo de exportación si no se usa un export al menos una vez en su código. Existen varias formas de exportar datos mediante la palabra clave de Javascript export:

Forma Descripción
export ... Declara un elemento o dato, añadiéndolo además al módulo de exportación.
export { name } Añade el elemento name al módulo de exportación.
export { n1, n2, n3... } Añade los elementos indicados ( n1 , n2 , n3 ...) al módulo de exportación.
export * from './file.js' Añade todos los elementos del módulo de file.js al módulo de exportación.
export default ... Declara un elemento y lo añade como módulo de exportación por defecto.

Además, como veremos a continuación, es posible renombrar los elementos exportados utilizando as seguido del nuevo nombre a utilizar. Por otro lado, si se realiza un export default, ese elemento será la exportación por defecto. Sólo puede haber una exportación por defecto por fichero.

Existen varias formas de exportar elementos. La más habitual, quizás, es la de simplemente añadir la palabra clave export a la izquierda de la declaración del elemento Javascript que deseamos exportar, ya sea una variable, una constante, una función, una clase u otro objeto más complejo:

export const number = 42;           // Se añade la constante number al módulo
export const f1 = () => 99;         // Se añade la función f1 al módulo
export default f2 = () => "Manz";   // Se añade la función f2 como exportación por defecto

Ten en cuenta que en el caso de utilizar una exportación por defecto en una declaración, no es posible utilizar var , let o const . Tampoco es posible usar export dentro de funciones, bucles o contextos específicos.

Por otro lado, si provienes de NodeJS, es muy probable que te resulte más intuitivo exportar módulos al final del fichero, ya que es como se ha hecho desde siempre en Node con los module.exports. Con export también podemos hacerlo de esta forma:

let number = 4;
const saludar = () => "¡Hola!";
const goodbye = () => "¡Adiós!";
class Clase {}

export { number };                       // Se crea un módulo y se añade number
export { saludar, goodbye as despedir }; // Se añade saludar y despedir al módulo
export { Clase as default };             // Se añade Clase al módulo (default)
export { saludar as otroNombre };        // Se añade otroNombre al módulo

// En este punto, nuestro módulo exporta number, saludar, despedir, default y otroNombre.

Hasta aquí, hemos aprendido a exportar elementos y añadirlos al módulo de exportación del fichero Javascript en el que nos encontramos. En el siguiente apartado, veremos las diferentes formas de importar estos elementos desde otro fichero.

Importación de módulos

La palabra clave import es la opuesta de export. Si con export podemos exportar datos o elementos de un fichero, con import podemos cargar un módulo de exportación desde otro fichero Javascript, para utilizar dichos elementos en nuestro código.

Existen varias formas de importar código, utilizando esta palabra clave import:

Forma Descripción
import nombre from './file.js' Importa el elemento por defecto de file.js en nombre.
import { nombre } from './file.js' Importa el elemento nombre de file.js.
import { n1, n2... } from './file.js' Importa los elementos indicados desde file.js.
import * as name from './file.js' Importa todos los elementos de file.js en el objeto name.
import './file.js' No importa elementos, sólo ejecuta el código de file.js.

Recuerda, al igual que hacíamos en la exportación, también puedes renombrar elementos con import utilizando as seguido del nuevo nombre.

En el primer caso, realizamos una importación por defecto, donde importamos el elemento desde el módulo file.js y lo guardamos en nombre. En el segundo y tercer caso, realizamos una importación nombrada, donde importamos los elementos con el nombre indicado en el interior de los corchetes, desde el módulo file.js.

Las importaciones por defecto suelen estar ligeramente mal vistas por algunos desarrolladores. Una exportación nombrada suele ser más intuitiva y predecible a la hora de utilizar en nuestro código.

En el cuarto caso, importamos todos los elementos del módulo externo file.js en un objeto de nombre obj (es obligatorio indicar el nombre en este caso) y en el quinto y último caso, no importamos elementos, simplemente leemos el código del módulo y lo ejecutamos.

Bare imports

Los bare imports (o imports desnudos) son aquellos que se realizan indicando en from un string que no comienza por . o /, sino directamente por el nombre de una carpeta o paquete:

import { Howler, Howl } from "howler";  // Bare import

En este ejemplo, en lugar de utilizar ./howler.js (o una ruta similar), se indica el string howler. Esta característica no es estándar ni está soportada directamente por los navegadores, sino que es un invento que popularizó NodeJS a través de NPM.

Generalmente, esto significa que se buscará un paquete con ese nombre en la carpeta node_modules, y que probablemente se estará utilizando algún bundler o automatizador como Webpack o similar. Tienes más información en Bare imports.

Metadatos de módulos

Cuando nos encontramos en un fichero .js que es un módulo, podemos acceder a la propiedad import.meta, la cuál es un objeto que contiene metadatos del módulo en cuestión. Por ejemplo, una propiedad url que nos devuelve la ruta completa del módulo en cuestión:

// main.js
import module from "./module.js";

// module.js
console.log(import.meta);     // { url: "https://dominio.com/module.js" }

Esto puede ser realmente útil en ocasiones que queremos saber en que fichero nos encontramos en tiempo de ejecución.

Convenciones de módulos ES

  • Si queremos utilizar import y export desde el navegador directamente, deberemos añadir los archivos .js que contienen los módulos con la etiqueta <script> utilizando el atributo type="module". En esta modalidad, los archivos Javascript se cargan en diferido, es decir, al final, como lo hace un <script defer>:
<script type="module" src="file.js"></script>
  • Por norma general, a los archivos Javascript con módulos se les pone la extensión .js, aunque también se pueden encontrar con otras extensiones como .es2015 o .mjs (menos extendidas).

  • Se aconseja utilizar las rutas UNIX en los export e import , ya que son las que tienen mejor soporte, tanto en navegadores como en NodeJS. También se pueden indicar rutas absolutas para cargar directamente desde el navegador:

// Incorrecto (no empieza por . o por /)
import { elemento } from "module.mjs";
import { elemento } from "folder/module.mjs";

// Correcto
import { elemento } from "./module.mjs";    // misma carpeta del .js
import { elemento } from "/module.mjs";     // carpeta raíz
import { elemento } from "../module.mjs";   // carpeta anterior al .js
import { ceil } from "https://unpkg.com/[email protected]/lodash.js";

En resumen, las exportaciones e importaciones en Javascript son una herramienta indispensable para escribir código Javascript moderno de una forma organizada y productiva.



Manz
Publicado por Manz

Docente, divulgador informático y Streamer de código. Amante del CSS y de la plataforma web en general. Autor de Emezeta.com tiempo atrás. Ha trabajado como profesor en la Universidad de La Laguna y es director del curso de Programación web FullStack y FrontEnd de EOI en Tenerife. En sus ratos libres, busca GIF de gatos en Internet.