Qualche chiarimento sui moduli NodeJS

Un tempo quando nel nostro codice c’era bisogno di qualche funzione “non innovativa” semplicemente si cercava qua e là, si copiava il file/la libreria/il pezzo di codice nel nostro programma e si andava avanti. Spesso – per mancanza di tempo o per semplice pigrizia – nemmeno si studiava bene nemmeno cosa facesse quel codice altrui: era il Cargo Cult Programming.
Con il passare degli anni in ogni settore dello sviluppo software si è cercato di standardizzare e modularizzare codice e librerie in modo da rendere la programmazione più un gioco di incastri che non un lavoro da sartoria, e ora i moduli CommonJS sono un po’ dappertutto… e il Cargo Cult Programming se possibile è anche diventato più forte.

Moduli NodeJS: installazione

Con la nascita di NodeJS e il conseguente lancio del package manager npm il mondo Javascript ha subito una brusca accelerazione dal punto di vista della riusabilità e ora è tutto un’installazione di moduli, ma c’è chi non ha capito bene come funziona. Lo so perché anch’io all’inizio installavo moduli e librerie un po’ a caso, e ancora spesso ho dei dubbi sulla correttezza di quello che sto facendo.

Installazione locale o globale?

Tanto per cominciare ci sono “due modi” di installare un modulo presente su npm: localmente o globalmente. Mentre per installare un modulo localmente (ovvero nella directory corrente da cui si esegue il comando) bisogna scrivere “npm install nome_modulo“, per installare un modulo globalmente bisogna aggiungere l’apposito parametro, facendo diventare il comando “npm install --g nome_modulo

A differenza dell’installazione locale, che come già detto installa il modulo localmente e più precisamente nella directory node_modules/nome_modulo, l’installazione globale scarica il modulo all’interno di una directory di sistema presente sul PATH delle variabili di sistema.
Se non avete cambiato le impostazioni di default di Node, su Windows la directory di installazione dei moduli globali è C:\Users\nome_utente\AppData\Roaming\npm.
Il motivo per cui i moduli globali vengono installati dove sono accessibili da console è abbastanza evidente… sono installati con questo scopo. Non solo i moduli installati globalmente sono richiamabili da console, sono richiamabili solo da console. Infatti devono essere installati globalmente i moduli che servono come “strumenti” (Bower, Grunt, Cordova, …), al contrario delle “librerie” che invece devono essere installate localmente (Underscore, Express, …).

C’è poi un terzo caso, in cui parlare di installazione è improprio: il modulo creato da noi e non presente su npm.

L’utilizzo/inclusione del modulo, ovvero la require

La require non solo rende possibile utilizzare la libreria scelta all’interno del proprio script, ma ne effettua anche il caching in modo che – se all’interno dell’applicazione si richiede nuovamente la stessa libreria – Node non ha bisogno di andarla a leggere di nuovo su filesystem.
Includere un modulo all’interno del nostro script così da poterlo usare è semplice, ma non è così scontato capire dove Node va a cercare i file quando scriviamo require('nome_modulo').
Premessa: non serve specificare l’estensione del file (.js) perché Node cerca di default i file aggiungendovi quest’estensione.

Require con specifica del path

var customModule1 = require('./custom_module1');
var customModule2 = require('../../custom_module2');
var customModule3 = require('/home/custom_module3');

Forse il caso più semplice, Node importa il modulo specificato andandolo a cercare nel percorso assoluto specificato, o in quello relativo rispetto allo script in cui è stata chiamata la require.

Require con specifica del solo nome della libreria

var http = require("http");
var _ = require("underscore");

Qui la situazione si complica.
Per prima cosa Node cerca il modulo tra quelli “core”, cioè i moduli rilasciati di default con Node (es: http, fs, path, …). Se il modulo non è tra quelli preinstallati (es: underscore) Node inizia a ricercare il modulo all’interno della prima directory node_modules che riesce a trovare partendo dalla directory corrente (quella dello script) e risalendo la gerarchia fino ad arrivare alla directory root.
C’è da dire anche che il “modulo” può essere semplicemente un file javascript nome_modulo.js ma anche una directory nome_modulo contenente un index.js piuttosto che un nome_modulo.js e tante altre risorse, compresa un’eventuale directory node_modules in cui sono contenute tutte le dipendenze del modulo.
Lo so, è un po’ un casino e forse ho sintetizzato troppo, il punto è che capirete meglio andando a scartabellare la directory node_modules globale e provando a installare qualche modulo locale qua e là vedendo come e quando Node lancia l’errore “Cannot find module ‘nome_modulo'” se la require fallisce.

Scrittura di un semplice modulo: exports

Un piccolo esempio pokeristico, sperando che conosciate questi termini. Se non li conoscete va bene uguale ma mi dispiace per voi.

var fold = function(){
  console.log("fold");
};
module.exports = fold;

Il modulo “weak” è estremamente basilare, ed esporta una sola funzione.

var bet = function(){
  console.log("bet");
};

exports.bet = bet;
exports.raise = function(){
  console.log("raise");
};
exports.when = "When I'm strong and/or in position and/or I want to convince my opponents that I'm strong";

Il modulo “strong” utilizza l’exports un po’ di più, collegandovi non una ma due funzioni, e una stringa tanto per fare mucchio. La prima funzione – bet – viene dichiarata e definita in una variabile che poi viene esportata, a differenza della seconda – raise – che viene assegnata direttamente all’omonima proprietà dell’oggetto exports. Il risultato è esattamente lo stesso.

var weak = require('./weak');
var strong = require('./strong');

weak();
strong.bet();
strong.raise();

console.log(weak);
console.log(strong);

Qui possiamo vedere che il modulo “weak” nient’altro non è che la funzione esportata, che infatti può essere richiamata direttamente. Strong è invece un oggetto Javascript contenente tre proprietà: una stringa e due funzioni – bet e raise – che in quanto tali possono essere eseguite.

Dubbi amletici: exports o module.exports?

I più attenti avranno notato una differenza tra weak.js e strong.js… e non serve nemmeno essere troppo attenti considerato che c’è scritto sul titolo. Che differenza c’è tra la variabile “exports” e la “module.exports”?
In pratica nessuna, perché exports è semplicemente un riferimento a module.exports. Si tratta di zucchero sintattico che serve a velocizzare la scrittura del modulo e a evitare che per sbaglio rendiamo la nostra vita amara andando ad assegnare qualcosa a module.exports, rompendo così l’interfaccia tra il modulo e i suoi utilizzatori.

Conclusioni

Modulare è bello, modulare è “c’è un sacco di roba gratis e si trova tutta lì”, modulare è veloce. Però è importante capire bene quali sono le regole perché altrimenti i pezzi non si incastrano e bisogna fare come gli astronauti dell’Apollo 13 con i filtri per l’anidride carbonica.
Parlando di installazioni ho omesso che quando si installa un modulo bisognerebbe anche preoccuparsi di quale versione installare, perché la pappa pronta è buona ma qualcuno potrebbe aver fatto delle modifiche alla ricetta dall’ultima volta, e potremmo ritrovarci con un’intolleranza. E poi bisogna spiegare al cliente che siamo stati sfortunati.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.