Node.js

Els mòduls de Node.js

  

Una de les principals característiques de Node.js és que està dissenyat de manera modular: el seu funcionament s’organitza en mòduls o paquets que podem incloure en els nostres projectes segons ho necessitem.

Podem distingir tres tipus de mòduls en Node.js:

Gràcies a este sistema, en un projecte Node.js només carreguem el que realment necessitem, mantenint el codi més net, lleuger i mantenible.

En esta unitat aprendrem a treballar amb els tres tipus de mòduls i a utilitzar les ferramentes que ens oferix l’ecosistema de Node.js per a gestionar-los.

1. Utilitzar mòduls del nucli de Node

Com comentàvem, el propi entorn de Node.js incorpora una sèrie de mòduls natius que podem usar directament en les nostres aplicacions, sense necessitat d’instal·lar gens addicional. En la documentació oficial pots consultar la llista completa.

Per a usar un mòdul (nadiu o de tercers), podem importar-ho en el nostre codi de dos maneres:

1.1 Exemple amb CommonJS (require)

Crearem un arxiu anomenat llistat.js en el nostre projecte de “ProjectesNode/Proves/ProbesSimples”. El programa llistarà tots els arxius i carpetes d’una ruta usant el mòdul fs (file system).

En Node.js és habitual que una mateixa funció es puga utilitzar de formes distintes:

A continuació veiem el primer cas, la versió síncrona:

const ruta = '/Users/may';
const fs = require('fs');
fs.readdirSync(ruta).forEach(fitxer => {
 console.log(fitxer);
});

Si executem este programa obtindrem el llistat de la carpeta indicada. Abans d’executar-ho, recorda canviar el valor de la constant ruta en el codi pel de una carpeta vàlida en el teu sistema.

Observa que en el codi s’han declarat dos constants (const), en lloc de variables (var/let), ja que no necessitem manipular o modificar el codi que s’emmagatzemarà en elles una vegada carregades (no canviarem la ruta, ni el contingut del mòdul fs en el nostre codi).

Vegem l’exemple equivalent a l’anterior usant la versió asíncrona (callback):

const ruta = '/Users/may';
const fs = require('fs');

fs.readdir(ruta, (err, arxius) => {
    if (err) {
        console.error("Error en llegir la carpeta:", err);
    return;
}
 
arxius.forEach(fitxer => {
 console.log(fitxer);
 });
});

console.log("El programa continua executant-se...");

La funció readdir() rep dos arguments: en primer lloc, la ruta, que indica la carpeta que volem llegir, i en segon lloc, un callback, que és la funció que s’executarà quan Node.js acabe de processar la lectura. Este callback rep al seu torn dos paràmetres: err, que contindrà un error en cas que l’operació falle (per exemple, si la ruta no existix), i arxius, que serà un array amb els noms dels arxius i subcarpetas de la ruta indicada quan l’operació es complete correctament.

En este cas, primer es mostra el missatge “El programa continua executant-se…“. Després, quan readdir() acaba de processar la carpeta, s’executa la funció que imprimeix el llistat d’arxius.

Finalment, vegem l’equivalent utilitzant la versió moderna (promeses + async/await):

const ruta = '/Users/may';
const fs = require('fs');

async function llistar() {
    try {
        const arxius = await fs.promises.readdir(ruta);
        console.log(arxius);
    } catch (err) {
    console.error(*err);
 }
}

llistar();

En este cas, la funció llistar() està definida com a asíncrona gràcies a la paraula reservada async. Això permet utilitzar dins d’ella la instrucció await, que deté l’execució de la funció fins que la promesa es resol.

A més, observa que en este cas estem usant fs.promises.readdir() en lloc de fs.readdir(). Això es deu al fet que el mòdul fs oferix diverses maneres de treballar: la versió síncrona (fs.readdirSync), la versió asíncrona amb callback (fs.readdir) i, dins de la propietat especial promises, la versió que retorna una promesa (fs.promises.readdir). D’esta manera Node.js agrupa en un mateix mòdul (fs) totes les alternatives disponibles, i podem triar la que més ens convinga segons el tipus d’aplicació que estiguem desenvolupant.

