node.js require () cache - möglich ungültig zu machen?


325

Aus der Dokumentation zu node.js:

Module werden nach dem ersten Laden zwischengespeichert. Dies bedeutet (unter anderem), dass bei jedem Aufruf ('foo') genau dasselbe Objekt zurückgegeben wird, wenn es in dieselbe Datei aufgelöst wird.

Gibt es eine Möglichkeit, diesen Cache ungültig zu machen? dh für Unit-Tests möchte ich, dass jeder Test an einem neuen Objekt arbeitet.



Ein weiteres NPM-Modul mit einem Beobachter: npmjs.com/package/updated-require
Jorge Fuentes González

Es ist möglich, den Dateiinhalt ohne Verwendung von require zwischenzuspeichern und für verschiedene Bereiche zu evaluieren stackoverflow.com/questions/42376161/…
lonewarrior556

Antworten:


304

Sie können einen Eintrag in require.cache jederzeit problemlos löschen, auch wenn zirkuläre Abhängigkeiten bestehen. Da Sie beim Löschen nur einen Verweis auf das zwischengespeicherte Modulobjekt und nicht auf das Modulobjekt selbst löschen, wird das Modulobjekt nicht gced, da bei zirkulären Abhängigkeiten immer noch ein Objekt auf dieses Modulobjekt verweist.

Angenommen, Sie haben:

Skript a.js:

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

und Skript b.js:

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

wenn Sie das tun:

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

Sie erhalten:

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

jetzt, wenn Sie Ihre b.js bearbeiten:

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

und TU:

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

Sie erhalten:

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

===

Das Obige gilt, wenn node.js direkt ausgeführt wird. Wenn Sie jedoch Tools verwenden, die über ein eigenes Modul-Caching-System verfügen, z. B. jest , lautet die richtige Aussage:

jest.resetModules();

2
Könnten Sie bitte erklären, warum, { ... a: undefined}wenn Sie b.jszum ersten Mal benötigen ? Ich würde erwarten, gleich zu sein 'a from a.js'. Vielen Dank
ira

1
Warum ist ein undefinierter?
Jeff P Chacko

4
Späte Antwort, aber von dem, was ich erfahre, b[a]ist das erste Mal undefiniert, seit es eine zirkuläre Abhängigkeit gibt. a.jserfordert b.jswas wiederum erfordert a.js. a.jsist noch nicht vollständig geladen und muss exports.anoch definiert werden, also b.jsnichts bekommen.
Nik10110

require.main.require(path)Wie kann ich das tun, wenn ich es wie hier beschrieben verwende? stackoverflow.com/questions/10860244/…
Flion

186

Wenn Sie Ihr Modul immer neu laden möchten, können Sie folgende Funktion hinzufügen:

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

und dann verwenden requireUncached('./myModule')statt erfordern.


6
Dies ist perfekt in Kombination mit der fs.watchMethode, die auf Dateiänderungen wartet.
Ph3NX

2
Was ist das Risiko?
Scarass

Dieselbe Frage, die ich habe, wie hoch ist das Risiko, diese Lösung zu verwenden, und nicht die akzeptierte Antwort?
Rotimi-Best

1
Es ist wirklich das gleiche. Abhängig von der Struktur des Codes kann es zu Abstürzen kommen, wenn Sie versuchen, ihn erneut zu initialisieren. Ex. wenn das Modul einen Server startet und einen Port abhört. Wenn Sie das Modul das nächste Mal nicht zwischengespeichert benötigen, schlägt dies fehl, da dieser Port bereits geöffnet ist, und so weiter.
Luff

133

Ja, Sie können auf den Cache zugreifen, require.cache[moduleName]indem Sie moduleNameden Namen des Moduls angeben, auf das Sie zugreifen möchten. Wenn Sie einen Eintrag durch Aufrufen löschen, delete require.cache[moduleName]wird requiredie eigentliche Datei geladen.

So würden Sie alle zwischengespeicherten Dateien entfernen, die dem Modul zugeordnet sind:

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

Verwendung wäre:

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

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

Da dieser Code denselben Resolver requireverwendet, geben Sie einfach an, was Sie benötigen.


"Unix wurde nicht entwickelt, um seine Benutzer davon abzuhalten, dumme Dinge zu tun, da dies sie auch davon abhalten würde, kluge Dinge zu tun." - Doug Gwyn

Ich denke, dass es eine Möglichkeit geben sollte , ein explizites Laden von Modulen ohne Zwischenspeicher durchzuführen.


17
+1 nur für Dougs Zitat. Ich brauchte jemanden, der ausdrückte, woran ich auch glaubte :)
Poni

1
Hervorragende Antwort! Wenn Sie eine Knotenreplikation mit aktiviertem Neuladen starten möchten, lesen Sie diese Übersicht .
Gleitz

1
genial. Ich würde dies der require.uncacheFunktion hinzufügen . `` `// siehe github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) delete module.constructor ._pathCache [k];}); `` `Angenommen, Sie haben ein Modul benötigt, es dann deinstalliert und dann dasselbe Modul neu installiert, aber eine andere Version verwendet, deren package.json ein anderes Hauptskript enthält. Die nächste Anforderung schlägt fehl, da dieses Hauptskript nicht vorhanden ist, weil Es ist zwischengespeichertModule._pathCache
Bentael

Mist. Mein Kommentar ist schrecklich. Ich konnte diesem Kommentar keinen sauberen Code hinzufügen und es ist zu spät zum Bearbeiten, also antwortete ich. @ Ben Barkay, wenn Sie Ihre Frage bearbeiten könnten, um den kleinen require.uncache
Codeausschnitt

Danke @bentael, ich habe dies zu meiner Antwort hinzugefügt.
Ben Barkay

39

Dafür gibt es ein einfaches Modul ( mit Tests )

Wir hatten genau dieses Problem beim Testen unseres Codes ( löschen Sie zwischengespeicherte Module, damit sie in einem neuen Zustand wieder benötigt werden ), also haben wir alle Vorschläge von Personen zu den verschiedenen Fragen und Antworten zu StackOverflow überprüft und ein einfaches node.js-Modul zusammengestellt ( mit Tests ):

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

Funktioniert erwartungsgemäß sowohl für veröffentlichte npm-Pakete als auch für lokal definierte Module. Windows, Mac, Linux usw.

Build-Status codecov.io Code Climate Wartbarkeit Abhängigkeitsstatus devDependencies Status

Wie? ( Verwendung )

Die Verwendung ist ziemlich einfach:

Installieren

Installieren Sie das Modul von npm:

npm install decache --save-dev

Verwenden Sie es in Ihrem 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)

Wenn Sie Fragen haben oder weitere Beispiele benötigen, erstellen Sie ein GitHub-Problem: https://github.com/dwyl/decache/issues


1
Ich habe mir das angeschaut und es sieht für mich sehr gut aus, es beim Testen zu verwenden, damit ich ein Modul unter bestimmten Bedingungen entladen und neu laden kann, aber leider bin ich auf der Arbeit und mein Unternehmen scheut GPL-Lizenzen. Ich möchte es nur zum Testen verwenden, deshalb denke ich immer noch darüber nach, weil es so hilfreich aussieht.
Matt_JD

@Matt_JD danke für dein Feedback. Welche Lizenz würden Sie bevorzugen?
Nelsonic

2
@Matt_JD Wir haben die Lizenz auf MIT aktualisiert. Viel Glück mit deiner Arbeit! :-)
Nelsonic

1
das hat erstaunlich funktioniert! Darsteller dieses Repos und Abstimmung dieser Antwort.
Aholt

1
Sehr zu empfehlen, läuft ab heute auf der neuesten Version 14.2.0 einwandfrei
Thomazella

28

Für alle, die darauf stoßen und Jest verwenden, da Jest ein eigenes Modul-Caching durchführt, gibt es hierfür eine integrierte Funktion - stellen Sie einfach sicher, dass jest.resetModulesz. nach jedem Ihrer Tests:

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

Ich habe dies gefunden, nachdem ich versucht habe, decache wie in einer anderen vorgeschlagenen Antwort zu verwenden. Vielen Dank an Anthony Garvan .

Funktionsdokumentation hier .


1
Vielen Dank für diesen Hinweis!
mjgpy3

2
Gott, wie lange habe ich experimentiert, bevor ich das gefunden habe ... danke!
Tiago

16

Die Lösungen sind zu verwenden:

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

Hier finden Sie einige grundlegende Erklärungen für diejenigen, die wie ich ein bisschen neu darin sind:

Angenommen, Sie haben eine Dummy- example.jsDatei im Stammverzeichnis Ihres Verzeichnisses:

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

Dann require()gefällt dir das:

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

Wenn Sie dann eine Zeile wie folgt hinzufügen example.js:

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

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

Und weiter in der Konsole, das Modul wird nicht aktualisiert:

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

Das ist, wenn Sie delete require.cache[require.resolve()]in Luffs Antwort angegeben verwenden können :

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

Der Cache wird also bereinigt und require()erfasst den Inhalt der Datei erneut, wobei alle aktuellen Werte geladen werden.


IMHO Dies ist die am besten geeignete Antwort
Piyush Katariya

5

neu verdrahten eignet sich hervorragend für diesen Anwendungsfall. Mit jedem Aufruf erhalten Sie eine neue Instanz. Einfache Abhängigkeitsinjektion für Unit-Tests von node.js.

rewire fügt Modulen einen speziellen Setter und Getter hinzu, damit Sie deren Verhalten für bessere Unit-Tests ändern können. Du darfst

Inject-Mocks für andere Module oder globale Elemente wie Prozesslecks überschreiben private Variablen Variablen innerhalb des Moduls. rewire lädt die Datei nicht und wertet den Inhalt aus, um den erforderlichen Mechanismus des Knotens zu emulieren. Tatsächlich verwendet es die knoteneigenen Anforderungen, um das Modul zu laden. Somit verhält sich Ihr Modul in Ihrer Testumgebung genauso wie unter normalen Umständen (mit Ausnahme Ihrer Änderungen).

Gute Nachrichten für alle Koffeinsüchtigen: rewire funktioniert auch mit Coffee-Script. Beachten Sie, dass in diesem Fall CoffeeScript in Ihren devDependencies aufgeführt sein muss.


4

Ich würde zu Luffs Antwort eine weitere Zeile hinzufügen und den Parameternamen ändern:

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

Damit soll die Funktion in Submodulen funktionieren? Nett! Eine kürzere Möglichkeit, das Modul aus dem Array module.children zu entfernen, ist die Verwendung einer Filterfunktion: module.children = module.children.filter (function (child) {return child.id! == require.resolve (_module);}) ;;
Luff

4

Ja, Sie können den Cache ungültig machen.

Der Cache wird in einem Objekt namens require.cache gespeichert, auf das Sie direkt anhand von Dateinamen zugreifen können (z. B. - /projects/app/home/index.js im Gegensatz zu ./homedem, den Sie in einer require('./home')Anweisung verwenden würden).

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

Unser Team hat das folgende Modul nützlich gefunden. Um bestimmte Gruppen von Modulen ungültig zu machen.

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


3

Ich konnte dem Kommentar einer Antwort keinen sauberen Code hinzufügen. Aber ich würde die Antwort von @Ben Barkay verwenden und diese dann zur require.uncacheFunktion hinzufügen .

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

Angenommen, Sie haben ein Modul benötigt, es dann deinstalliert und dann dasselbe Modul neu installiert, aber eine andere Version verwendet, deren package.json ein anderes Hauptskript enthält. Die nächste Anforderung schlägt fehl, da dieses Hauptskript nicht vorhanden ist, weil es zwischengespeichert ist Module._pathCache


3

Ich bin nicht 100% sicher, was Sie unter "ungültig machen" verstehen, aber Sie können über den requireAnweisungen Folgendes hinzufügen , um den Cache zu leeren:

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

Entnommen aus dem Kommentar von @ Dancrumb hier


2

requireUncached mit relativem Pfad: 🔥

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

module.exports = requireUncached;

Rufen Sie requireUncached mit dem relativen Pfad auf:

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

1

Das Befolgen des zweistufigen Verfahrens funktioniert für mich perfekt.

Nach dem Ändern der ModelDatei, dh 'mymodule.js'dynamisch, müssen Sie zuerst das vorkompilierte Modell im Mungomodell löschen und dann mit require-reload neu laden

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

Wenn es sich um Unit-Tests handelt, ist Proxyquire ein weiteres gutes Tool . Jedes Mal, wenn Sie das Modul als Proxy anfordern, wird der Modulcache ungültig und ein neuer zwischengespeichert. Außerdem können Sie die Module ändern, die für die zu testende Datei erforderlich sind.


0

Ich habe ein kleines Modul erstellt, um das Modul nach dem Laden aus dem Cache zu löschen. Dies erzwingt eine Neubewertung des Moduls, wenn es das nächste Mal benötigt wird. Siehe 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: Sie können auch "Selbstzerstörung" in das Modul selbst einfügen. Sehen https://github.com/bahmutov/unload-me

PSS: Weitere Tricks mit Node sind in meinem https://glebbahmutov.com/blog/hacking-node-require/ erforderlich.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.