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.
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.
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.
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.
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';
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';
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));
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';
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';