Tercera parte: Usando un administrador de paquetes de JavaScript (npm)
La mayoría de los lenguajes de programación proporcionan una forma de importar código de un archivo a otro. JavaScript no fue diseñado originalmente con esta función, porque JavaScript fue diseñado para ejecutarse solo en el navegador, sin acceso al sistema de archivos de la computadora del cliente (por razones de seguridad). Entonces, durante mucho tiempo, organizar el código JavaScript en varios archivos requirió que cargaras cada archivo con variables compartidas globalmente.
Esto es en realidad lo que estamos haciendo con el ejemplo de moment.js anterior: todo el archivo moment.min.js se carga en el HTML, que define una variable global moment, que luego está disponible para cualquier archivo cargado después moment.min.js (independientemente de si necesita o no acceso a ella).
En 2009, se inició un proyecto llamado CommonJS con el objetivo de especificar un ecosistema para JavaScript fuera del navegador. Una gran parte de CommonJS era su especificación de módulos, que finalmente permitiría a JavaScript importar y exportar código a través de archivos como la mayoría de los lenguajes de programación, sin recurrir a variables globales. La implementación más conocida de los CommonJS modules es node.js.
Como se mencionó anteriormente, node.js es un ambiente de ejecución de JavaScript diseñado para ejecutarse en el servidor. Así es como se vería el ejemplo anterior usando módulos node.js. En lugar de cargar todo moment.min.jscon una etiqueta de script HTML, puede cargarlo directamente en el archivo JavaScript de la siguiente manera:
// index.jsvar moment = require('moment');
console.log("Hello from JavaScript!");
console.log(moment().startOf('day').fromNow());
Nuevamente, así es como funciona la carga de módulos en node.js, que funciona muy bien ya que node.js es un lenguaje del lado del servidor con acceso al sistema de archivos de la computadora. Node.js también conoce la ubicación de cada ruta de módulo npm, por lo que en lugar de tener que escribir require(’./node_modules/moment/min/moment.min.js), simplemente puedes escribir require(‘moment’), bastante bien hast aqui no!!.
Todo esto es genial para node.js, pero si intenta usar el código anterior en el navegador, obtendrá un error que dice require no está definido. El navegador no tiene acceso al sistema de archivos, lo que significa que cargar módulos de esta manera es muy complicado: la carga de archivos debe realizarse de forma dinámica, ya sea de forma sincrónica (lo que ralentiza la ejecución) o asincrónica (lo que puede tener problemas de tiempo).
Aquí es donde entra un empaquetador de módulos. Un module bundler de JavaScript es una herramienta que soluciona el problema con un paso de compilación (que tiene acceso al sistema de archivos) para crear un resultado final que sea compatible con el navegador (que no necesita acceso al sistema de archivos). En este caso, necesitamos un module bundler para encontrar todas las declaraciones require (que es una sintaxis JavaScript de navegador no válida) y reemplazarlas con el contenido real de cada archivo requerido. El resultado final es un solo archivo JavaScript empaquetado (sin sentencias require).
El module bundler más popular fue Browserify, que se lanzó en 2011 y fue pionero en el uso de sentencias de require al estilo node.js en el frontend (que es esencialmente lo que permitió a npm convertirse en el administrador de paquetes de frontend de primera elección). Alrededor de 2015, el webpack finalmente se convirtió en el module bundler más utilizado (impulsado por la popularidad de React, que aprovechó al máximo las diversas características de webpack).
Echemos un vistazo a cómo usar webpack paraque el require(‘moment’) anterior funcione en el navegador. Primero necesitamos instalar webpack en el proyecto. Webpack en sí es un paquete npm, por lo que podemos instalarlo desde la línea de comandos:
$ npm install webpack webpack-cli --save-dev
Ten en cuenta que estamos instalando dos paquetes: webpack y webpack-cli (que nos permite utilizar webpack desde la línea de comandos). También ten en cuenta que: –save-dev lo guarda como una dependencia de desarrollo, lo que significa que es un paquete que necesita en su entorno de desarrollo pero no en su servidor de producción. Puede ver esto reflejado en el archivo package.json, que se actualizó automáticamente:
{
"name": "modern-javascript-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.19.1"
},
"devDependencies": {
"webpack": "^4.17.1",
"webpack-cli": "^3.1.0"
}
}
Ahora tenemos webpack y webpack-cli instalados como paquetes en la carpeta node_modules. Puedes usar webpack-cli desde la línea de comando de la siguiente manera:
$ webpack index.js --mode=development
Este comando ejecutará la herramienta de webpack que se instaló en la carpeta node_modules, comenzará con el archivo index.js, buscará las declaraciones require y las reemplazará con el código apropiado para crear un único archivo de salida (que por defecto es dist/main.js o dist/bundle.js). El argumento --mode=development es para mantener el JavaScript legible para los desarrolladores, en lugar de una salida reducida con el argumento --mode=production.
Ahora que tenemos la salida dist/main.js de webpack, la usaremos en lugar de index.js en el navegador, ya que este contiene declaraciones require no válidas. Esto se reflejaría en el archivo index.html de la siguiente manera:
<!-- index.html --><!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><title>JavaScript Example</title><scriptsrc="dist/main.js"></script></head><body><h1>Hello from HTML!</h1></body></html>
Si actualiza el navegador, debería ver que todo funciona como antes.
Ten en cuenta que necesitaremos ejecutar el comando de webpack cada vez que cambiemos el index.js. Esto es tedioso y se volverá aún más tedioso a medida que usemos las funciones más avanzadas de webpack. Sin embargo eso ya esta resuelto, Webpack puede leer opciones de un archivo de configuración en el directorio raíz del proyecto llamado webpack.config.js, que en nuestro caso se vería así:
// webpack.config.jsmodule.exports = {
mode: 'development',
entry: './index.js',
output: {
filename: 'main.js',
publicPath: 'dist'
}
};
Ahora, cada vez que cambiamos index.js, podemos ejecutar webpack con el comando:
$ webpack
Ya no necesitamos especificar las opciones index.js y --mode=development, ya que el webpack carga esas opciones desde el archivo webpack.config.js. Esto es mejor, pero sigue siendo tedioso ingresar este comando para cada cambio de código; veremos que este proceso sera más fluido mas adelante.
En general, esto puede parecer poco, pero este flujo de trabajo tiene grandes ventajas. Ya no cargamos scripts externos a través de variables globales. Cualquier nueva biblioteca de JavaScript se agregará mediante declaraciones require en JavaScript, en lugar de agregar nuevas etiquetas <script> en HTML. Tener un solo archivo bundle de JavaScript es a menudo mejor para el rendimiento. Y ahora que agregamos un paso de compilación, ¡hay otras características poderosas que podemos agregar a nuestro flujo de trabajo de desarrollo!