node.js richiedono () cache - è possibile invalidare?


325

Dalla documentazione node.js:

I moduli vengono memorizzati nella cache dopo il primo caricamento. Ciò significa (tra le altre cose) che ogni chiamata da richiedere ('pippo') otterrà esattamente lo stesso oggetto restituito, se si risolvesse nello stesso file.

C'è un modo per invalidare questa cache? cioè per i test unitari, vorrei che ogni test funzionasse su un nuovo oggetto.



Un altro modulo NPM con un osservatore: npmjs.com/package/updated-require
Jorge Fuentes González,

E 'possibile memorizzare nella cache il contenuto del file senza l'utilizzo di richiedere e eval per scopi diversi stackoverflow.com/questions/42376161/...
lonewarrior556

Risposte:


305

È sempre possibile eliminare in modo sicuro una voce in request.cache senza problemi, anche in presenza di dipendenze circolari. Perché quando si elimina, è sufficiente eliminare un riferimento all'oggetto modulo memorizzato nella cache, non all'oggetto modulo stesso, l'oggetto modulo non verrà GCeducato perché in caso di dipendenze circolari, c'è ancora un oggetto che fa riferimento a questo oggetto modulo.

Supponiamo di avere:

script a.js:

var b=require('./b.js').b;
exports.a='a from a.js';
exports.b=b;

e script b.js:

var a=require('./a.js').a;
exports.b='b from b.js';
exports.a=a;

quando lo fai:

var a=require('./a.js')
var b=require('./b.js')

otterrete:

> a
{ a: 'a from a.js', b: 'b from b.js' }
> b
{ b: 'b from b.js', a: undefined }

ora se modifichi b.js:

var a=require('./a.js').a;
exports.b='b from b.js. changed value';
exports.a=a;

e fai:

delete require.cache[require.resolve('./b.js')]
b=require('./b.js')

otterrete:

> a
{ a: 'a from a.js', b: 'b from b.js' }
> b
{ b: 'b from b.js. changed value',
  a: 'a from a.js' }

===

Quanto sopra è valido se si esegue direttamente node.js. Tuttavia, se si utilizzano strumenti con il proprio sistema di memorizzazione nella cache dei moduli, come jest , la frase corretta sarebbe:

jest.resetModules();

2
potresti per favore spiegare perché { ... a: undefined}quando richiesto b.jsper la prima volta? Mi aspetterei di eguagliare 'a from a.js'. Grazie
ira,

1
perché è indefinito?
Jeff P Chacko,

4
Risposta in ritardo, ma da quello che raccolgo b[a]è indefinito la prima volta poiché c'è una dipendenza circolare. a.jsrichiede b.jsche a sua volta richiede a.js. a.jsnon è ancora completamente caricato e exports.adeve ancora essere definito, quindi b.jsottieni niente.
nik10110

un modo per farlo se sto usando require.main.require(path)come descritto qui? stackoverflow.com/questions/10860244/...
Flion

186

Se vuoi sempre ricaricare il tuo modulo, puoi aggiungere questa funzione:

function requireUncached(module) {
    delete require.cache[require.resolve(module)];
    return require(module);
}

e quindi utilizzare requireUncached('./myModule')invece di richiedere.


6
Questo è perfetto in combinazione con il fs.watchmetodo che ascolta le modifiche ai file.
ph3nx,

2
qual è il rischio?
Scarass

Stessa domanda che ho, qual è il rischio di utilizzare questa soluzione e non la risposta accettata?
Rotimi-best

1
È lo stesso davvero. A seconda di come è strutturato il codice, le cose potrebbero andare in crash quando si tenta di inizializzarlo di nuovo. Ex. se il modulo avvia un server e ascolta una porta. La prossima volta che hai richiesto Scollega il modulo, fallirà poiché quella porta è già aperta e così via.
Luff,

133

Sì, puoi accedere alla cache tramite require.cache[moduleName]dove si moduleNametrova il nome del modulo a cui desideri accedere. L'eliminazione di una voce chiamando delete require.cache[moduleName]chiamerà requireil file effettivo.

Ecco come rimuovere tutti i file memorizzati nella cache associati al modulo:

/**
 * Removes a module from the cache
 */
function purgeCache(moduleName) {
    // Traverse the cache looking for the files
    // loaded by the specified module name
    searchCache(moduleName, function (mod) {
        delete require.cache[mod.id];
    });

    // Remove cached paths to the module.
    // Thanks to @bentael for pointing this out.
    Object.keys(module.constructor._pathCache).forEach(function(cacheKey) {
        if (cacheKey.indexOf(moduleName)>0) {
            delete module.constructor._pathCache[cacheKey];
        }
    });
};

/**
 * Traverses the cache to search for all the cached
 * files of the specified module name
 */
function searchCache(moduleName, callback) {
    // Resolve the module identified by the specified name
    var mod = require.resolve(moduleName);

    // Check if the module has been resolved and found within
    // the cache
    if (mod && ((mod = require.cache[mod]) !== undefined)) {
        // Recursively go over the results
        (function traverse(mod) {
            // Go over each of the module's children and
            // traverse them
            mod.children.forEach(function (child) {
                traverse(child);
            });

            // Call the specified callback providing the
            // found cached module
            callback(mod);
        }(mod));
    }
};

L'utilizzo sarebbe:

// Load the package
var mypackage = require('./mypackage');

// Purge the package from cache
purgeCache('./mypackage');

Dal momento che questo codice utilizza lo stesso risolutore require, specifica semplicemente ciò che vorresti.


"Unix non è stato progettato per impedire ai suoi utenti di fare cose stupide, in quanto ciò impedirebbe loro anche di fare cose intelligenti". - Doug Gwyn

Penso che avrebbe dovuto esserci un modo per eseguire un caricamento esplicito del modulo non memorizzato nella cache.


17
+1 solo per la citazione di Doug. Avevo bisogno di qualcuno che esprimesse ciò in cui credevo anche :)
Poni

1
Risposta eccellente! Se desideri avviare un nodo di sostituzione con la ricarica abilitata, consulta questo riepilogo .
Gleitz,

1
eccezionale. Vorrei aggiungere questo alla require.uncachefunzione. `` `// vedi github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) elimina module.constructor ._pathCache [k];}); `` Supponi di aver richiesto un modulo, quindi di averlo disinstallato, quindi di aver reinstallato lo stesso modulo ma di aver utilizzato una versione diversa che ha uno script principale diverso nel suo pacchetto.json, il successivo requisito fallirà perché quello script principale non esiste perché è memorizzato nella cacheModule._pathCache
bentael,

una schifezza. il mio commento è terribile. Non ho potuto aggiungere il codice in modo chiaro in questo commento ed è troppo tardi per modificarlo, quindi ho risposto. @Ben Barkay se potessi modificare la tua domanda per aggiungere il piccolo frammento di codice al tuorequire.uncache
bentael,

Grazie @bentael, l'ho aggiunto alla mia risposta.
Ben Barkay,

39

C'è un modulo semplice per questo ( con test )

Abbiamo riscontrato questo esatto problema durante il test del nostro codice ( elimina i moduli memorizzati nella cache in modo che possano essere riutilizzati in uno stato aggiornato), quindi abbiamo esaminato tutti i suggerimenti delle persone sui vari StackOverflow Domande e risposte e abbiamo creato un semplice modulo node.js ( con test ):

https://www.npmjs.com/package/ decache

Come ci si aspetterebbe, funziona sia per i pacchetti npm pubblicati che per i moduli definiti localmente . Windows, Mac, Linux, ecc.

Stato build codecov.io Manutenibilità climatica del codice Stato delle dipendenze Stato devDependencies

Come? ( utilizzo )

L'utilizzo è piuttosto semplice:

installare

Installa il modulo da npm:

npm install decache --save-dev

Usalo nel tuo codice:

// require the decache module:
const decache = require('decache');

// require a module that you wrote"
let mymod = require('./mymodule.js');

// use your module the way you need to:
console.log(mymod.count()); // 0   (the initial state for our counter is zero)
console.log(mymod.incrementRunCount()); // 1

// delete the cached module:
decache('./mymodule.js');

//
mymod = require('./mymodule.js'); // fresh start
console.log(mymod.count()); // 0   (back to initial state ... zero)

Se hai domande o hai bisogno di altri esempi, crea un problema con GitHub: https://github.com/dwyl/decache/issues


1
Ho esaminato questo aspetto e mi sembra davvero fantastico da utilizzare durante i test, in modo da poter scaricare e ricaricare un modulo in condizioni specifiche, ma sfortunatamente sono al lavoro e la mia azienda evita le licenze GPL. Voglio usarlo solo per i test, quindi lo sto ancora considerando perché sembra così utile.
Matt_JD

@Matt_JD grazie per il tuo feedback. quale licenza preferiresti?
nelsonic,

2
@Matt_JD Abbiamo aggiornato la licenza al MIT. Buona fortuna con il tuo lavoro! :-)
nelsonic

1
questo ha funzionato incredibilmente! Protagonista questo repository e votazione di questa risposta.
Aholt

1
lo consiglio vivamente, funziona bene con l'ultima versione 14.2.0 di oggi
Thomazella,

28

Per chiunque si imbatta in questo che sta usando Jest, poiché Jest esegue la propria memorizzazione nella cache del modulo, c'è una funzione integrata per questo - assicurati solo di eseguire jest.resetModulesad es. dopo ogni test:

afterEach( function() {
  jest.resetModules();
});

Trovato questo dopo aver provato a usare decache come suggerito un'altra risposta. Grazie ad Anthony Garvan .

Documentazione delle funzioni qui .


1
Grazie mille per questa nota!
mjgpy3,

2
dio per quanto tempo ho sperimentato prima di averlo trovato .... grazie!
Tiago,

16

La soluzione è usare:

delete require.cache[require.resolve(<path of your script>)]

Trova qui alcune spiegazioni di base per coloro che, come me, sono un po 'nuovi in ​​questo:

Supponiamo di avere un example.jsfile fittizio nella directory principale della directory:

exports.message = "hi";
exports.say = function () {
  console.log(message);
}

Allora ti require()piace questo:

$ node
> require('./example.js')
{ message: 'hi', say: [Function] }

Se poi aggiungi una linea come questa a example.js:

exports.message = "hi";
exports.say = function () {
  console.log(message);
}

exports.farewell = "bye!";      // this line is added later on

E continua nella console, il modulo non viene aggiornato:

> require('./example.js')
{ message: 'hi', say: [Function] }

Ecco quando puoi usare delete require.cache[require.resolve()]indicato nella risposta di Luff :

> delete require.cache[require.resolve('./example.js')]
true
> require('./example.js')
{ message: 'hi', say: [Function], farewell: 'bye!' }

Quindi la cache viene pulita e require()cattura nuovamente il contenuto del file, caricando tutti i valori correnti.


IMHO Questa è la risposta più appropriata
Piyush Katariya,

5

rewire è ottimo per questo caso d'uso, ottieni una nuova istanza ad ogni chiamata. Iniezione semplice di dipendenze per i test unitari di node.js.

rewire aggiunge uno speciale setter e getter ai moduli in modo da poter modificare il loro comportamento per un migliore test dell'unità. Potresti

iniettare mock per altri moduli o globi come variabili private di perdite di processo ignorando le variabili all'interno del modulo. rewire non carica il file e valuta i contenuti per emulare il meccanismo di richiesta del nodo. In effetti utilizza il proprio nodo necessario per caricare il modulo. Pertanto, il modulo si comporta esattamente nello stesso ambiente di test e in circostanze normali (tranne le modifiche).

Buone notizie per tutti i drogati di caffeina: rewire funziona anche con Coffee-Script. Si noti che in questo caso CoffeeScript deve essere elencato in devDependencies.


4

Aggiungerei alla risposta di Luff un'altra riga e cambierei il nome del parametro:

function requireCached(_module){
    var l = module.children.length;
    for (var i = 0; i < l; i++)
    {
        if (module.children[i].id === require.resolve(_module))
        {
            module.children.splice(i, 1);
            break;
        }
    }
    delete require.cache[require.resolve(_module)];
    return require(_module)
}

Quindi questo serve a far funzionare la funzione nei sottomoduli? Bello! Un modo più breve per rimuovere il modulo dall'array module.children è utilizzare una funzione di filtro: module.children = module.children.filter (function (child) {return child.id! == require.resolve (_module);}) ;
Luff,

4

Sì, puoi invalidare la cache.

La cache è memorizzata in un oggetto chiamato require.cache a cui è possibile accedere direttamente in base ai nomi dei file (ad esempio - /projects/app/home/index.jsal contrario di quello ./homeche si userebbe in require('./home')un'istruzione).

delete require.cache['/projects/app/home/index.js'];

Il nostro team ha trovato utile il seguente modulo. Per invalidare determinati gruppi di moduli.

https://www.npmjs.com/package/node-resource


3

Non ho potuto aggiungere ordinatamente codice nel commento di una risposta. Ma userei la risposta di @Ben Barkay e aggiungerei questo alla require.uncachefunzione.

    // see https://github.com/joyent/node/issues/8266
    // use in it in @Ben Barkay's require.uncache function or along with it. whatever
    Object.keys(module.constructor._pathCache).forEach(function(cacheKey) {
        if ( cacheKey.indexOf(moduleName) > -1 ) {
            delete module.constructor._pathCache[ cacheKey ];
        }
    }); 

Supponiamo che tu abbia richiesto un modulo, quindi lo abbia disinstallato, quindi reinstallato lo stesso modulo ma utilizzato una versione diversa che ha uno script principale diverso nel suo package.json, il successivo requisito fallirà perché quello script principale non esiste perché è memorizzato nella cache Module._pathCache


3

Non sono sicuro al 100% di ciò che intendi per "invalidato", ma puoi aggiungere quanto segue sopra le requireistruzioni per cancellare la cache:

Object.keys(require.cache).forEach(function(key) { delete require.cache[key] })

Tratto dal commento di @ Dancrumb qui


2

requireUncached con percorso relativo: 🔥

const requireUncached = require => module => {
  delete require.cache[require.resolve(module)];
  return require(module);
};

module.exports = requireUncached;

invoke requestUncached con relativo percorso:

const requireUncached = require('../helpers/require_uncached')(require);
const myModule = requireUncached('./myModule');

1

Seguire la procedura in due passaggi funziona perfettamente per me.

Dopo aver modificato il Modelfile, cioè in 'mymodule.js'modo dinamico, è necessario prima eliminare il modello precompilato nel modello mongoose, quindi ricaricarlo utilizzando request-ricaricare

Example:
        // Delete mongoose model
        delete mongoose.connection.models[thisObject.singular('mymodule')]

        // Reload model
        var reload = require('require-reload')(require);
        var entityModel = reload('./mymodule.js');

0

Se è per unit test, un altro buon strumento da usare è proxyquire . Ogni volta che richiedi il proxy, il modulo invalida la cache del modulo e ne memorizza una nuova. Inoltre, consente di modificare i moduli richiesti dal file che si sta testando.


0

Ho creato un piccolo modulo per eliminare il modulo dalla cache dopo il caricamento. Questo forza la rivalutazione del modulo la prossima volta che è necessario. Vedi https://github.com/bahmutov/require-and-forget

// random.js
module.exports = Math.random()
const forget = require('require-and-forget')
const r1 = forget('./random')
const r2 = forget('./random')
// r1 and r2 will be different
// "random.js" will not be stored in the require.cache

PS: puoi anche inserire "autodistruzione" nel modulo stesso. Vedi https://github.com/bahmutov/unload-me

PSS: più trucchi con Node richiedono nel mio https://glebbahmutov.com/blog/hacking-node-require/

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.