A diferència de les versions anteriors, esta implementació no bloqueja el programa com ocorre amb readdirSync, tampoc requerix l’ús de callbacks com en el cas de readdir, i a més oferix un codi molt més llegible i fàcil de mantindre gràcies a la sintaxi moderna de async/await.

1.2 Exemple amb ES Modules (import)

En projectes moderns és habitual usar ES Modules, que ens permet importar mòduls amb la instrucció import en lloc de require.

Per a poder usar esta sintaxi, hem de configurar el nostre projecte d’una d’estes dos maneres:

Vegem els mateixos exemples d’abans, però escrits amb import.

Versió síncrona (readdirSync)

import { readdirSync } from 'fs';

const ruta = '/Users/may';
const arxius = readdirSync(ruta);

arxius.forEach(fitxer => {
    console.log(fitxer);
});

Ací readdirSync s’importa directament des del mòdul fs, i el programa queda bloquejat fins que obté el llistat d’arxius.

Versió asíncrona amb callback (readdir)

import { readdir } from 'fs';

const ruta = '/Users/may';

readdir(ruta, (err, arxius) => {
    if (err) {
        console.error("Error en llegir la carpeta:", err);
        return;
    }
    arxius.forEach(fitxer => {
        console.log(fitxer);
    });
});

console.log("El programa continua executant-se...");

Versió moderna amb promeses (fs/promises)

import { readdir } from 'fs/promises';

const ruta = '/Users/may';

const llistarArxius = async () => {
    try {
        const arxius = await readdir(ruta);
        console.log(arxius);
    } catch (err) {
        console.error("Error en llegir la carpeta:", err);
    }
};

llistarArxius();

Observa que ací, en usar ÉS Modules, importem directament la funció readdir des de fs/promises. A diferència de CommonJS, on hem d’accedir mitjançant fs.promises.readdir, ací la tenim disponible amb una sola instrucció, la qual cosa fa el codi més net i directe.

Esta és la forma actual i recomanada de fer-ho en projectes Node.js moderns, ja que aprofita l’estàndard de ES Modules i la potència de les promeses amb async/await.

Exercici 1:

Crea una carpeta anomenada “SalutacioUsuari” en el teu espai de treball, en la carpeta de “Exercicis”. Afig un arxiu anomenat salutacio.js. Dona una ullada en la API de Node al mòdul us, i en concret al mètode userInfo(). Utilitza-ho per a fer un programa que salude a l’usuari que ha accedit al sistema operatiu. Per exemple, si l’usuari és “May”, hauria de dir “Hola May”. Executa el programa en el terminal per a comprovar el seu correcte funcionament.

L’exercici pot resoldre’s de diferents maneres: primer utilitzant CommonJS amb require, després amb ES Modules usant import, que és la forma moderna i recomanada en els projectes actuals de Node.js.

Opcionalment, fes que el programa perquè mostre no sols el nom de l’usuari, sinó també el seu directori personal (homedir).

NOTA: el mètode console.log admet un nombre indefinit de paràmetres, i els mostra concatenats un darrere l’altre separats per un espai. Estes instruccions són equivalents i produïxen el mateix resultat:

console.log("Hola " + nom);
console.log("Hola", nom);
console.log(`Hola ${nom}`);

2. Incloure els nostres propis mòduls

A més d’utilitzar mòduls natius, en *Node.js també podem crear els nostres propis mòduls. Això ens permet dividir una aplicació en diversos arxius, de manera que el codi estiga més ben organitzat, siga més fàcil de mantindre i puguem reutilitzar funcions en diferents llocs.

Per a entendre-ho millor, crearem un xicotet projecte anomenat “ProvesRequire” en la nostra carpeta de “Proves”. Començarem amb dos arxius:

2.1 CommonJS

2.1.1 Importar mòduls amb require

Per a incloure un arxiu el nostre dins d’un altre en CommonJS, usem la instrucció require.

const utilitats = require('./utilitats.js');

El prefix ./ indica que estem important un arxiu local que està en el mateix directori que principal.js. Si no ho posem, Node.js buscarà un mòdul natiu (com fso os) o un mòdul de tercers instal·lat en node_modules (com veurem més avant).

