ES6 Modules

  • Por
Conoce y aprende a usar los módulos de ES6, ya soportados de manera nativa por los navegadores. Uso de import y export en los ES6 Modules.

Una de las estupendas novedades de ES6 es la posibilidad de crear módulos, que son piezas de código que podemos escribir en ficheros independientes. Los módulos pueden tener código, como clases, funciones, objetos o simples datos primitivos, que se puede importar desde otros archivos.

Con la palabra "export" consigues exponer algún miembro del módulo, para que se pueda usar desde fuera. Con la palabra "import" consigues traerte algo exportado por un módulo independiente.

Aunque lo cierto es que seguro que muchos de vosotros ya venís usando módulos desde hace años, apoyándose en transpiladores como Babel, la novedad es que hoy ya podemos usarlos en la mayoría de los navegadores. Safari, Chrome, Opera ya los pueden usar de manera nativa y Firefox y Edge también los tienen implementados, aunque todavía hay que activarlos en la configuración del navegador (aunque posiblemente cuando leas este artículo también los puedas usar sin necesidad de configurar nada).

En este artículo te explicaremos cómo usar "ES6 modules" y como exportar e importar cosas entre distintos módulos.

Nota: Si quieres conocer otras características del estándar de Javascript, te aconsejamos leer el Manual de ECMAScript 2015 (ES6).

Cómo usar un módulo desde un archivo HTML

Lo primero que hay que decir es que debes hacer una acción especial para que el navegador procese correctamente los archivos Javascript que usan módulos. Básicamente se trata de advertir al navegador que el script Javascript usa módulos, para lo que necesitas traerte ese código desde el HTML usando una sintaxis especial en la etiqueta SCRIPT. Simplemente tenemos que marcar con type="module" el script que queremos incluir.

<script src="index.js" type="module"></script>

A partir de este momento, en index.js ya puedes usar imports de otros módulos, tal como explicaremos seguidamente.

Compatibilidad en navegadores antiguos

El uso de este "type" es importante, no solo porque así te aseguras que puedes usar ES6 modules, sino porque puedes implementar alternativas para diversos navegadores. Aunque el soporte es nativo en las recientes versiones de browsers, es obvio que los módulos no van a funcionar en todos los navegadores viejos o poco actualizados (léase los IE antiguos). Para ellos podemos disponer de una alternativa.

De momento, debes percibir que no todos los navegadores entenderán qué es un type="module". Al no saber qué tipo de script es un "module", los navegadores antiguos simplemente no harán nada con él. Es lógico, pues aunque lo abrieran no lo podrían entender.

Para los navegadores viejos hay que seguir transpilando, con Babel o similares y creando alternativas de scripts que no usen módulos ES6.

Nota: puedes obtener más información de la transpilación y Babel en este artículo: Introducción a ES6.

Los archivos transpilados a ES5, sin usar módulos los cargarás por medio de una etiqueta SCRIPT que incluya un atributo "nomodule", de esta manera:

<script src="codigo-transpilado.js" nomodule></script>

En resumen:

  • Los navegadores modernos entenderán el atributo "nomodule", por lo tanto no accederán a este script, porque sabrán que es un script pensado por y para los navegadores obsoletos.
  • Los navegadores antiguos no entenderán el atributo "nomodule", pero tampoco le harán caso y cargarán este script alternativo.

Export

Como hemos dicho, usamos la sentencia export para permitir que otros módulos usen código del presente módulo.

Con un export puedes exportar todo tipo de piezas de software, como datos en variables de tipos primitivos, funciones, objetos, clases. Ahora vamos a comenzar viendo un caso sencillo de hacer un export, luego veremos más alternativas.

export const pi = 3.1416;

Simplemente anteponemos la palabra export a aquello que queremos exportar hacia afuera.

Import

En el momento que queramos cargar alguna cosa de un módulo externo, usaremos la sentencia import. Para ello tenemos que indicar qué es lo que queremos importar y la ruta donde está el módulo que contiene aquello que se desea importar.

import { pi } from './pi-module.js';

Así estamos importando la anterior constante definida "pi", que estaba en un archivo aparte, en un módulo llamado "pi-module.js". Observarás por la ruta que pi-module.js está en la misma ruta que el archivo desde el que importamos.

Una vez importado ese dato (lo que sea que se importe) lo podemos usar como si la declaración hubiera sido hecha en este mismo archivo.

Resumen de uso de módulos

Para que te quede claro el proceso, vamos a ver listados los tres archivos que puedes tener para hacer el primer test a los módulos de ES6.

index.html

Tendremos un archivo index.html que tendrá que acceder al script que trabaja con módulos.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>test es6 modules</title>
</head>
<body>
  <h1>Test de los módulos ES6</h1>

  <script src="index.js" type="module"></script>
