Novedades ES6

Novedades de ES6 - Modulos

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.
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.

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.

Un módulo es un archivo de JavaScript que agrupa funciones, clases, variables que luego pueden ser exportadas y utilizadas en otras partes de nuestra aplicación. Un módulo permite ocultar funcionalidad del mismo y solo exportar aquello para lo que ha sido implementado.

Esta característica agregada a JavaScript nos permite implementar programas mucho más ordenados y facilita la reutilización del código.

export e import

Veamos un ejemplo donde implementamos un módulo y luego lo consumimos:

modulo1.js

export function sumar(x,y) {
  return x+y;
}

export function restar(x,y) {
  return x-y;
}

El módulo consta de 2 funciones exportadas, podría haber dentro del módulo otras funciones, clases, variables etc. sin la palabra export que solo se pueden acceder dentro del módulo.

Para consumir un módulo desde otro módulo o una página HTML debemos utilizar la palabra clave import:

<!DOCTYPE html>
<html>
<head>
  <title>Ejemplo de JavaScript</title>
  <meta charset="UTF-8">
</head>
<body>

<script type="module">

  import {sumar, restar} from './modulo1.js';
  alert(sumar(3,8));
  alert(restar(10,3));

</script>  
</body>
</html>

Luego de la palabra clave import indicamos entre llaves los nombres de las funciones, clases, variables, constantes etc. que importamos, también debemos indicar entre comillas el nombre del módulo y el path o dirección donde se almacena (con ./ indicamos que se encuentra en la misma carpeta):

  import {sumar, restar} from './modulo1.js';

Una vez importado los recursos del paquete los podemos acceder por su nombre:

  alert(sumar(3,8));

Es muy importante indicarle al navegador que estamos utilizando la nueva tecnología de módulos asignando a la propiedad type el valor de 'module':

<script type="module">

Para exportar recursos de un módulo podemos utilizar otra sintaxis:

function sumar(x,y) {
  return x+y;
}

function restar(x,y) {
  return x-y;
}

export {sumar, restar};

En un principio se definen dos funciones privadas al módulo y luego mediante la palabra clave export y entre llaves indicamos los nombres de las funciones a exportar.

default export

Es una práctica común en JavaScript crear módulos que exporten un único valor. En estos casos tenemos una sintaxis distinta para indicar el recurso a exportar mediante las palabras claves 'default export'.

Veamos un ejemplo donde almacenamos en un módulo la clase Dado y la exportamos por defecto:

dado.js

export default class Dado {
  constructor() {
    this.tirar();
  }
  
  get valor() {
    return this._valor;
  }
  
  tirar() {
    this._valor=Math.trunc(Math.random()*6)+1;
  }
}

Luego cambia la sintaxis cuando tenemos que importar la clase:

<!DOCTYPE html>
<html>
<head>
  <title>Ejemplo de JavaScript</title>
  <meta charset="UTF-8">
</head>
<body>

<script type="module">

  import Dado from './dado.js';
  const dado1 = new Dado();
  alert(dado1.valor);
  dado1.tirar();
  alert(dado1.valor);

</script>  
</body>
</html>

No debemos indicar entre llaves el recurso a importar (si las disponemos se genera un error sintáctico):

  import Dado from './dado.js';

Otra práctica común es no indicar el nombre de la clase en el módulo, luego el nombre del módulo representa la clase:

export default class {
  constructor() {
    this.tirar();
  }
  
  get valor() {
    return this._valor;
  }
  
  tirar() {
    this._valor=Math.trunc(Math.random()*6)+1;
  }
}

Hemos indicado export default para la clase, luego el archivo se llama 'dado.js' y representa dicha clase. No hay cambios para hacer uso de la clase:

  import Dado from './dado.js';

export y default export en el mismo módulo

Un módulo solo puede exportar por defecto un solo recurso pero puede tener otros recursos que se exportan. Veamos un ejemplo:

export default class{
  constructor() {
    this.tirar();
  }
  
  get valor() {
    return this._valor;
  }
  
  tirar() {
    this._valor=Math.trunc(Math.random()*6)+1;
  }
}

export const lados=6;