És possible també suprimir l’extensió de l’arxiu en el cas d’arxius JavaScript, per la qual cosa la instrucció anterior seria equivalent a esta altra (Node.js sobreentén que es tracta d’un arxiu JavaScript):

const utilitats = require('./utilitats');

2.1.2 Exportar contingut amb module.exports

El contingut de l’arxiu utilitats.js ha de tindre una estructura determinada. Si, per exemple, l’arxiu té este contingut:

console.log('Entrant en utilitats.js');

Llavors el mer fet d’incloure’l amb require mostrarà per pantalla el missatge “Entrant en utilitats.js” en executar l’aplicació. És a dir, qualsevol instrucció directa que continga l’arxiu inclòs s’executa en incloure’l. El normal, no obstant això, és que este arxiu no continga instruccions directes, sinó una sèrie de propietats i mètodes que puguen ser accessibles des de l’arxiu que l’inclou.

Suposem que l’arxiu utilitats.js té unes funcions matemàtiques per a sumar i restar dos números i retornar el resultat. Una cosa així:

let sumar = (num1, num2) => num1 + num2;
let restar = (num1, num2) => num1  num2;

El lògic seria pensar que, en incloure este arxiu amb require des de principal.js, puguem accedir a les funcions sumar i restar que hem definit… però no és així. El contingut de utilitats.js no és accessible automàticament des de fora.

Per a poder fer els mètodes o propietats d’un arxiu visibles des d’un altre que l’incloga, hem d’afegir-los com a elements de l’objecte module.exports. Així, les dos funcions anteriors s’haurien de definir d’esta manera:

module.exports.sumar = (num1, num2) => num1 + num2;
module.exports.restar = (num1, num2) => num1  num2;

És habitual definir un objecte en module.exports, i afegir dins tot el que vulguem exportar. D’esta manera, tindrem d’una banda el codi del nostre mòdul, i per un altre la part que volem exportar. El mòdul quedaria així, en este cas:

let sumar = (num1, num2) => num1 + num2;
let restar = (num1, num2) => num1  num2;

module.exports = {
    sumar: sumar,
    restar: restar
};

En qualsevol cas, ara sí que podem utilitzar estes funcions des del programa principal.js:

const utilitats = require('./utilitats');
console.log(utilitats.sumar(3, 2));

L’objecte module.exports admet tant funcions com atributs o propietats. Per exemple, podríem definir una propietat per a emmagatzemar el valor del número “pi”:

module.exports = {
    pi: 3.1416,
    sumar: sumar,
    restar: restar
};

… i accedir a ella des del programa principal:

console.log(utilitats.pi);

2.2 ES Modules

2.2.1 Importar mòduls amb import

En projectes moderns, Node.js permet usar ES Modules. En este cas emprem import en lloc de require:

import * as utilitats from './utilitats.js';

Igual que en CommonJS, “./” indica que l’arxiu és local. No obstant això, en ES Modules, l’extensió .js no es pot ometre, ha d’escriure’s sempre.

2.2.2 Exportar contingut amb export

En ES Modules tenim dos opcions, usar exportacions amb nom o una exportació per defecte.

a) Exportacions amb nom

En l’arxiu utilitats.js tindrem:

// utilitats.js
export const sumar = (a, b) => a + b;
export const restar = (a, b) => a - b;
export const pi = 3.1416;

I en principal.js:

import { sumar, restar, pi } from './utilitats.js';

console.log(sumar(3, 2)); // 5
console.log(restar(5, 1)); // 4
console.log(pi); // 3.1416

b) Exportacions per defecte

En este cas, exportem un únic objecte que conté totes les funcions i propietats que volem usar des de fora.

Així, en l’arxiu utilitats.js tindrem:

// utilitats.js
const sumar = (a, b) => a + b;
const restar = (a, b) => a - b;
const pi = 3.1416;

export default {
    sumar,
    restar,
    pi
};

I en principal.js:

// principal.js
import utilitats from './utilitats.js';

console.log(utilitats.sumar(3, 2)); // 5
console.log(utilitats.restar(5, 1)); // 4
console.log(utilitats.pi); // 3.1416

