Articolo originale: https://www.freecodecamp.org/news/requiring-modules-in-node-js-everything-you-need-to-know-e7fbd119be8/

Aggiornamento: questo articolo fa ora parte del mio libro "Node.js Beyond The Basics".

Leggi la versione aggiornata di questo contenuto e altro su Node su jscomplete.com/node-beyond-basics .

Node utilizza due moduli principali per la gestione delle dipendenze dei moduli:

  • Il modulorequire, che è disponibile nell'ambito globale, quindi non è necessario richiede require('require').
  • Il modulo module, che è anche disponibile nell'ambito globale, e non è necessario richiedere require('module').

Puoi pensare al modulo require come al comando e al modulo module come al raccoglitore di tutti i moduli richiesti.

Richiedere un modulo in Node non è un concetto così complicato.

const config = require('/path/to/file');

L'oggetto principale esportato dal modulo require è una funzione (come nell'esempio precedente). Quando richiama quella funzione require() con un percorso di file locale come unico argomento della funzione, Node esegue la seguente sequenza di passaggi:

  • Risoluzione : per trovare il percorso assoluto del file.
  • Caricamento : per determinare il tipo di contenuto del file.
  • Wrapping : per assegnare al file il suo ambito privato. Questo è ciò che rende gli oggetti require e module locali in ogni file di cui abbiamo bisogno.
  • Valutazione : questo è ciò che la VM alla fine fa con il codice caricato.
  • Memorizzazione nella cache : in modo che quando richiediamo nuovamente questo file, non ripercorriamo tutti i passaggi un'altra volta.

In questo articolo, cercherò di spiegare con esempi, queste diverse fasi e come influiscono sul modo in cui scriviamo i moduli in Node.

Fammi prima creare una directory per ospitare tutti gli esempi usando il mio terminale:

mkdir ~/learn-node && cd ~/learn-node

Tutti i comandi nel resto di questo articolo verranno eseguiti dall'interno di ~/learn-node.

Risoluzione di un percorso locale

Lascia che ti presenti l'oggetto module. Puoi verificarlo in una semplice sessione REPL:

~/learn-node $ node
> module
Module {
  id: '<repl>',
  exports: {},
  parent: undefined,
  filename: null,
  loaded: false,
  children: [],
  paths: [ ... ] }

Ogni oggetto modulo ottiene una proprietà id per identificarlo. Questo id è solitamente il percorso completo del file, ma in una sessione REPL è semplicemente<repl>.

I moduli del nodo hanno una relazione uno-a-uno con i file sul file system. Richiediamo un modulo caricando il contenuto di un file in memoria.

Tuttavia, poiché Node permette molti modi di richiedere un file (ad esempio, con un percorso relativo o un percorso preconfigurato), prima di poter caricare il contenuto di un file nella memoria, dobbiamo trovare la posizione assoluta di quel file.

Quando richiediamo un modulo 'find-me', senza specificare un percorso:

require('find-me');

Il nodo cercherà find-me.js in tutti i percorsi specificati da module.paths— in ordine.

~/learn-node $ node
> module.paths
[ '/Users/samer/learn-node/repl/node_modules',
  '/Users/samer/learn-node/node_modules',
  '/Users/samer/node_modules',
  '/Users/node_modules',
  '/node_modules',
  '/Users/samer/.node_modules',
  '/Users/samer/.node_libraries',
  '/usr/local/Cellar/node/7.7.1/lib/node' ]

L'elenco dei percorsi è fondamentalmente un elenco di directory di node_modules, dalla directory corrente alla directory principale. Include anche alcune directory legacy il cui utilizzo non è raccomandato.

Se Node non riesce a trovare find-me.js in nessuno di questi percorsi, genererà un "cannot find module error".

