node.js require () cache - possible to invalidate?


325

Dans la documentation node.js:

Les modules sont mis en cache après le premier chargement. Cela signifie (entre autres) que chaque appel à exiger ('foo') obtiendra exactement le même objet retourné, s'il se résout dans le même fichier.

Existe-t-il un moyen d'invalider ce cache? c'est-à-dire pour les tests unitaires, j'aimerais que chaque test travaille sur un nouvel objet.



Un autre module NPM avec un observateur: npmjs.com/package/updated-require
Jorge Fuentes González

Il est possible de mettre en cache le contenu du fichier sans utiliser require et de l'évaluer pour différentes étendues stackoverflow.com/questions/42376161/…
lonewarrior556

Réponses:


305

Vous pouvez toujours supprimer en toute sécurité une entrée dans require.cache sans problème, même en présence de dépendances circulaires. Parce que lorsque vous supprimez, vous supprimez simplement une référence à l'objet module mis en cache, pas à l'objet module lui-même, l'objet module ne sera pas GCed car en cas de dépendances circulaires, il existe toujours un objet référençant cet objet module.

Supposons que vous ayez:

script a.js:

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

et script b.js:

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

quand tu fais:

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

tu auras:

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

maintenant si vous éditez votre b.js:

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

et fait:

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

tu auras:

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

===

Ce qui précède est valide si vous exécutez directement node.js. Cependant, si vous utilisez des outils qui ont leur propre système de mise en cache de module, tel que jest , l'instruction correcte serait:

jest.resetModules();

2
pourriez-vous expliquer pourquoi { ... a: undefined}lors b.jsde la première demande ? Je m'attendrais à égaler 'a from a.js'. Merci
ira

1
pourquoi un indéfini?
Jeff P Chacko

4
Réponse tardive, mais d'après ce que je b[a]comprends, la première fois n'est pas définie car il y a une dépendance circulaire. a.jsexige b.jsqui à son tour nécessite a.js. a.jsn'est pas encore complètement chargé et exports.an'a pas encore été défini, donc b.jsrien à faire.
nik10110

une façon de le faire si j'utilise require.main.require(path)comme décrit ici? stackoverflow.com/questions/10860244/…
Flion

186

Si vous souhaitez toujours recharger votre module, vous pouvez ajouter cette fonction:

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

puis utilisez requireUncached('./myModule')au lieu d'exiger.


6
C'est parfait en combinaison avec la fs.watchméthode qui écoute les changements de fichiers.
ph3nx

2
quel est le risque?
Scarass

Même question que j'ai, quel est le risque d'utiliser cette solution et non la réponse acceptée?
rotimi-best

1
C'est vraiment pareil. Selon la structure du code, les choses peuvent se bloquer lorsque vous essayez de l'initialiser à nouveau. Ex. si le module démarre un serveur et écoute un port. La prochaine fois que vous aurez besoin de déconnecter le module, il échouera car ce port est déjà ouvert, etc.
guindant le

133

Oui, vous pouvez accéder au cache via require.cache[moduleName]moduleNameest le nom du module auquel vous souhaitez accéder. La suppression d'une entrée en appelant delete require.cache[moduleName]entraînera requirele chargement du fichier réel.

Voici comment supprimer tous les fichiers mis en cache associés au module:

/**
 * 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'utilisation serait:

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

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

Comme ce code utilise le même résolveur require, spécifiez simplement ce dont vous avez besoin.


"Unix n'a pas été conçu pour empêcher ses utilisateurs de faire des choses stupides, car cela les empêcherait également de faire des choses intelligentes." - Doug Gwyn

Je pense qu'il aurait dû y avoir un moyen d'effectuer un chargement explicite de module non mis en cache.


17
+1 juste pour la citation de Doug. J'avais besoin de quelqu'un pour exprimer ce en quoi je croyais aussi :)
Poni

1
Excellente réponse! Si vous souhaitez démarrer un repl de noeud avec le rechargement activé, consultez cet aperçu .
gleitz

1
impressionnant. J'ajouterais ceci à la require.uncachefonction. `` `// voir github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) delete module.constructor ._pathCache [k];}); `` Supposons que vous ayez requis un module, que vous l'ayez désinstallé, puis réinstallé le même module mais que vous ayez utilisé une version différente qui a un script principal différent dans son package.json, la prochaine exigence échouera car ce script principal n'existe pas car il est mis en cacheModule._pathCache
bentael

merde. mon commentaire est terrible. Je n'ai pas pu ajouter de code dans ce commentaire et il est trop tard pour le modifier, j'ai donc répondu. @Ben Barkay si vous pouviez modifier votre question pour ajouter le petit extrait de code à votrerequire.uncache
bentael

Merci @bentael, j'ai ajouté ceci à ma réponse.
Ben Barkay

39

Il y a un module simple pour cela ( avec des tests )

Nous avons eu ce problème exact lors du test de notre code ( supprimer les modules mis en cache afin qu'ils puissent être requis à nouveau ), nous avons donc examiné toutes les suggestions des personnes sur les diverses questions et réponses de StackOverflow et mis en place un module node.js simple ( avec tests ):

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

Comme vous vous en doutez, fonctionne pour les packages npm publiés et les modules définis localement . Windows, Mac, Linux, etc.

Statut de la construction codecov.io Code Maintenabilité du climat Statut des dépendances Statut devDependencies

Comment? ( utilisation )

L'utilisation est assez simple:

installer

Installez le module à partir de npm:

npm install decache --save-dev

Utilisez-le dans votre code:

// 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)

Si vous avez des questions ou avez besoin de plus d'exemples, veuillez créer un problème GitHub: https://github.com/dwyl/decache/issues


1
J'ai regardé cela et cela me semble vraiment génial à utiliser lors des tests afin de pouvoir décharger et recharger un module dans des conditions spécifiques, mais malheureusement je suis au travail et mon entreprise évite les licences GPL. Je veux seulement l'utiliser pour des tests, donc je le considère toujours car il semble si utile.
Matt_JD

@Matt_JD merci pour vos commentaires. quelle licence préféreriez-vous?
nelsonic

2
@Matt_JD Nous avons mis à jour la licence du MIT. Bonne chance avec ton travail! :-)
nelsonic

1
cela a fonctionné à merveille! Mettre en vedette ce repo et voter pour cette réponse.
aholt

1
recommande fortement, fonctionne bien sur la dernière v14.2.0 à partir d'aujourd'hui
Thomazella

28

Pour toute personne rencontrant cela qui utilise Jest, parce que Jest fait sa propre mise en cache de module, il y a une fonction intégrée pour cela - assurez-vous simplement qu'il jest.resetModulesfonctionne par exemple. après chacun de vos tests:

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

Trouvé cela après avoir essayé d'utiliser le décaché comme une autre réponse suggérée. Merci à Anthony Garvan .

Documentation fonctionnelle ici .


1
Merci beaucoup pour cette note!
mjgpy3

2
dieu combien de temps ai-je expérimenté avant de trouver ça .... merci!
Tiago

16

La solution consiste à utiliser:

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

Trouvez ici quelques explications de base pour ceux qui, comme moi, sont un peu nouveaux dans ce domaine:

Supposons que vous ayez un example.jsfichier factice à la racine de votre répertoire:

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

Alors vous require()aimez ça:

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

Si vous ajoutez ensuite une ligne comme celle-ci à example.js:

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

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

Et continuez dans la console, le module n'est pas mis à jour:

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

C'est alors que vous pouvez utiliser delete require.cache[require.resolve()]indiqué dans la réponse de luff :

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

Ainsi, le cache est nettoyé et require()capture à nouveau le contenu du fichier, en chargeant toutes les valeurs actuelles.


IMHO C'est la réponse la plus appropriée
Piyush Katariya

5

recâbler est idéal pour ce cas d'utilisation, vous obtenez une nouvelle instance à chaque appel. Injection de dépendance facile pour les tests unitaires node.js.

rewire ajoute un setter et un getter spéciaux aux modules afin que vous puissiez modifier leur comportement pour de meilleurs tests unitaires. Tu peux

injecter des simulations pour d'autres modules ou globaux comme les variables privées de fuite de processus remplacer les variables dans le module. rewire ne charge pas le fichier et évalue le contenu pour émuler le mécanisme requis du nœud. En fait, il utilise l'exigence du nœud pour charger le module. Ainsi, votre module se comporte exactement de la même manière dans votre environnement de test que dans des circonstances normales (à l'exception de vos modifications).

Bonne nouvelle pour tous les accros à la caféine: le recâblage fonctionne également avec Coffee-Script. Notez que dans ce cas, CoffeeScript doit être répertorié dans vos devDependencies.


4

Je voudrais ajouter à la réponse de luff une ligne de plus et changer le nom du paramètre:

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)
}

Il s'agit donc de faire fonctionner la fonction dans des sous-modules? Agréable! Une méthode plus courte pour supprimer le module du tableau module.children consiste à utiliser une fonction de filtre: module.children = module.children.filter (function (child) {return child.id! == require.resolve (_module);}) ;
luff

4

Oui, vous pouvez invalider le cache.

Le cache est stocké dans un objet appelé require.cache auquel vous pouvez accéder directement en fonction des noms de fichiers (par exemple, /projects/app/home/index.jspar opposition à ceux ./homeque vous utiliseriez dans une require('./home')instruction).

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

Notre équipe a trouvé le module suivant utile. Pour invalider certains groupes de modules.

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


3

Je n'ai pas pu ajouter proprement de code dans le commentaire d'une réponse. Mais j'utiliserais la réponse de @Ben Barkay, puis j'ajouterais cela à la require.uncachefonction.

    // 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 ];
        }
    }); 

Supposons que vous ayez requis un module, que vous l'ayez désinstallé, puis réinstallé le même module mais que vous ayez utilisé une version différente qui a un script principal différent dans son package.json, la prochaine exigence échouera car ce script principal n'existe pas car il est mis en cache dans Module._pathCache


3

Je ne suis pas sûr à 100% de ce que vous entendez par «invalider», mais vous pouvez ajouter ce qui suit au-dessus des requireinstructions pour vider le cache:

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

Tiré du commentaire de @ Dancrumb ici


2

requireUncached avec chemin relatif: 🔥

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

module.exports = requireUncached;

invoquer requireUncached avec un chemin relatif:

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

1

La procédure en deux étapes fonctionne parfaitement pour moi.

Après avoir changé le Modelfichier, c'est-à-dire 'mymodule.js'dynamiquement, vous devez d'abord supprimer le modèle précompilé dans le modèle mangouste, puis le recharger à l'aide de require-reload

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

S'il s'agit de tests unitaires, un autre bon outil à utiliser est proxyquire . Chaque fois que vous achetez le module par procuration, il invalide le cache du module et en cache un nouveau. Il vous permet également de modifier les modules requis par le fichier que vous testez.


0

J'ai fait un petit module pour supprimer le module du cache après le chargement. Cela force la réévaluation du module la prochaine fois qu'il sera nécessaire. Voir 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: vous pouvez également mettre "auto-destruction" dans le module lui-même. Voir https://github.com/bahmutov/unload-me

PSS: plus d'astuces avec Node nécessitent dans mon https://glebbahmutov.com/blog/hacking-node-require/

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.