Amb export default no usem claus { } en importar, perquè tot arriba agrupat en un únic objecte.

2.3 Agrupar mòduls

2.3.1 Incloure carpetes senceres

En el cas que el nostre projecte continga diversos mòduls relacionats, és recomanable organitzar-los en carpetes seguint una nomenclatura específica. Els passos a seguir són:

Des del programa principal (o un altre lloc que necessite incloure la carpeta sencera), incloure el nom de la carpeta. Automàticament es localitzarà i inclourà l’arxiu index.js, amb tots els mòduls que este haja inclòs dins.

Vegem un exemple: anem a la nostra carpeta “ProvesRequire” creada en l’exemple anterior, i crea una carpeta anomenada “idiomes”. Dins, crea estos tres arxius, amb el següent contingut:

Arxiu es.js*

module.exports = {
    salutacio : "Hola"
};

Arxiu en.js*

module.exports = {
    salutacio : "Hello"
};

Arxiu index.js

const en = require('./en');
const es = require('./es');

module.exports = {
    es : es,
    en : en
};

Ara, en la carpeta arrel de “ProvesRequire” crea un arxiu anomenat salutacio_idioma.js, amb este contingut:

const idiomes = require('./idiomes');

console.log("English:", idiomes.en.salutacio);
console.log("Español:", idiomas.es.salutacio);

Com pots veure, des de l’arxiu principal només hem inclòs la carpeta, i amb això automàticament incloem l’arxiu index.js que, al seu torn, inclou als altres. Una vegada fet això, i tal com hem exportat les propietats en index.js, podem accedir a la salutació en cada idioma.

2.3.2 Incloure arxius JSON

Node.js permet incloure directament arxius JSON com si foren mòduls. Els arxius JSON són especialment útils, com veurem, per a definir una certa configuració bàsica (no encriptada) en les aplicacions, a més de per a enviar informació entre parts de l’aplicació (el que veurem també més avant).

Per exemple, i seguint amb l’exemple anterior, podríem traure a un arxiu JSON el text de la salutació en cada idioma. Afegim un arxiu anomenat salutacions.json dins de nostra subcarpeta “idiomes”:

{
    "es" : "Hola",
    "en" : "Hello"
}

Després, podem modificar el contingut dels arxius es.js i en.js perquè no posen literalment el text, sinó que l’agafen de l’arxiu JSON, incloent-lo. Ens quedarien així:

Arxiu es.js:

const textos = require('./salutacions.json');

module.exports = {
    salutacio : textos.es
};

Arxiu en.js:

const textos = require('./salutacions.json');

module.exports = {
 salutacio : textos.en
};

La manera d’accedir als textos des del programa principal no canvia, però ara la informació està centralitzada en un únic arxiu JSON en lloc d’estar repartida en diversos arxius JavaScript. Això facilita el manteniment: si hi ha una errata o volem actualitzar un text, n’hi ha prou amb modificar el JSON una sola vegada. A més, evitem el problema de les magic strings (cadenes escrites “a mà” en el codi, que són més difícils de localitzar i de reutilitzar).

NOTA: Els arxius JSON només poden contindre dades (números, cadenes, booleans, arrays o objectes), però mai funcions ni lògica de programació. Per això s’utilitzen principalment per a configuració i per a emmagatzemar o intercanviar informació, però no per a definir comportament.

2.4. Detalls avançats

Per a finalitzar amb este subapartat d’inclusió de mòduls locals de la nostra aplicació (o divisió de la nostra aplicació en diversos fitxers font, segons com vulguem veure’l), convé tindre en compte un parell de matisos addicionals.

2.4.1. Rutes relatives i __dirname

Fins ara, quan hem emprat la instrucció require per a incloure un mòdul del nostre projecte, hem partit de la carpeta actual. Per exemple:

const utilitats = require('./utilitats');

Este codi funcionarà sempre que executem l’aplicació Node.js des de la seua mateixa carpeta:

node principal.js

Però si estem en una altra carpeta i executem l’aplicació des d’allí…

node /Users/May/Projectes/ProvesRequire/principal.js

