¡Hola! Me parece que andas usando un bloqueador de anuncios =( ¿Nos desbloqueas? ¿Por qué?
F13

Funcion 13

Aprende ES6 - Los módulos: import y export

Como ya sabrás, en Función 13 estamos embarcados en que aprendas todos los entresijos de ES6. En artículos anteriores hemos hablado sobre let, const y los ámbitos, las funciones flecha, que son realmente útiles, las Promesas y las Clases, cómo podemos usar la Desestructuración o sobre parámetros y propagación e incluso sobre las plantillas con cadenas. Además hemos aprendido qué nuevas opciones tienen los objetos literales, las matrices (o arrays) y las cadenas. ES6 además introduce nuevos conceptos como Iteradores e Iterables. Si esto no te suena de nada y estás intentando ponerle algún sentido a las siglas, ¡no te preocupes! Echa un vistazo a nuestra Introducción a ES6.

En JavaScript siempre ha existido el problema de cómo modularizar las cosas. Durante muchos años había un sistema ampliamente aceptado: ninguno. Todo se escribía en variables globales y el caos se impuso.

Tras el caos, la comunidad se organizó y aparecieron dos formatos en los que la comunidad se dividió: AMD y CommonJS.

Diseño de Módulos Asíncronos (AMD) tiene en cuenta la naturaleza asíncrona de JavaScript pero algunos pensaron que era algo feo de leer al usar una función que encapsulara todo.

CommonJS es síncrono y por tanto bloquea pero es más sencillo de leer.

Veamos un ejemplo:

// AMD
define(function() {  
  return 'Funcion13';
})

// CommonJS
module.exports = 'Funcion13';  

Node escogió CommonJS mientras que la web se decantó por AMD. Eran los tiempos de RequireJS y Backbone.

Una nueva era

Con la llegada de ES6, llegó el estándar de los módulos. Como en todo, hay a gente que le gusta y a gente a la que no aunque realmente no importa ya que acabarán por implementarse lo queramos o no.

Veamos cómo funciona todo esto de los módulos en ES6.

export

Como hemos visto arriba, en CommonJS (Node) exportas valores usando module.exports. Se puede exportar cualquier cosa: primitivos, objetos, matrices, funciones, clases, etc.

module.exports = 13;  
module.exports = Inifinity;  
module.exports = () => {};  

Los módulos ES6 son ficheros que exportan algo, de manera similar a como lo hace CommonJS. Las declaraciones de variables, funciones e incluso exportaciones, son solo válidas para dicho módulo (igual que en CommonJS). Eso significa que si declaramos una variable o una función en un módulo, no podremos acceder a ellas directamente, a no ser que sean exportadas de forma explícita (y luego importadas, claro).

Exportando por defecto

Los módulos solo pueden tener un valor/clase/función exportada por defecto. Simplemente tenemos que cambiar el module.exports = que hemos visto antes por export default.

Veamos un ejemplo:

export default 13;  
export default Inifinity;  
export default () => {};  

Es conveniente destacar, que las sentencias export solo pueden estar ubicadas en el nivel más alto del módulo y no, no me refiero a la parte de arriba. Veamos un ejemplo de lo que no es válido:

if(hoyEsLunes) {  
  export default = 'No hay ganas de trabajar';
}

En este caso, export está dentro de un if por lo que no sería código válido.

Exportaciones con nombre

Volviendo al ejemplo de CommonJS, podemos exportar varias propiedades en module.exports de la siguiente forma:

module.exports.nombre = 'funcion13';  
module.exports.numero = 13;  

Por supuesto, podemos hacer lo mismo con ES6 usando la sintaxis de exportaciones con nombre. En vez de asignar propiedades a module.exports, simplemente prefijamos los elementos a exportar con la palabra export.

export const nombre = 'funcion13';  
export const numero = 13;  

Exportando listas

A la hora de exportar, especialmente si lo hacemos por nombre, hay un método que nos permite exportar cuantos elementos queramos de una forma clara y concisa, gracias a las nuevas adiciones a los objetos literales de las que ya hablamos.

const nombre = 'funcion13';  
const numero = 13;

export { nombre, numero }  

Este método suele gustar porque queda claro, normalmente al final del módulo, qué es lo que estamos exportando en vez de tener que revisar todo el módulo a ver qué líneas son las que tienen la palabra export. Esta sintaxis nos permite además renombrar propiedades:

export { nombre as nombrePagina }  

E incluso podemos añadir una exportación por defecto usando as default. Después de todo, default solo es una exportación con el nombre default. El código de abajo sería equivalente a escribir export default nombre y export numero después.

export { nombre as default, numero }  

Sobre exportaciones y mejores prácticas

Tener tantas formas de definir una exportación (listas, alias, por defecto) nos ofrece un gran poder y flexibilidad aunque también confusión. La pregunta inevitable es ¿qué uso ahora? Mi recomendación personal es que hagas un uso casi exclusivo de export default y que aparezca siempre al final de tu fichero.

¿Y si quiero exportar varias cosas?

Sigues pudiendo hacerlo así:

const miModulo = {  
  nombre: 'funcion13',
  numero: 13
}

export default miModulo  

De esta forma siempre nos queda claro qué estamos exportando y cuál es el API del módulo a simple vista en vez de tener que andas buscando. Por otro lado, si nos ceñimos a esto, dejamos de tener dudas sobre qué tipo de sintaxis vamos a usar.

Por último me gustaría destacar, que a la hora de exportar algo, estamos exportando un valor por referencia y si cambiamos dicho valor, cambiará también en los módulos que lo estuvieran usando. Esto es algo que no es recomendable hacer.

Veámoslo con un ejemplo:

let nombre = 'funcion13';

setTimeout(function(){  
  nombre = 'funcion14';
}, 1000);

export default nombre;  

Esto haría que durante un segundo el nombre fuera funcion13 y que luego cambiara a ser funcion14. ¡Nada recomendable!

import

Ya hemos aprendido cómo exportar y ahora tenemos que ver cómo podemos importar nuestros módulos. Esto nos permite cargar módulos desde otros. Merece la pena destacar que por el momento no hay ningún navegador (que no sea beta) que implemente esto de forma nativa. Ya veremos cómo solucionar esto.

Merece la pena destacar que el hecho de importar un módulo, ejecutará cualquier código de primer nivel que este módulo posea, veamos un rápido ejemplo de esto:

function calcular() {  
  return 2 + 2;
}

const cuatro = calcular();  
let valor = cuatro + calcular();

export default calcular;  

En este tonto ejemplo, todas las líneas de cálculo son ejecutadas (aunque no tengan uso alguno). Idealmente un módulo no debería realizar este tipo de efectos co-laterales al ser importado a no ser que sea necesario por algún motivo muy específico.

Importando exportaciones por defecto

Volvamos a CommonJS como ejemplo en el cual usaríamos require para importar un módulo. Así:

const _ = require('miModulo');  

Para importar la exportación por defecto de un módulo ES6 simplemente tenemos que escoger un nombre. La sintaxis tiene sus diferencias con la declaración de variables ya que, recordemos, estamos importando una asociación o referencia:

import moduloImportante from 'miModulo'  

Importando exportaciones con nombre

La sintaxis que vemos ahora es similar a la que vimos con la desestructuración para poder importar exportaciones con nombre:

import { pluck, cloneDeep } from 'lodash'  

En este caso estamos importando las funciones pluck y cloneDeep de la librería Lodash. De manera similar a como vimos con las importaciones, podemos asignar otros nombres:

import { cloneDeep as clone } from 'lodash'  

Y por supuesto podemos mezclar sintaxis:

import { default as _, cloneDeep } from 'lodash'  
import _,  { cloneDeep } from 'lodash'  

Ambas líneas son completamente equivalentes.

Importando todas las exportaciones

Es también posible importar un módulo completo. En vez de importar las exportaciones con nombre o la que haya por defecto, importará todo. Ten en cuenta que la sintaxis necesita un alias donde se estarán todas las asociaciones. Si había una exportación por defecto, estará en alias.default. Ten en cuenta que default no es más que una exportación con nombre especial.

import * as _ from 'lodash';  

Conclusiones

Recuerda que para usar los módulos en la mayoría de los navegadores, necesitarás usar Babel para convertir tu código a un sistema que entiendan los navegadores actuales.

El mayor beneficio de los módulos es el contar con un estándar que los navegadores puedan finalmente implementar y podamos disfrutar de algo que no dependa de la opinión o de hacia dónde sople el viento.

¿Dudas? ¡En los comentarios!

Programador Front-end en First + Third y Potato. Trabajando con JavaScript y HTML5 desde el corazón de Sevilla.

Comentarios ¡Únete a la conversación!