</body>
</html>

index.js

Este es el archivo principal de Javascript, en el que vamos a hacer los import.

Una vez que has hecho el import correspondiente, puedes usar aquello que hayas importado.

import { pi } from './pi-module.js';
console.log(pi);

Podrías importar varias cosas al mismo tiempo de un módulo, en cuyo caso entre las llaves colocarías todos los elementos a importar, separados por comas.

import { pi, e, log2 } from './pi-module.js';

pi-module.js

Este es el archivo que realiza el export, el módulo que expone hacia afuera las piezas de software que sean necesarias.

export const pi = 3.1416;

// En este archivo puedo tener código Javascript que no exporto... es como código privado del módulo
var variableLocalPrivada = 222;

Es interesante observar cómo el módulo puede tener cosas que exporta y cosas que se quedan dentro. Por ejemplo puedes tener funciones que realizan cálculos internos, que no necesitan o que no deberían ser invocadas desde fuera del módulo. Obviamente, te da el trabajo de ser explícito en todo lo que deseas exportar, pero te permite tener código privado que no se puede usar desde fuera.

Export default

Cuando exportamos algo en un módulo podemos definir que sea la exportación por defecto. No es necesario que se exporte nada por defecto en un módulo, pero en caso de hacerlo, debes tener en cuenta que sólo se puede exportar una cosa "default" por módulo.

Por ejemplo, así se puede exportar una función en un módulo, marcando que sea la exportación predeterminada:

Archivo validate-email-function.js con export default

function validateEmail(email) {
  if(email.indexOf('@') != -1) {
    return true;
  }
  return false
}

export default validateEmail

Importar el elemento default

A la hora de importar aquel elemento marcado como default tenemos que hacer el import de una manera ligeramente diferente.

import validateEmail from './validate-email-function.js';

Crear un alias (namespace) para los elementos importados

Otra cosa útil que puedes hacer es crear un alias para los elementos importados, con la palabra "as". Vamos a verlo con un ejemplo.

Imagina que tienes varias funciones definidas en un módulo.

export function reir() {
  console.log('jajaja');
}

export function reirFuerte() {
  console.log('JAJAJAJAJA');  
}

export function reirSuave() {
  console.log('jeje');
}

Ya sabes que podrías importarlas todas de una vez, separando por comas todo aquello que quieres importar. Luego podrás usar las funciones como si las hubieras declarado en este mismo archivo.

import { reir, reirFuerte, reirSuave} from './risas.js';

reir();
reirFuerte();
reirSuave();

Pero otra posibilidad sería traernos todos los elementos exportados, asignando un alias, como un espacio de nombres, mediante el cual se conocerán en el módulo que importa. En este caso, todas las funciones dependerán del alias, como puedes ver a continuación.

import * as risas from './risas.js';

risas.reir();
risas.reirFuerte();
risas.reirSuave();

Organizar los exports, para declararlos todos de una vez

Algo que puedes hacer es exportar una única vez en el módulo todo lo que quieras que se vea desde fuera. Es una práctica normal, que permite tener un poco más de orden en lo que se está entregando en un módulo hacia el exterior. Obviamente, es totalmente opcional.

function titular(cadena) {
  return `<h1>${cadena}</h1>`;
}
function parrafo(cadena) {
  return `<p>${cadena}</p>`;
}
function salto() {
  return '<br>';
}

export const tags = {
  titular,
  parrafo,
  salto
}

Lo único que estás exportando es un objeto, donde tienes las funciones que deseas exponer hacia fuera. Ahora, al importar, con que hagas un import, podrás traerte todos los elementos exportados. Aunque, como ves, realmente es solo uno que los contiene a todos.

import { tags } from './tags.js';

console.log(tags.titular('Hola!'));
console.log(tags.parrafo('Esto es un test de los módulos ES6'));

Conclusión sobre los módulos ECMAScript 2015

Con lo que has aprendido no deberías tener problemas al usar módulos, definidos en el estándar ECMAScript 2015 (ES6). Como has visto, permite organizar el código de distintas maneras, colocando cada cosa en su sitio: cada módulo con su responsabilidad definida.

Lo más interesante es que ya se puede usar en los navegadores modernos, por lo que es una realidad ya disponible para la programación frontend. La pena es que los navegadores antiguos no la van a conocer nunca, por lo que hasta que desaparezcan finalmente tenemos que seguir usando transpiladores y compactando el código en un único fichero. No es tarea difícil y hemos aprendido a colocar scripts diferentes para navegadores que aceptan módulos o no, pero significa un paso adicional, que lógicamente deberías automatizar para que no suponga un esfuerzo constantemente.