~/learn-node $ node
> require('find-me')
Error: Cannot find module 'find-me'
    at Function.Module._resolveFilename (module.js:470:15)
    at Function.Module._load (module.js:418:25)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at repl:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:23:33)
    at REPLServer.defaultEval (repl.js:336:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.onLine (repl.js:533:10)

Se ora crei una directory locale node_modules e inserisci un file find-me.js lì, la riga require('find-me') la troverà.

~/learn-node $ mkdir node_modules 

~/learn-node $ echo "console.log('Non mi sono perso');" > node_modules/find-me.js

~/learn-node $ node
> require('find-me');
Non mi sono perso
{}
>

Se esistesse un altro file find-me.js in uno qualsiasi degli altri percorsi, ad esempio, se avessimo una directory node_modules nella directory home e avessimo un diverso file find-me.js in quella cartella:

$ mkdir ~/node_modules
$ echo "console.log('Io sono la radice di tutti i problemi');" > ~/node_modules/find-me.js

Quando eseguiamo require('find-me') nella directory learn-node, che ha il suo node_modules/find-me.js, il file find-me.js nella directory home non verrà caricato affatto:

~/learn-node $ node
> require('find-me')
Non mi sono perso
{}
>

Se rimuoviamo la directory locale node_modules in ~/learn-node e proviamo a richiede find-me ancora una volta, verrà utilizzato il file nella directory home node_modules :

~/learn-node $ rm -r node_modules/
~/learn-node $ node
> require('find-me')
Io sono la radice di tutti i problemi
{}
>

Richiedere una cartella

I moduli possono non essere file. Possiamo anche creare una cartella find-me sotto node_modules e inserire lì un file index.js. La stessa riga require('find-me') utilizzerà il file index.js di quella cartella:

~/learn-node $ mkdir -p node_modules/find-me

~/learn-node $ echo "console.log('Ritrovato.');" > node_modules/find-me/index.js

~/learn-node $ node
> require('find-me');
Ritrovato.
{}
>

Nota come ha ancora ignorato il percorso della home directory node_modules poiché ne abbiamo una locale.

Un file index.js verrà utilizzato per impostazione predefinita quando richiediamo una cartella, ma possiamo indicare con quale file iniziare nella cartella utilizzando la proprietà main in package.json. Ad esempio, per fare in modo che la riga require('find-me') si risolva in un file diverso nella cartella find-me, tutto ciò che dobbiamo fare è aggiungere lì un file package.json e specificare quale file deve essere usato per risolvere questa cartella:

~/learn-node $ echo "console.log('I rule');" > node_modules/find-me/start.js

~/learn-node $ echo '{ "name": "find-me-folder", "main": "start.js" }' > node_modules/find-me/package.json

~/learn-node $ node
> require('find-me');
I rule
{}
>

require.resolve

Se vuoi solo risolvere il modulo e non eseguirlo, puoi usare la funzione require.resolve . Questa si comporta esattamente come la funzione principale require , ma non carica il file. Verrà comunque generato un errore se il file non esiste e restituirà il percorso completo del file una volta trovato.

> require.resolve('find-me');
'/Users/samer/learn-node/node_modules/find-me/start.js'
> require.resolve('not-there');
Error: Cannot find module 'not-there'
    at Function.Module._resolveFilename (module.js:470:15)
    at Function.resolve (internal/module.js:27:19)
    at repl:1:9
    at ContextifyScript.Script.runInThisContext (vm.js:23:33)
    at REPLServer.defaultEval (repl.js:336:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.onLine (repl.js:533:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:191:7)
>

Questo può essere utilizzato, ad esempio, per verificare se un pacchetto opzionale è installato o meno e utilizzarlo solo quando è disponibile.

Percorsi relativi e assoluti

Oltre a risolvere i moduli dall'interno delle directory node_modules, possiamo anche posizionare il modulo dove vogliamo e richiederlo con percorsi relativi ( ./e ../) o con percorsi assoluti che iniziano con /.

Se, ad esempio, il file find-me.js era in una cartella lib anziché in una cartella node_modules, possiamo richiederlo con:

require('./lib/find-me');

Relazione genitore-figlio tra file

Crea un file lib/util.js e aggiungi una riga console.log per identificarlo. Inoltre, stampa con console.log l'oggetto stesso module:

~/learn-node $ mkdir lib
~/learn-node $ echo "console.log('In util', module);" > lib/util.js

Fai lo stesso per un file index.js, che è ciò che eseguiremo con il comando node. Fai in modo che questo file index.js richieda lib/util.js:

~/learn-node $ echo "console.log('In index', module); require('./lib/util');" > index.js

Ora esegui il file index.js con node:

~/learn-node $ node index.js
In index Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/Users/samer/learn-node/index.js',
  loaded: false,
  children: [],
  paths: [ ... ] }
In util Module {
  id: '/Users/samer/learn-node/lib/util.js',
  exports: {},
  parent:
   Module {
     id: '.',
     exports: {},
     parent: null,
     filename: '/Users/samer/learn-node/index.js',
     loaded: false,
     children: [ [Circular] ],
     paths: [...] },
  filename: '/Users/samer/learn-node/lib/util.js',
  loaded: false,
  children: [],
  paths: [...] }

Nota come il modulo principale index (id: '.') sia ora elencato come genitore per il modulo lib/util. Tuttavia, il modulo lib/util non è stato elencato come figlio del modulo index. Invece, abbiamo lì il valore [Circular] perché questo è un riferimento circolare. Se Node stampa l'oggetto modulo lib/util, entrerà in un ciclo infinito. Ecco perché sostituisce semplicemente il riferimento lib/util con [Circular].

Cosa ancora più importante ora, cosa succede se il modulo lib/util richiede il modulo principale index? È qui che entriamo in quella che è nota come la dipendenza modulare circolare, consentita in Node.

Per capirlo meglio, comprendiamo prima alcuni altri concetti sull'oggetto module.

exports, module.exports e caricamento sincrono dei moduli

In ogni modulo, exports è un oggetto speciale. Se hai notato sopra, ogni volta che abbiamo stampato un oggetto module, aveva una proprietà exports che è stata finora un oggetto vuoto. Possiamo aggiungere qualsiasi attributo a questo speciale oggetto di esportazione . Ad esempio, esportiamo un attributo id per index.js e lib/util.js:

// Aggiungi la riga seguente in cima a lib/util.js
exports.id = 'lib/util';

// Aggiungi la riga seguente in cima a index.js
exports.id = 'index';

Quando ora eseguiamo index.js, vedremo questi attributi come gestiti sull'oggetto module di ogni file:

~/learn-node $ node index.js
In index Module {
  id: '.',
  exports: { id: 'index' },
  loaded: false,
  ... }
In util Module {
  id: '/Users/samer/learn-node/lib/util.js',
  exports: { id: 'lib/util' },
  parent:
   Module {
     id: '.',
     exports: { id: 'index' },
     loaded: false,
     ... },
  loaded: false,
  ... }

Ho rimosso alcuni attributi nell'output sopra per mantenerlo breve, ma nota come l'oggetto exports ora ha gli attributi che abbiamo definito in ciascun modulo. Puoi inserire tutti gli attributi che vuoi su quell'oggetto exports e puoi effettivamente cambiare l'intero oggetto in qualcos'altro. Ad esempio, per modificare l'oggetto exports in modo che sia una funzione anziché un oggetto, procediamo come segue:

// Aggiungi la riga seguente index.js prima di console.log

module.exports = function() {};

Ora quando esegui index.js, vedrai come l'oggetto exports sia una funzione:

~/learn-node $ node index.js
In index Module {
  id: '.',
  exports: [Function],
  loaded: false,
  ... }

Nota come non abbiamo fatto exports = function() {}per trasformare l'oggetto exports in una funzione. In realtà non possiamo farlo perché la variabile exports all'interno di ogni modulo è solo un riferimento a module.exports che gestisce le proprietà esportate. Quando riassegnamo la variabile exports, quel riferimento viene perso e introdurremmo una nuova variabile invece di cambiare l'oggetto module.exports.

L'oggetto module.exports in ogni modulo è ciò che la funzione require restituisce quando richiediamo quel modulo. Ad esempio, cambia la riga require('./lib/util') in index.js:

const UTIL = require('./lib/util');

console.log('UTIL:', UTIL);

Questo catturerà le proprietà esportate in lib/util nella costante UTIL. Ora quando eseguiamo index.js, l'ultima riga produrrà:

UTIL: { id: 'lib/util' }

Parliamo anche dell'attributo loaded di ogni modulo. Finora, ogni volta che abbiamo stampato un oggetto module, abbiamo visto un attributo loaded in quell'oggetto con un valore false.

Il modulo module utilizza l'attributo loaded per tenere traccia di quali moduli sono stati caricati (valore true) e quali moduli sono ancora in fase di caricamento (valore false). Possiamo, ad esempio, vedere il modulo index.js completamente caricato se stampiamo il suo oggetto module nel ciclo successivo nel ciclo di eventi utilizzando una chiamata setImmediate:

// In index.js
setImmediate(() => {
  console.log('Oggetto del modulo index.js è ora caricato!', module)
});

L'output sarebbe:

Oggetto del modulo index.js è ora caricato! Module {
  id: '.',
  exports: [Function],
  parent: null,
  filename: '/Users/samer/learn-node/index.js',
  loaded: true,
  children:
   [ Module {
       id: '/Users/samer/learn-node/lib/util.js',
       exports: [Object],
       parent: [Circular],
       filename: '/Users/samer/learn-node/lib/util.js',
       loaded: true,
       children: [],
       paths: [Object] } ],
  paths:
   [ '/Users/samer/learn-node/node_modules',
     '/Users/samer/node_modules',
     '/Users/node_modules',
     '/node_modules' ] }

Nota come in questo output ritardato di console.log sia lib/util.js che index.js sono completamente caricati.

L'oggetto exports diventa completo quando Node termina il caricamento del modulo (e lo etichetta così). L'intero processo di richiesta/caricamento di un modulo è sincrono. Ecco perché siamo stati in grado di vedere i moduli completamente caricati dopo un ciclo nel ciclo degli eventi.

Ciò significa anche che non possiamo modificare l'oggetto exports in modo asincrono. Non possiamo, ad esempio, fare quanto segue in nessun modulo:

fs.readFile('/etc/passwd', (err, data) => {
  if (err) throw err;
  
  exports.data = data; // Non funzionerà.
});

Dipendenza del modulo circolare

Proviamo ora a rispondere all'importante domanda sulla dipendenza circolare in Node: cosa succede quando il modulo 1 richiede il modulo 2 e il modulo 2 richiede il modulo 1?

Per scoprirlo, creiamo i seguenti due file in lib/, module1.js e module2.js e facciamo in modo che si richiedano a vicenda:

// lib/module1.js

exports.a = 1;

require('./module2');

exports.b = 2;
exports.c = 3;

// lib/module2.js

const Module1 = require('./module1');
console.log('Module1 è parzialmente caricato qui', Module1);

Quando eseguiamo module1.js si vedrà quanto segue:

~/learn-node $ node lib/module1.js
Module1 è parzialmente caricato qui { a: 1 }

Abbiamo richiesto module2 prima che module1 fosse completamente caricato e poiché module2 ha richiesto module1 prima del suo completo caricamento, ciò che otteniamo dall'oggetto exports a quel punto sono tutte le proprietà esportate prima della dipendenza circolare. Solo la proprietà a è stata riportata perché sia b che c sono stati esportati dopo aver richiesto module2 e stampato module1.

Node rende ciò davvero semplice. Durante il caricamento di un modulo, costruisce l'oggetto exports. Puoi richiedere il modulo prima che il caricamento sia terminato e otterrai solo un oggetto exports parziale con tutto ciò che è stato definito sino a quel momento.

Componenti aggiuntivi JSON e C/C++

Possiamo richiedere in modo nativo file JSON e file aggiuntivi C++ con la funzione require. Non è nemmeno necessario specificare un'estensione di file per farlo.

Se non è stata specificata un'estensione di file, la prima cosa che Node tenterà di risolvere è un file .js. Se non riesce a trovare un file .js, proverà un file .json e lo analizzerà se trovato come file di testo JSON. Successivamente, proverà a trovare un file binario .node. Tuttavia, per rimuovere l'ambiguità, sarebbe meglio specificare un'estensione di file quando richiedi qualcosa di diverso dai file .js.

La richiesta di file JSON è utile se, ad esempio, tutto ciò che devi gestire in quel file sono alcuni valori di configurazione statici o alcuni valori che leggi periodicamente da un'origine esterna. Ad esempio, se avessimo il seguente file config.json:

{
  "host": "localhost",
  "port": 8080
}

Possiamo richiederlo direttamente in questo modo:

const { host, port } = require('./config');

console.log(`Il server verrà eseguito a http://${host}:${port}`);

L'esecuzione del codice sopra avrà questo output:

Il server verrà eseguito a http://localhost:8080

Se Node non riesce a trovare un file .js o un file .json, cercherà un file .node e interpreterà il file come un modulo aggiuntivo compilato.

Il sito della documentazione di Node ha un file aggiuntivo di esempio scritto in C++. È un modulo semplice che espone una funzione hello() che stampa "hello world".

È possibile utilizzare il pacchetto node-gyp per compilare e creare il file .cc in un file .node. Devi solo configurare un file binding.gyp per dire a node-gyp cosa fare.

Una volta che hai il file addon.node (o qualunque nome tu specifichi in binding.gyp), puoi richiederlo in modo nativo proprio come qualsiasi altro modulo:

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

console.log(addon.hello());

Possiamo effettivamente vedere il supporto delle tre estensioni guardando require.extensions.

D3NuN7cetXAjYpHfqBGribFo-ex0fj-AvuDA

Osservando le funzioni per ciascuna estensione, puoi vedere chiaramente cosa farà Node con ciascuna. Esso utilizza module._compile per i file .js, JSON.parse per i file .json e process.dlopen per i file .node.

Tutto il codice che scrivi in ​​Node sarà racchiuso in funzioni

Il wrapping dei moduli da parte di Node è spesso frainteso. Per capirlo, lascia che ti ricordi la relazione exports/ module.exports.

Possiamo usare l'oggetto exports per esportare le proprietà, ma non possiamo sostituire l'oggetto exports direttamente perché è solo un riferimento a module.exports

exports.id = 42; // Questo è ok.

exports = { id: 42 }; // Questo non va bene.

module.exports = { id: 42 }; // Questo è ok.

In che modo esattamente questo oggetto exports, che sembra essere globale per ogni modulo, viene definito come riferimento sull'oggetto module ?

Consentitemi di porre un'altra domanda prima di spiegare il processo di wrapping di Node.

In un browser, quando dichiariamo una variabile in uno script come questo:

var answer = 42;

Quella variabile answer sarà globalmente disponibile in tutti gli script dopo lo script che l'ha definita.

Questo non è il caso di Node. Quando definiamo una variabile in un modulo, gli altri moduli del programma non avranno accesso a quella variabile. Quindi, come mai le variabili in Node hanno magicamente un ambito globale?

La risposta è semplice. Prima di compilare un modulo, Node racchiude il codice del modulo stesso in una funzione, che possiamo ispezionare usando la proprietà wrapper del modulo module.

~ $ node
> require('module').wrapper
[ '(function (exports, require, module, __filename, __dirname) { ',
  '\n});' ]
>

Node non esegue direttamente il codice che scrivi in ​​un file. Esegue questa funzione wrapper che avrà il tuo codice nel suo corpo. Questo è ciò che mantiene le variabili di primo livello, che sono definite in qualsiasi modulo con ambito a questo modulo.

Questa funzione wrapper ha 5 argomenti: exports, require, module, __filename e __dirname. Questo è ciò che li fa sembrare globali quando in realtà sono specifici per ciascun modulo.

Tutti questi argomenti ottengono i loro valori quando Node esegue la funzione wrapper. exports è definito come un riferimento a module.exports prima di quello. require e module sono entrambi specifici della funzione da eseguire, e le variabili __filename/ __dirname conterranno il nome assoluto del file del modulo wrapped e il percorso della directory.

Puoi vedere questo wrapping in azione se esegui uno script con un problema sulla prima riga:

~/learn-node $ echo "euaohseu" > bad.js

~/learn-node $ node bad.js
~/bad.js:1
(function (exports, require, module, __filename, __dirname) { euaohseu
                                                              ^
ReferenceError: euaohseu is not defined

Nota come la prima riga dello script riportato sopra, fosse la funzione wrapper, non il riferimento errato.

Inoltre, poiché ogni modulo viene racchiuso in una funzione, possiamo effettivamente accedere agli argomenti di quella funzione con la parola chiave arguments:

~/learn-node $ echo "console.log(arguments)" > index.js

~/learn-node $ node index.js
{ '0': {},
  '1':
   { [Function: require]
     resolve: [Function: resolve],
     main:
      Module {
        id: '.',
        exports: {},
        parent: null,
        filename: '/Users/samer/index.js',
        loaded: false,
        children: [],
        paths: [Object] },
     extensions: { ... },
     cache: { '/Users/samer/index.js': [Object] } },
  '2':
   Module {
     id: '.',
     exports: {},
     parent: null,
     filename: '/Users/samer/index.js',
     loaded: false,
     children: [],
     paths: [ ... ] },
  '3': '/Users/samer/index.js',
  '4': '/Users/samer' }

Il primo argomento è l'oggetto exports, che inizialmente è vuoto. Quindi abbiamo gli oggetti require/ module, entrambi istanze associate al file index.js che stiamo eseguendo. Non sono variabili globali. Gli ultimi 2 argomenti sono il percorso del file e il percorso della sua directory.

Il valore restituito dalla funzione di wrapping è module.exports. All'interno della funzione wrapped, possiamo utilizzare l'oggetto exports per modificare le proprietà di module.exports, ma non possiamo riassegnare exports perché è solo un riferimento.

Quello che succede è più o meno equivalente a:

function (require, module, __filename, __dirname) {
  let exports = module.exports;
  
  // Il tuo codice...
  
  return module.exports;
}

Se cambiassimo l'intero oggetto exports, non sarebbe più un riferimento a module.exports. Questo è il modo in cui la referenziazione degli oggetti di JavaScript funziona ovunque, non solo in questo contesto.

L'oggetto require

Non c'è niente di speciale in require. È un oggetto che agisce principalmente come una funzione che prende il nome di modulo o un percorso e restituisce l'oggetto module.exports. Possiamo semplicemente sovrascrivere l'oggetto require con la nostra logica, se vogliamo.

Ad esempio, a solo scopo di test, vogliamo che ogni chiamata require venga ingannata per impostazione predefinita e restituisca semplicemente un oggetto falso invece dell'oggetto di esportazione del modulo richiesto. Questa semplice riassegnazione di require farà il trucco:

require = function() {

  return { mocked: true };
  
}

Dopo aver eseguito la suddetta riassegnazione di require, ogni chiamata require('something') nello script restituirà semplicemente l'oggetto che abbiamo definito.

L'oggetto require ha anche proprietà proprie. Abbiamo visto la proprietà resolve, che è una funzione che esegue solo la fase di risoluzione del processo require. Sopra abbiamo visto anche require.extensions.

C'è anche require.main che può essere utile per determinare se lo script è richiesto o eseguito direttamente.

Supponiamo, ad esempio, di avere questa semplice funzione printInFrame in print-in-frame.js:

// In print-in-frame.js

const printInFrame = (size, header) => {
  console.log('*'.repeat(size));
  console.log(header);
  console.log('*'.repeat(size));
};

La funzione accetta un argomento numerico size e un argomento stringa header e stampa quell'intestazione in una cornice di stelle con la dimensione specificata.

Vogliamo usare questo file in due modi:

  1. Direttamente dalla riga di comando in questo modo:
~/learn-node $ node print-in-frame 8 Hello

Passando 8 e Hello come argomenti della riga di comando per stampare "Hello" in una cornice di 8 stelle.

2. Con require. Supponendo che il modulo richiesto esporterà la funzione printInFrame, possiamo semplicemente chiamarla:

const print = require('./print-in-frame');

print(5, 'Hey');

Per stampare l'intestazione "Hey" in una cornice di 5 stelle.

Sono due usi diversi. Abbiamo bisogno di un modo per determinare se il file viene eseguito come script autonomo o se è richiesto da altri script.

Qui è dove possiamo usare questa semplice istruzione if:

if (require.main === module) {
  // Il file viene eseguito direttamente (non con require)
}

Quindi possiamo utilizzare questa condizione per soddisfare i requisiti di utilizzo di cui sopra richiamando la funzione printInFrame in modo diverso:

// In print-in-frame.js

const printInFrame = (size, header) => {
  console.log('*'.repeat(size));
  console.log(header);
  console.log('*'.repeat(size));
};

if (require.main === module) {
  printInFrame(process.argv[2], process.argv[3]);
} else {
  module.exports = printInFrame;
}

Quando il file non è richiesto, chiamiamo semplicemente la funzione printInFrame con gli elementi process.argv. Altrimenti, cambiamo semplicemente l'oggetto module.exports in modo che questo sia la funzione stessa printInFrame.

Tutti i moduli verranno memorizzati nella cache

La memorizzazione nella cache è importante da capire. Vorrei usare un semplice esempio per dimostrarlo.

Supponiamo che tu abbia il seguente file ascii-art.js che stampa un'intestazione dall'aspetto interessante:

RbgFBaBdZszHFKEcmsyE8mMW7udDG4NdYofy

Vogliamo visualizzare questa intestazione ogni volta che richiediamo il file. Quindi, quando richiediamo il file due volte, vogliamo che l'intestazione venga visualizzata due volte.

require('./ascii-art') // mostrerà l'intestazione.
require('./ascii-art') // non mostrerà l'intestazione.

La second richiesta non mostrerà l'intestazione a causa della memorizzazione nella cache dei moduli. Node memorizza nella cache la prima chiamata e non carica il file nella seconda chiamata.

Possiamo vedere questa cache stampando require.cache dopo la prima richiesta. Il registro della cache è semplicemente un oggetto che ha una proprietà per ogni modulo richiesto. Questi valori di proprietà sono gli oggetti module utilizzati per ogni modulo. Possiamo semplicemente eliminare una proprietà da questo oggetto require.cache per invalidare quella cache. Se lo facciamo, Node ricaricherà il modulo per reinserirlo nella cache.

Tuttavia, questa non è la soluzione più efficiente per questo caso. La soluzione semplice è avvolgere la riga log ascii-art.js con una funzione ed esportare quella funzione. In questo modo, quando richiediamo il file ascii-art.js, otteniamo una funzione che possiamo eseguire per invocare la stampa ogni volta:

require('./ascii-art')() // mostrerà l'intestazione.
require('./ascii-art')() // mostrerà anche l'intestazione.

Questo è tutto ciò che ho per questo argomento. Grazie per aver letto. Alla prossima!

Stai imparando React o Node? Scopri i miei libri: