Aprende Node.js con aplicaciones reales

Capítulo 5 - Inicializar proyectos de Node.js

Inicialización de un proyecto de Node.js

Normalmente, por cada proyecto de Node.js, se crea un directorio donde se almacenan todos los archivos y paquetes relacionados con el proyecto; No necesariamente un directorio debe estar en blanco para inicializarlo, por lo tanto, se continúa trabajando con el proyecto greeting.

npm puede almacenar toda la meta información y administrar los paquetes requeridos, con sus respectivas versiones del proyecto, a través de un archivo de manifiesto. Una de las maneras para crear este archivo manifiesto es con el comando npm init, que a su vez mostrará un asistente que guiará mediante preguntas con el fin de obtener la información necesaria para el proyecto y así hasta finalizar el proceso: Pero una manera más rápida de hacer este proceso para crear el archivo manifiesto, con todas las opciones que trae por defecto, con el siguiente comando:

npm init -y

El resultado es un archivo de texto llamado package.json, el cual contiene toda la información básica generada por defecto; estos valores se pueden cambiar solamente editando y guardando los cambios en el archivo. Lo más común y recomendado es que este archivo se encuentre en la raíz del directorio del proyecto.

Opcionalmente, para no tener que cambiar la información del archivo manifiesto cada vez que se crea un nuevo proyecto, es posible establecer valores por defecto para cuando se ejecute el comando npm init, como por ejemplo:

npm set init.author.name "Gustavo Morales"
npm set init.author.email "gustavo.morales@gmail.com"
npm set init.author.url "https://gmoralesc.me"

La otra opción es guardar estos valores en un archivo llamado .npmrc en la raíz del directorio del usuario o en la del directorio del proyecto, para ser más específico. Más información acerca del archivo npmrc.

Más información:

Administración de paquetes y configuración con el archivo package.json

Instalación de dependencias

El archivo package.json es muy importante, pues será el manifiesto de la aplicación. De ahora en adelante, para instalar un paquete como dependencia en el proyecto, añadimos la bandera -S o --save si queremos ser más explícitos.

Antes de instalar un paquete puede conocer toda la información acerca del mismo con el siguiente comando:

npm info colors

Instalar la dependencia en el proyecto con el siguiente comando:

npm install colors

En el archivo package.json se ha añadido la siguiente sección:

"dependencies": {
  "colors": "^1.4.0"
}

Indica el listado de dependencias del proyecto y la versión instalada de cada paquete. Si otra persona copiara este proyecto e instalará las dependencias (con el comando npm install) tendría la versión mínima del paquete colors equivalente a 1.4.0. El carácter ^ al inicio de la versión del paquete indica que se puede instalar cualquier upgrade o patch de la versión 1, pero no instalará ninguna versión con el mayor release 2; en otras palabras, podrá instalar 1.4.0 <= x < 2.0.0. Esta nomenclatura es llamada semver y permite establecer las reglas para las versiones de los paquetes del proyecto.

Para listar todas las dependencias que tiene el proyecto con el siguiente comando:

npm list

O alguna en particular se agrega el nombre de la dependencia con el siguiente comando:

npm list colors

Instalación de paquetes para desarrollo

Existen paquetes que no son dependencias, pero son necesarias en el entorno local de desarrollo; por ejemplo, el paquete ESLint permite comprobar la sintaxis de los archivos JavaScript basado en un conjunto de reglas. Esto puede ser muy útil a la hora de estandarizar el código seleccionando o creando una guía de estilo. Para incluirla en el manifiesto como paquete de desarrollo se incluye la bandera -D o --save-dev.

npm install --save-dev eslint

Muchos subcomandos de npm tienen accesos directos, en el anterior se puede reemplazar install por la abreviación i, es decir: npm i -D eslint

Ahora, en el archivo package.json, está incluida una nueva sección para los paquetes que se van a utilizar en el entorno de desarrollo; de esta manera es como npm distingue las dependencias necesarias a instalar en ambientes de producción.

"devDependencies": {
    "eslint": "^8.56.0"
},

Configuración de git

Es muy importante tener en cuenta que si se está utilizando un sistema de control de versiones se debe ignorar el directorio de node_modules, pues este no pertenece al código fuente del proyecto y además puede variar dependiendo de las versiones de los paquetes que se realice en cada instalación.

Si estamos utilizando git, se inicializa el repositorio si no lo está:

git init

En la raíz del directorio del proyecto se crea un archivo llamado .gitignore y se coloca las siguientes líneas:

node_modules/
.DS_Store
Thumbs.db

Con lo anterior, se establece que los archivos o directorios que cumplan con este patrón no serán incluidos en el repositorio de git. Aquí también se incluye los archivos como .DS_Store en el caso de Mac OS o Thumbs.db en el caso del sistema Windows, los cuales son archivos temporales para la generación de la vista previa (Thumbnails) de los directorios del sistema.

Si el proyecto se va a alojar en un repositorio público como Github, Gitlab o Bitbucket es muy recomendable crear un archivo llamado README.md con la descripción del proyecto; ya que una vez publicado, el repositorio mostrará información preliminar de este mismo. La sintaxis con la que se escribe este archivo es Markdown.

Más información:

Configuración de npm scripts

Cuando estemos trabajando con el proyecto vamos a repetir una y otra vez los mismos comandos en la Terminal. Estos comandos se pueden crear para agilizar el trabajo, y npm lo hace a través de los scripts. En el contenido del archivo package.json podrá observar la siguiente sección:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
},

En esta sección se pueden especificar los comandos que se desean utilizar en la Terminal. En el texto anterior está configurado para que el script test imprima una cadena en la pantalla, indicando que aún no se han configurado las pruebas. Este script se puede ejecutar de la siguiente manera:

npm test

Scripts incorporados

npm tiene varios alias de scripts ya incorporados para trabajar, como por ejemplo test, que vimos en la sección anterior. Así como su nombre lo indica, se utiliza para ejecutar las pruebas del proyecto. Uno muy recomendado es el script start que se ha convertido en una convención para iniciar los proyectos en Node.js, el cual se configurará más adelante.

Antes de agregar el script tenga en cuenta que en la llave main del archivo package.json está declarando cuál será el archivo de entrada por defecto del proyecto para ejecutarlo, este se llama index.js.

Por convención, el archivo principal se llama index.js pero puede ser cualquier nombre e inclusive en otra ubicación, no necesariamente en la raíz del proyecto.

Modificar la sección de scripts del archivo package.json

"scripts": {
  "start": "node index.js",
  "test": "echo \"Error: no test specified\"&& exit 1"
},

De ahora en adelante, para iniciar la aplicación de Node.js se ejecuta el siguiente comando:

npm start

Scripts personalizados

Es posible crear scripts personalizados para ejecutar diferentes tipos de tareas. En la sección anterior, se instalo y utilizo el paquete global ESLint, a continuación se un script personalizado que compruebe los archivos del proyecto:

"scripts": {
  "start": "node index.js",
  "test": "echo \"Error: no test specified\"&& exit 1",
  "lint": "eslint --no-eslintrc *.js"
},

Para ejecutar esta tarea personalizada, esta vez se añade el prefijo run, pues no es un script incorporado:

npm run lint

Para listar todas las tareas que se pueden ejecutar con npm podemos ejecutar el siguiente comando:

npm run

Convenciones de un proyecto que incluyen npm

Normalmente, todos los proyectos que utilicen npm como administrador de paquetes y dependencias, utilizan la siguiente convención cada vez que se descarga o clona un proyecto:

  1. npm install
  2. npm start

Y si desea ejecutar las pruebas npm test inclusive lo más recomendado es incluir estas instrucciones en el archivo README.md.

Utilizando las dependencias y scripts

Se edita el archivo index.js con el siguiente contenido:

const colors = require('colors/safe');

const args = process.argv.slice(2);
const [name = 'Friend'] = args;
const hour = new Date().getHours();

// Ask for hours range
if (hour >= 6 && hour < 12) {
  console.log(colors.yellow(`Good morning ${name}`));
} else if (hour >= 12 && hour < 18) {
  console.log(colors.green(`Good afternoon ${name}`));
} else if (hour >= 18 && hour < 23) {
  console.log(colors.cyan(`Good evening ${name}`));
} else {
  console.log(colors.blue(`Good night ${name}`));
}

Para ejecutar el programa se hace con el siguiente comando, esta vez para enviarle argumentos utilizamos un parámetro especial (--) para que tome los argumentos de línea de comando:

npm start -- Gustavo

Pre y pos scripts

Es posible tomar partida de la convención de los scripts de npm para ejecutar otros scripts antes y/o después de otro script por ejemplo:

"scripts": {
  "postinstall": "npm test",
  "prestart": "npm run lint",
  "start": "node index.js",
  "test": "echo \"Error: no test specified\"&& exit 1",
  "lint": "eslint **.js"
},

En el ejemplo anterior se especifica que luego de ejecutar el script npm install se ejecutarán automáticamente los comandos (no está limitado solo a ejecutar otros scripts) especificados en el script postinstall. Y antes de ejecutar el script npm start se ejecutarán automáticamente los comandos especificados en el script prestart. No importa el orden en que se coloquen en el archivo package.json dentro de la sección de scripts; lo importante es la nomenclatura.

Estandarizar argumentos de línea de comandos

Cada vez que ejecutamos una aplicación de Node.js estamos enviando al menos un argumento al ejecutable de Node.js (node), este argumento es el archivo de entrada de la aplicación (index.js).

node index Gustavo
node index Gustavo Morales
node index "Gustavo Morales"
node index name=Gustavo
node index -n "Gustavo Morales"

Como se puede observar, Node.js separa los argumentos por el espacio y el orden de almacenamiento es el mismo orden de entrada; es decir, que para el segundo ejemplo sería incorrecto enviar el nombre separado por espacio, pues, sería interpretado como 2 argumentos. La manera correcta sería como está el tercer ejemplo.

Una forma de crear argumentos nombrados puede ser el cuarto ejemplo, luego tocaría separar la llave (key) del valor (value) y, por último, está la manera tradicional como se utilizan los argumentos nombrados, pero como podemos observar sería bastante trabajo tener que interpretar lo que queda almacenado en process.argv, existen muchas librerías útiles para este caso, en el siguiente ejemplo se utilizará command-line-args, se instala como una dependencia del proyecto con el siguiente comando:

npm i command-line-args

Se agrega el siguiente contenido al archivo index.js:

const colors = require('colors/safe');
const commandLineArgs = require('command-line-args');

const params = [{ name: 'name', alias: 'n', type: String }];

const options = commandLineArgs(params);
const name = options.name || 'Friend';
const hour = new Date().getHours();

...

En la variable params se ha indicado que argumentos y de qué tipo se esperan para que la librería los procese y finalmente deje el resultado en la variable options que es un objeto de llave / valor con cada uno de los argumentos declarados.

Ejecutar nuevamente la aplicación con los siguientes argumentos:

npm start -- -n Gustavo

Finalmente, tendrá el mismo resultado.

Es muy importante comprobar que los argumentos sean válidos, pues en este ejercicio, y en los siguientes de este tipo, se asume que el usuario ingresa los argumentos en forma correcta. Para practicar y profundizar puede hacer sus propias comprobaciones.

Más información: