372 votes

cache node.js require() - possibilité d'invalider ?

Extrait de la documentation de node.js :

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

Existe-t-il un moyen d'invalider ce cache ? Par exemple, pour les tests unitaires, j'aimerais que chaque test porte sur un nouvel objet.

8 votes

0 votes

Un autre module NPM avec un observateur : npmjs.com/package/updated-require

0 votes

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

352voto

kaisa1028 Points 481

Vous pouvez toujours supprimer sans problème une entrée dans require.cache, même en cas de dépendances circulaires. Parce que lorsque vous supprimez, vous supprimez juste une référence à l'objet module mis en cache, pas l'objet module lui-même, l'objet module ne sera pas GCed parce que dans le cas de dépendances circulaires, il y a 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 vous le faites :

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

vous obtiendrez :

> 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 faire :

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

vous obtiendrez :

> 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 valable si vous exécutez directement node.js. Cependant, si vous utilisez des outils qui possèdent leur propre système de mise en cache des modules, tels que jest l'affirmation correcte serait :

jest.resetModules();

4 votes

Pourriez-vous expliquer pourquoi { ... a: undefined} lorsqu'il exige b.js pour la première fois ? Je m'attendrais à ce qu'il soit égal à 'a from a.js' . Merci.

1 votes

Pourquoi a est-il indéfini ?

6 votes

Réponse tardive, mais d'après ce que j'ai compris b[a] est indéfini la première fois puisqu'il y a une dépendance circulaire. a.js nécessite b.js qui à son tour nécessite a.js . a.js n'est pas encore complètement chargé et exports.a n'est pas encore défini, donc b.js c'est rien.

237voto

luff Points 765

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

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

et ensuite utiliser requireUncached('./myModule') au lieu de require.

10 votes

Il est parfait en combinaison avec le fs.watch qui écoute les changements de fichiers.

3 votes

Quel est le risque ?

0 votes

J'ai la même question, quel est le risque d'utiliser cette solution et non la réponse acceptée ?

140voto

Ben Barkay Points 2116

Oui, vous pouvez accéder au cache via require.cache[moduleName]moduleName est le nom du module auquel vous souhaitez accéder. La suppression d'une entrée en appelant delete require.cache[moduleName] causera require pour charger le fichier actuel.

C'est ainsi que vous supprimez tous les fichiers 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'usage serait :

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

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

Puisque ce code utilise le même résolveur require fait, il suffit de spécifier ce que vous feriez pour require.


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

Je pense qu'il y a devrait aurait été un moyen d'effectuer un chargement explicite de module sans cache.

19 votes

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

1 votes

Excellente réponse ! Si vous souhaitez démarrer un nœud repl avec le rechargement activé, consultez le site suivant cette phrase .

2 votes

Génial. J'ajouterais ceci à la require.uncache fonction. ``` // 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] ; }) ; ```Si vous avez requis un module, puis l'avez désinstallé, puis réinstallé le même module mais en utilisant une version différente qui a un script principal différent dans son package.json, le prochain require échouera parce que ce script principal n'existe pas car il est mis en cache dans Module._pathCache

59voto

Rohan Singh Points 4213

Non, il n'y a vraiment aucun moyen de le faire. Voir aussi la documentation :

Des appels multiples à require('foo') peuvent ne pas entraîner le code du module à être exécuté plusieurs fois. Il s'agit d'une fonctionnalité importante. Grâce à elle, des objets "partiellement réalisés" peuvent être retournés, permettant ainsi aux dépendances transitives transitives d'être chargées même si elles provoquent des cycles.

Si vous souhaitez qu'un module exécute du code plusieurs fois, exportez alors une fonction, et appelez cette fonction.

Deux points ici :

  1. La raison pour laquelle cela est nécessaire est de permettre la résolution des cycles. Vous pouvez en voir un exemple ici : http://nodejs.org/docs/latest/api/modules.html#modules_cycles . Si vous pouviez invalider le cache d'une manière ou d'une autre, vous pourriez provoquer une boucle infinie en raison des dépendances circulaires. Même si vous pouvez être raisonnablement sûr que le code de votre application ne causera pas cela, cela peut se produire dans toutes les bibliothèques que vous utilisez.

  2. Comme l'indique la documentation, vous pouvez simplement envelopper la fonctionnalité dans une fonction que vous pouvez appeler dans chaque test. Il s'agit généralement d'un bon modèle de conception.

EDIT :

Je me suis trompé. Comme seppo0010 a fait remarquer, vous pouvez forcer un rechargement en supprimant le module mis en cache de l'application require.cache : http://nodejs.org/docs/latest/api/globals.html#globals_require_cache

Cela dit, je vous déconseille toujours de le faire pour les raisons mentionnées ci-dessus. Mais encore une fois, si vous êtes uniquement en le faisant dans votre couche de tests unitaires, vous pourrez peut-être vous en sortir sans chaînes de dépendances infinies.

5voto

daniellmb Points 10289

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

rewire ajoute un setter et un getter spéciaux aux modules afin que vous puissiez modifier leur comportement pour améliorer les tests unitaires. Vous pouvez

injecter des mocks pour d'autres modules ou globaux comme process faire fuir des variables privées remplacer des variables dans le module. rewire ne charge pas le fichier et n'évalue pas son contenu pour émuler le mécanisme require de node. En fait, il utilise le propre require de node 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 caféinomanes : rewire fonctionne aussi avec Coffee-script. Notez que dans ce cas CoffeeScript doit être listé dans vos devDependencies.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X