Los 3 tipos de módulos en Javascript

Al hablar de módulos en JS uno puede llegar a confundirse. Por un lado encuentras tutoriales sobre como configurar tus archivos a la CommonJS, por otro ves que te recomiendan que uses ESModules y a veces encuentras una explicación acerca de un módulo clásico que no se parece a ninguno de los otros 2.

Es confuso si, pero dentro de poco verás que las diferencias en realidad son mínimas.

Classic module

Antes que nada debes de tener muy claro que es un módulo en Javascript. Para ello puedes leer: ¿Qué es un módulo en JS?

Si no, de cualquier manera aquí tienes la definición: “Un módulo es un a colección de datos y funciones encapsuladas stateful con accesibilidad pública y privada”.

const transactions = function() {
    const transactionsList = [
        {   
            id: 1,
            amount: 54.56
        },
        {   
            id: 2,
            amount: 119.00
        }
    ]

    function getTransactionData(transactionId) {
        return transactionsList.find(transaction => transaction.id === transactionId)
    }

    return {
        getTransactionData
    }
    
}

Para utilizar el módulo simplemente tienes que crear una instancia del mismo.

const transactionInstance = transactions()

const transactionItem = transactionInstance.getTransactionData(2)

console.dir(transactionItem)

CommonJS

¿Conoces Node? Es un entorno de ejecución de Javascript. Utilizado más que nada para realizar proyectos back-end con javascript.

NodeJS nos compete para este tema porque precisamente para su entorno se crearon los módulos de CommonJS.

const transactionsList = [
    {   
        id: 1,
        amount: 54.56
    },
    {   
        id: 2,
        amount: 119.00
    }
]

function getTransactionData(transactionId) {
    return transactionsList.find(transaction => transaction.id === transactionId)
}

module.exports.getTransactionData = getTransactionData

Lo primero que notarás es la falta de encapsulación dentro de una función (como en el módulo clásico). Esto de hecho es la diferencia más importante.

Los módulos de CommonJS se basan en archivos. Osea que un archivo equivale a un módulo. Lo que esto ocasiona es que por default los elementos ya se encuentren encapsulados y son privados. Encapsularlos en una función de nuevo sería redundante.

Así mismo tiempo la síntaxis para indicar que elementos son públicos es diferente. En CommonJS se usa el objeto predefinido module.exports en lugar de usar el return dentro de la función en un módulo clásico.

¿Y como se usa? Simple. Si en CommonJS, el módulo tiene que ser un archivo, entonces para usarlo necesitas invocar ese archivo desde otro lado. Para lograrlo solo tienes que importar ese archivo/módulo donde lo quieres utilizar.

Supongamos que el código anterior lo guardaste con el nombre commonjs_module.js

const transactionInstance = require('./commonjs_module')

console.dir(transactionInstance)
const transactionItem = transactionInstance.getTransactionData(2)

console.dir(transactionItem)

!Eso es todo! Ya tienes un módulo CommonJS funcionando.

ESModule

Estos módulos se comportan en esencia de igual manera que los de CommonJS. Pero también proveen algunas funcionalidades adicionales al momento de exportar e importar elementos.

La diferencia mas notable es que estos módulos corren por default con strict_mode. ¿No sabes qué es eso? Bueno agregalo a tu temario y cuando termines de entender estos tipos de módulos puedes aprender acerca del strict mode.

Primero lo más fácil. Los cambios en sintaxis:

const transactionsList = [
    {   
        id: 1,
        amount: 54.56
    },
    {   
        id: 2,
        amount: 119.00
    }
]

function getTransactionData(transactionId) {
    return transactionsList.find(transaction => transaction.id === transactionId)
}

export { getTransactionData }

Este es el ejemplo más simple. Como ves lo único que cambia es la sintaxis al momento de definir los elementos a exportar (los que van a ser de acceso público).

Adicional a este formato ESModules brinda otra alternativa de sintaxis: agregar la keyword export al principio de cada elemento que quieras exportar:


const transactionsList = [
    {   
        id: 1,
        amount: 54.56
    },
    {   
        id: 2,
        amount: 119.00
    }
]

export function getTransactionData(transactionId) {
    return transactionsList.find(transaction => transaction.id === transactionId)
}

function anotherTransactionFunction() {
    console.log('I do other stuff')
}

* La función nueva anotherTransactionFunction sería privada en este ejemplo


A su vez ESModule cambia la sintaxis a la hora de importar, reemplazando require con import.

import { getTransactionData } from './es_module'

const transactionItem = getTransactionData(2)

console.dir(transactionItem)

Eso es lo básico , ahora veamos las funcionalidades adicionales.

Renombrar elementos importados

Permite reenombrar el elemento importado en caso de que creas que hay algo mas adecuado, o por si hubiera algún conflicto.

import { getTransactionData as getData } from './es_module'

const transactionItem = getData(2)

console.dir(transactionItem)

Seleccionar los elementos importados

Te permite elegir cuáles de los elementos públicos vas a importar.

Digamos que tienes 3 funciones públicas:

const transactionsList = [
    {   
        id: 1,
        amount: 54.56
    },
    {   
        id: 2,
        amount: 119.00
    }
]

export function getTransactionData(transactionId) {
    return transactionsList.find(transaction => transaction.id === transactionId)
}

export function coolFunction() {
    console.log('I do cool stuff')
}

export function anotherTransactionFunction() {
    console.log('I do other stuff')
}

Tu puedes elegir si quieres importar solo uno de los elementos:

import { anotherTransactionFunction } from './es_module_syntax_a'

anotherTransactionFunction()

Algunos de ellos:

import { anotherTransactionFunction, coolFunction } from './es_module'

coolFunction()
anotherTransactionFunction()

O todos:

import * as transactionInstance from './es_module'

transactionInstance.coolFunction(); 

Esto se llama namespace import.

*Nota que necesitas proporcionar un nombre forzosamente para este caso.

Default import

Puedes especificar un elemento que se comporte como el elemento a importar por default. Para hacerlo simplemente agrega la keyword default al elemento que desees.


const transactionsList = [
    {   
        id: 1,
        amount: 54.56
    },
    {   
        id: 2,
        amount: 119.00
    }
]

export default function getTransactionData(transactionId) {
    return transactionsList.find(transaction => transaction.id === transactionId)
}

export function anotherTransactionFunction() {
    console.log('I do other stuff')
}

Ahora al momento de importar puedes hacer esto:

import getTransactionData from './es_module'

const transactionItem = getTransactionData(2)

console.dir(transactionItem)

¿En qué ayuda esto? Es un poco de syntax sugar, es decir, sirve para que se vea bien pero no tiene funcionalidades diferentes.

A su vez si quieres adicionalmente importar algun otro elemento que no sea el default, simplemente puedes hacer lo siguiente:

import getTransactionData, {anotherTransactionFunction} from './es_module'

const transactionItem = getTransactionData(2)
console.dir(transactionItem)

anotherTransactionFunction()

Como ves a pesar de que no cambie la funcionalidad, la sintaxis puede variar un poco y eso hace que se preste a que se cambie por simple cuestión de gustos. Entonces es muy importante que la comprendas para que puedas leer el código sin importar como lo hayan escrito.