… llavors require farà referència a la carpeta des d’on estem executant, i no trobarà l’arxiu “utilitats.js”, en este cas. Per a evitar este problema, podem emprar la propietat __dirname, que fa referència a la carpeta del mòdul que s’està executant (principal.js, en este cas):

const utilitats = require(__dirname + '/utilitats');

2.4.2. Diferència entre exports i module.exports en CommonJS

En Node.js, la qual cosa realment es retorna en usar require és module.exports. Eixe és l’objecte “oficial” que es retorna quan importem un mòdul.

No obstant això, en molts exemples d’Internet veuràs també exports. Això pot generar confusió, perquè exports és simplement una drecera (àlies) que apunta a module.exports.

A l’inici de cada arxiu, Node.js fa internament una cosa així:

let module = { exports: {} };
let exports = module.exports;

D’esta manera, tant exports com module.exports apunten inicialment al mateix objecte. Mentres no es reassigne, afegir propietats a exports és equivalent a afegir-les a module.exports.

El següent exemple mostra el ús correcte de exports:

// utilitats.js
exports.sumar = (a, b) => a + b;
exports.restar = (a, b) => a - b;

// principal.js
const util = require('./utilitats');
console.log(util.sumar(3, 2)); // 5

Funciona perquè en fer exports.sumar = ..., en realitat estem afegint propietats a module.exports, que és el que es retorna.

En canvi, si reassignem exports obtindrem un error d’execució perquè en fer exports = { ... }, trenquem la connexió entre exports i module.exports i, per tant, l’objecte importat estarà buit i no tindrà la funció.

Este altre exemple mostra un ús incorrecte de exports:

// utilitats.js
exports = {
    sumar: (a, b) => a + b
};

// principal.js
const util = require('./utilitats');
console.log(util.sumar(3, 2)); // Error: util.sumar is not a function

Ara exports apunta a un nou objecte, però require continua retornant el vell module.exports, que està buit.

La moralitat de tot això és que, en principi, exports i module.exports servixen per al mateix sempre que no les reassignem. Per a evitar errors, el recomanable és usar sempre module.exports.

Exercici 2:

Per a realitzar aquest exercici, ens basarem en l’exercici “Promeses” de la sessió anterior, on gestionàvem les persones d’un vector mitjançant uns mètodes que inserien o esborraven dades d’aquest, i retornaven una promesa amb el resultat.

Còpia aqueixa carpeta i canvia-la de nom a “Modularitzar”. El que farem en aquest exercici és dividir el codi en dos arxius:

Executa el programa per a verificar que les dependències amb el mòdul s’han establit correctament, i les dades s’insereixen i esborren del vector de manera satisfactòria.

3. Mòduls de tercers. El gestor npm

Existeixen nombrosos mòduls fets per tercers que poden ser afegits i utilitzats en les nostres aplicacions, com per exemple el mòdul mongoose per a accés a bases de dades MongoDB, o el mòdul express per a incorporar el framework Express.js al nostre projecte, i desenvolupar aplicacions web amb ell, com veurem en sessions posteriors. Aquests mòduls de tercers s’instal·len a través del gestor npm.

npm (Node Package Manager) és un gestor de paquets per a JavaScript, i s’instal·la automàticament en instal·lar Node.js. Podem comprovar que ho tenim instal·lat, i quina versió concreta tenim, mitjançant la comanda:

npm -v

encara que també ens servirà la comanda npm --version.

Inicialment, npm es va pensar com un gestor per a poder instal·lar mòduls en les aplicacions Node, però s’ha convertit en molt més que això, i a través d’ell podem també descarregar i instal·lar en les nostres aplicacions altres mòduls o llibreries que no tenen a veure amb Node, com per exemple Bootstrap o jQuery. Així que actualment és un enorme ecosistema de llibreries open-source, que ens permet centrar-nos en les necessitats específiques de la nostra aplicació, sense haver de “reinventar la roda” cada vegada que necessitem una funcionalitat que ja han fet uns altres abans. El registre de llibreries o mòduls gestionat per NPM està en la web npmjs.com.