En éste módulo se exporta por defecto la clase y además se exporta una constante llamada lados.

Para consumir los dos valores exportados por este módulo tenemos la siguiente sintaxis:

<!DOCTYPE html>
<html>
<head>
  <title>Ejemplo de JavaScript</title>
  <meta charset="UTF-8">
</head>
<body>

<script type="module">

  import Dado, {lados} from './dado.js';
  const dado1 = new Dado();
  alert('valor del dado:'+dado1.valor);
  alert('lados del dado:'+lados);

</script>  
</body>
</html>

Importamos la clase por defecto y la constante lados:

  import Dado, {lados} from './dado.js';

Importar todos los recursos definidos con export.

Si un módulo tiene varios recursos definidos con la palabra clave export luego los podemos importar a todos juntos utilizando la siguiente sintaxis:

<script type="module">

  import * as operacion from './modulo1.js';
  alert(operacion.sumar(3,8));
  alert(operacion.restar(10,3));

</script>  

Importamos el módulo1.js que tiene dos funciones export y definimos luego de la palabra clave as el nombre de un objeto que tendrá como propiedades los recursos expotados por dicho módulo.

Llamamos ahora a cada función antecediendo el nombre del objeto creado:

  alert(operacion.sumar(3,8));

Importar un valor por defecto definiendo un alias.

Otra sintaxis para importar un recurso definido con 'export default' es mediante la sintaxis:

<!DOCTYPE html>
<html>
<head>
  <title>Ejemplo de JavaScript</title>
  <meta charset="UTF-8">
</head>
<body>

<script type="module">

  import {default as Dado, lados} from './dado.js';
  const dado1 = new Dado();
  alert('valor del dado:'+dado1.valor);
  alert('lados del dado:'+lados);

</script>  
</body>
</html>

Utilizamos la palabra clave default y seguidamente el nombre o alias que se asigna al recurso importado (debe ir entre llaves como cuando importamos valores definidos con 'export'):

  import {default as Dado, lados} from './dado.js';

Reexportar un recurso importado en un módulo.

Un módulo que importa un recurso puede reexportar el mismo. Veamos un ejemplo con dos módulos y el archivo html:

dado.js

export default class{
  constructor() {
    this.tirar();
  }
  
  get valor() {
    return this._valor;
  }
  
  tirar() {
    this._valor=Math.trunc(Math.random()*6)+1;
  }
}

export const lados=6;

juegodedados.js

import Dado, {lados} from './dado.js'

export class JuegoDeDados {
  constructor() {
    this.dado1=new Dado();
    this.dado2=new Dado();
    this.dado3=new Dado();
  }

  sumaDados() {
    return this.dado1.valor+this.dado2.valor+this.dado3.valor;
  }
}

export {lados};

El módulo 'juegodedados.js' importa la clase por defecto 'Dado' y la constante 'lados'. Luego este módulo exporta la clase JuegoDeDados y reexporta 'lados':

export {lados};

pagina1.html

<!DOCTYPE html>
<html>
<head>
  <title>Ejemplo de JavaScript</title>
  <meta charset="UTF-8">
</head>
<body>

<script type="module">

  import {JuegoDeDados, lados} from './juegodedados.js';
  const juego=new JuegoDeDados();
  alert('suma de los tres dados:'+juego.sumaDados());
  alert('lados del dado:'+lados);

</script>  
</body>
</html>

Como vemos solo importamos el módulo 'juegodedados.js' y recuperamos la clase JuegoDeDados y lados:

 import {JuegoDeDados, lados} from './juegodedados.js';

No hace falta importar el módulo 'dado.js' para acceder a la constante 'lados'.

Si en el módulo 'juegodedados.js' no haremos uso de la constante 'lados' pero queremos reexportar la misma podemos utilizar ésta otra sintaxis:

import Dado from './dado.js'

export class JuegoDeDados {
  constructor() {
    this.dado1=new Dado();
    this.dado2=new Dado();
    this.dado3=new Dado();
  }

  sumaDados() {
    return this.dado1.valor+this.dado2.valor+this.dado3.valor;
  }
}

export {lados} from './dado.js';