Podem consultar informació sobre alguna llibreria en particular, consultar estadístiques de quanta gent li la descarrega, i fins i tot proporcionar nosaltres les nostres pròpies llibreries si volem. Per exemple, aquesta és la fitxa de la llibreria express, que emprarem més endavant:

L’opció més habitual d’ús de npm és instal·lar mòduls o paquets en un projecte concret, de manera que cada projecte tinga els seus propis mòduls. No obstant això, en algunes ocasions també ens pot interessar (i és possible) instal·lar algun mòdul de manera global al sistema. Veurem com fer aquestes dues operacions.

3.1. Instal·lar mòduls locals a un projecte

En aquest apartat veurem com instal·lar mòduls de tercers de manera local a un projecte concret. Farem proves dins d’un projecte anomenat “ProvaNPM” en la nostra carpeta de “ProjectesNode/Proves”, la carpeta de les quals podem crear ja.

3.1.1. L’arxiu “package.json”

La configuració bàsica dels projectes Node s’emmagatzema en un arxiu JSON anomenat package.json. Aquest arxiu es pot crear directament des de línia de comandes, utilitzant una d’aquestes dues opcions (hem d’executar-la en la carpeta del nostre projecte Node):

{
    "name": "ProvaNPM",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "tire \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC"
}

Al final de tot el procés, tindrem l’arxiu en la carpeta del nostre projecte. En ell afegirem després (de manera manual o automàtica) els mòduls que necessitem, i les versions d’aquests, com explicarem a continuació.

NOTA: en generar l’arxiu package.json, podem observar que el nom de programa principal (entry point) que s’assigna per defecte a l’aplicació Node és index.js. És habitual que el fitxer principal d’una aplicació Node es diga així, o també app.js, com veurem en posteriors exemples, encara que no és obligatori cridar-los així.

3.1.2. Afegir mòduls al projecte i utilitzar-los

Per a instal·lar un mòdul extern en un projecte determinat, hem d’obrir un terminal i situar-nos en la carpeta del projecte. Després, escrivim la següent comanda:

npm install nom_modul

on nom_modul serà el nom del mòdul que vulguem instal·lar. Podem instal·lar també una versió específica del mòdul afegint-lo com a sufix amb una arrova al nom del mòdul. Per exemple:

npm install nom_modul@1.1.0

Provarem amb un mòdul senzill i molt utilitzat (té milions de descàrregues setmanalment), ja que conté una sèrie d’utilitats per a facilitar-nos el desenvolupament dels nostres projectes. Es tracta del mòdul lodash, que podeu consultar en la web citada anteriorment (ací). Per a instal·lar-ho, escrivim el següent:

npm install lodash

Algunes puntualitzacions abans de seguir:

{
    "name": "provanpm",
    ...
    "dependencies": {
        "lodash": "^4.17.15"
    }
}

Per a poder utilitzar el nou mòdul, procedirem de la mateixa forma que per a utilitzar mòduls predefinits de Node: emprarem la instrucció require amb el nom original del mòdul. Per exemple, editarem un arxiu index.js en la carpeta “ProvaNPM” que venim editant en aquests últims passos, i afegim aquest codi que carrega el mòdul “lodash” i l’utilitza per a eliminar un element d’un vector:

const lodash = require('lodash');
console.log(lodash.difference([1, 2, 3], [1]));

NOTA: si busqueu documentació o exemples d’ús d’aquesta llibreria en Internet, és habitual que el nom de variable o constant on es carrega (en la línia require) siga un simple símbol de subratllat (això és el que significa low dash en anglés), amb el que l’exemple anterior quedaria així:

const _ = require('lodash');
console.log(_.difference([1, 2, 3], [1]));

Si executem aquest exemple des del terminal, obtindrem el següent:

node index.js
[ 2, 3 ]

Exercici 3:

Crea una carpeta anomenada “Lodash” en el teu espai de treball, en la carpeta de “Exercicis”. Dins, crea un arxiu package.json utilitzant la comanda npm init vist abans. Deixa els valors per defecte que et planteja l’assistent, i posa el teu nom com a autor.

Després, instal·la el paquet lodash com s’ha explicat en un exemple anterior, i consulta la seua documentació ací, per a fer un programa en un arxiu index.js que, donat un vector de noms de persones, els mostre per pantalla separats per comes. Hauràs de definir a mà l’array de noms dins del codi. Per exemple, per a l’array ["Nacho", "Ana", "Mario", "Laura"], l’eixida del programa haurà de ser:

Nacho,Ana,Mario,Laura

NOTA: revisa el mètode join dins de la documentació de “lodash”, pot ser-te molt útil per a aquest exercici.

3.1.3. Desinstal·lar un mòdul

Per a desinstal·lar un mòdul (i eliminar-lo de l’arxiu package.json, si existeix), escrivim la comanda següent:

npm uninstall nom_modul

3.1.4. Ordre d’inclusió dels mòduls

Hem vist com incloure en una aplicació Node tres tipus de mòduls:

Encara que no hi ha una norma obligatòria a seguir sobre aquest tema, sí que és habitual que, quan la nostra aplicació necessita incloure mòduls de diversos tipus (predefinits de Node, de tercers i arxius propis), es faça amb una estructura determinada.

Bàsicament, el que es fa és incloure primer els mòduls de Node i els de tercers, i després (separats per un espai del bloc anterior), els arxius propis del nostre projecte. Per exemple:

const fs = require('fs');
const _ = require('lodash');

const utilitats = require('./utilitats');

3.1.5. Algunes consideracions sobre mòduls de tercers

Hem vist els passos elementals per a poder instal·lar, utilitzar, i desinstal·lar (si és necessari) mòduls de tercers localment en les nostres aplicacions. Però hi ha alguns aspectes referents a aquests mòduls, i la forma en què s’instal·len i distribueixen, que has de tindre en compte.

Gestió de versions

Des que comencem a desenvolupar una aplicació fins que la finalitzem, o en manteniments posteriors, és possible que els mòduls que la componen s’hagen actualitzat. Algunes d’aqueixes noves versions poden no ser compatibles amb el que en el seu moment vam fer, o al contrari, hem actualitzat l’aplicació i ja no ens serveixen versions massa antigues d’uns certs mòduls.

Per a poder determinar quines versions o rangs de versions són compatibles amb el nostre projecte, podem utilitzar la mateixa secció de “dependencies” de l’arxiu package.json, amb una nomenclatura determinada. Vegem alguns exemples utilitzant el paquet “lodash” del cas anterior:

Existeixen altres modificadors també, però amb aquests podem fer-nos una idea del que podem controlar. Una vegada hàgem especificat els rangs de versions compatibles de cada mòdul, amb la següent comanda actualitzem els paquets que es vegen afectats per aquestes restriccions, deixant per a cadascun una versió dins del rang compatible indicat:

npm update --save

Afegir mòduls a mà en “package.json”

També podríem afegir a mà en l’arxiu “package.json” mòduls que necessitem instal·lar. Per exemple, així afegiríem a l’exemple anterior l’última versió del mòdul “express”:

{
    ...
    "dependencies": {
        "lodash": "^4.17.4",
        "express": "*"
    }
}

Per a fer efectiva la instal·lació dels mòduls d’aquest arxiu, una vegada afegits, hem d’executar aquesta comanda en el terminal:

npm install

Automàticament s’afegiran els mòduls que falten en la carpeta “node_modules” del projecte.

Compartir el nostre projecte

Si decidim pujar el nostre projecte a algun repositori en Internet com Github o similars, o deixar que algú li ho descarregue per a modificar-ho després, no és bona idea pujar la carpeta “node_modules”, ja que conté codi font fet per terceres persones, provat en entorns reals i fiable, que no hauria de poder-se modificar a la lleugera. A més, la forma en què s’estructura la carpeta “node_modules” depén de la versió de npm que cadascun tinguem instal·lada, i és possible que ocupe massa. De fet, els propis mòduls que descarreguem poden tindre dependències amb altres mòduls, que al seu torn es descarregaran en una subcarpeta interna.

Per tant, el recomanable és no compartir aquesta carpeta (no pujar-la al repositori, o no deixar-la a terceres persones), i no és cap problema fer això, ja que gràcies a l’arxiu package.json sempre podem (devem) executar la comanda:

npm install 

i descarregar totes les dependències que en ell estan reflectides. Dit d’una altra forma, l’arxiu package.json conté un resum de tan extern com el nostre projecte necessita, i que no és recomanable facilitar amb aquest.

3.2. Instal·lar mòduls globals al sistema

Per a una certa mena de mòduls, especialment aquells que s’executen des de terminal com Grunt (un gestor i automatizador de tasques JavaScript) o JSHint (un comprovador de sintaxi JavaScript), pot ser interessant instal·lar-los de manera global, per a poder-los usar dins de qualsevol projecte.

La manera de fer això és similar a la instal·lació d’un mòdul en un projecte concret, afegint algun paràmetre addicional, i amb la diferència que, en aquest cas, no és necessari un arxiu “package.json” per a gestionar els mòduls i dependències, ja que no són mòduls d’un projecte, sinó del sistema. La sintaxi general de la comanda és:

npm install -g nom_modul

on el flag -g fa referència al fet que es vol fer una instal·lació global.

És important, a més, tindre present que qualsevol mòdul instal·lat de manera global en el sistema no podrà importar-se amb require en una aplicació concreta (per a fer-ho hauríem d’instal·lar-lo també de manera local a aquesta aplicació).

3.2.1. Exemple: nodemon

Vegem com funciona la instal·lació de mòduls a nivell global amb un realment útil: el mòdul nodemon. Aquest mòdul funciona a través del terminal, i ens serveix per a monitorar l’execució d’una aplicació Node, de manera que, davant qualsevol canvi en aquesta, automàticament la reinicia i torna a executar-la per nosaltres, evitant-nos haver d’escriure la comanda node en el terminal de nou. Podeu consultar informació sobre nodemon ací.

Per a instal·lar nodemon de manera global escrivim la següent comanda (amb permisos d’administrador):

npm install -g nodemon

En instal·lar-ho de manera global, s’afegirà la comanda nodemon en la mateixa carpeta on resideixen les comandes node o npm. Per a utilitzar-ho, n’hi ha prou amb col·locar-nos en la carpeta del projecte que vulguem provar, i emprar aquesta comanda en lloc de node per a llançar l’aplicació:

nodemon index.js

Automàticament apareixeran diversos missatges d’informació en pantalla i el resultat d’executar el nostre programa. Davant cada canvi que fem, es reiniciarà aquest procés tornant a executar-se el programa.

Per a finalitzar l’execució de nodemon (i, per tant, de l’aplicació que estem monitorant), n’hi ha prou amb prémer Control+C en el terminal.

NOTA: si utilitzem el terminal powershell de Visual Studio Code podem tindre problemes per a executar Nodemon. En aquest cas hem de configurar el terminal perquè siga de tipus Command Prompt

3.2.2. Desinstal·lar mòduls globals

De la mateixa manera, per a desinstal·lar un mòdul que s’ha instal·lat de manera global, utilitzarem la comanda:

npm uninstall -g nom_module

Exercici 4:

Crea una carpeta anomenada “Moment” en el teu espai de treball, en la carpeta “Exercicis”. Dins, crea un arxiu package.json amb la corresponent comanda npm init vist abans. Deixa els valors per defecte que et planteja l’assistent, i posa el teu nom com a autor.

Instal·la el paquet moment, una llibreria per a gestió de dates i temps la documentació dels quals es pot consultar ací. Defineix un programa principal en un arxiu index.js que incloga aquesta llibreria:

const moment = require('moment');

Una vegada inclosa, fes el següent:

let ara = moment();
let abans = moment("07/10/2015", "DD/MM/YYYY");
console.log(moment.duration(dataPosterior.diff(dataAnterior)).years());
if (dataAnterior.isBefore(dataPosterior))...
if (dataPosterior.isAfter(dataAnterior))...

Si no ho has fet encara, instal·la el mòdul nodemon de manera global al sistema, com s’ha explicat en aquesta sessió. Executa aquesta aplicació amb aquest mòdul, i comprova que totes les dades que es mostren per consola són els esperats. Després, prova de canviar alguna data (la passada i/o la futura), i comprova com s’executa de nou automàticament i mostra els nous resultats.