node.js wymaga pamięci podręcznej () - czy można unieważnić?


325

Z dokumentacji node.js:

Moduły są buforowane po pierwszym załadowaniu. Oznacza to (między innymi), że każde wywołanie wymagające („foo”) otrzyma dokładnie ten sam obiekt, jeśli zostanie rozwiązany w tym samym pliku.

Czy istnieje sposób na unieważnienie tej pamięci podręcznej? tzn. w przypadku testów jednostkowych chciałbym, aby każdy test działał na świeżym obiekcie.



Kolejny moduł NPM z obserwatorem: npmjs.com/package/updated-require
Jorge Fuentes González

Możliwe jest buforowanie zawartości pliku bez użycia wymagania i ewaluacja go dla różnych zakresów stackoverflow.com/questions/42376161/…
lonewarrior556

Odpowiedzi:


305

Zawsze możesz bezpiecznie usunąć wpis w pliku wymagania.cache bez problemu, nawet jeśli występują zależności cykliczne. Ponieważ po usunięciu wystarczy usunąć odwołanie do buforowanego obiektu modułu, a nie do samego obiektu modułu, obiekt modułu nie zostanie poddany GC, ponieważ w przypadku zależności cyklicznych nadal istnieje obiekt odwołujący się do tego obiektu modułu.

Załóżmy, że masz:

skrypt a.js:

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

i skrypt b.js:

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

kiedy to zrobisz:

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

dostaniesz:

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

teraz, jeśli edytujesz swoje b.js:

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

i robić:

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

dostaniesz:

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

===

Powyższe jest ważne, jeśli bezpośrednio uruchamiasz node.js. Jednak w przypadku korzystania z narzędzi, które mają swój własny system buforowania modułów, takich jak jest , poprawną instrukcją byłoby:

jest.resetModules();

2
czy mógłbyś wyjaśnić dlaczego, { ... a: undefined}kiedy wymagasz b.jstego po raz pierwszy? Oczekiwałbym równości 'a from a.js'. Dziękuję
ira

1
dlaczego jest niezdefiniowany?
Jeff P. Chacko

4
Późna odpowiedź, ale z tego, co zbieram, b[a]jest nieokreślony za pierwszym razem, ponieważ istnieje zależność cykliczna. a.jswymaga, b.jsco z kolei wymaga a.js. a.jsnie jest jeszcze w pełni załadowany i exports.anie został jeszcze zdefiniowany, więc nie b.jsdostaniesz nic.
nik10110

jakikolwiek sposób, aby to zrobić, jeśli używam require.main.require(path)zgodnie z opisem tutaj? stackoverflow.com/questions/10860244/…
Flion

186

Jeśli zawsze chcesz ponownie załadować moduł, możesz dodać tę funkcję:

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

a następnie użyj requireUncached('./myModule')zamiast wymagać.


6
Jest to idealne w połączeniu z fs.watchmetodą, która nasłuchuje zmian plików.
ph3nx

2
jakie jest ryzyko
Scarass

Mam to samo pytanie, jakie jest ryzyko korzystania z tego rozwiązania, a nie zaakceptowanej odpowiedzi?
rotimi-best

1
To naprawdę tak samo. W zależności od struktury kodu, może wystąpić awaria podczas próby jego ponownego zainicjowania. Dawny. jeśli moduł uruchomi serwer i nasłuchuje na porcie. Następnym razem, gdy będziesz potrzebować Odłączony moduł, zakończy się niepowodzeniem, ponieważ ten port jest już otwarty i tak dalej.
luff

133

Tak, możesz uzyskać dostęp do pamięci podręcznej, require.cache[moduleName]gdzie moduleNamejest nazwa modułu, do którego chcesz uzyskać dostęp. Usunięcie wpisu przez wywołanie delete require.cache[moduleName]spowoduje requirezaładowanie rzeczywistego pliku.

W ten sposób usuniesz wszystkie buforowane pliki powiązane z modułem:

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

Wykorzystanie byłoby:

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

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

Ponieważ ten kod używa tego samego resolvera require, po prostu określ, czego potrzebujesz.


„Uniks nie został zaprojektowany, aby powstrzymać użytkowników przed robieniem głupich rzeczy, ponieważ powstrzymałoby ich to także od robienia mądrych rzeczy”. - Doug Gwyn

Uważam, że powinien istnieć sposób na wykonanie jawnego ładowania modułu bez pamięci podręcznej.


17
+1 tylko za cytat Douga. Potrzebowałem kogoś, kto wyraziłby to, w co również wierzyłem :)
Poni,

1
Doskonała odpowiedź! Jeśli chcesz uruchomić replikację węzła z włączonym przeładowaniem, sprawdź tę treść .
gleitz

1
niesamowite. Dodałbym to do require.uncachefunkcji. `` // patrz github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) delete module.constructor ._pathCache [k];}); `` Powiedzmy, że wymagałeś modułu, następnie go odinstalowałeś, a następnie ponownie zainstalowałeś ten sam moduł, ale użyłeś innej wersji, która ma inny skrypt główny w pakiecie.json, następne wymaganie zakończy się niepowodzeniem, ponieważ ten skrypt główny nie istnieje, ponieważ jest zbuforowanyModule._pathCache
bentael

bzdury. mój komentarz jest okropny. Nie mogłem starannie dodać kodu w tym komentarzu i jest już za późno na edycję, więc odpowiedziałem. @Ben Barkay, gdybyś mógł edytować swoje pytanie, aby dodać mały fragment kodu do swojegorequire.uncache
bentael

Dzięki @bentael, dodałem to do mojej odpowiedzi.
Ben Barkay

39

Jest na to prosty moduł ( z testami )

Ten dokładnie problem występował podczas testowania naszego kodu ( usuwaj moduły buforowane, aby mogły być ponownie wymagane w nowym stanie ), dlatego przejrzeliśmy wszystkie sugestie osób z różnych pytań i odpowiedzi StackOverflow i przygotowaliśmy prosty moduł node.js ( z testami ):

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

Jak można się spodziewać, działa zarówno dla opublikowanych pakietów NPM, jak i lokalnie zdefiniowanych modułów. Windows, Mac, Linux itp.

Stan kompilacji codecov.io Kod Utrzymanie klimatu Status zależności devDependencies Status

W jaki sposób? ( użycie )

Użycie jest dość proste:

zainstalować

Zainstaluj moduł z npm:

npm install decache --save-dev

Użyj go w swoim kodzie:

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

Jeśli masz pytania lub potrzebujesz więcej przykładów, utwórz problem z GitHub: https://github.com/dwyl/decache/issues


1
Patrzyłem na to i wygląda na to, że mogę używać go podczas testowania, dzięki czemu mogę rozładować i ponownie załadować moduł w określonych warunkach, ale niestety jestem w pracy i moja firma unika licencji GPL. Chcę go używać tylko do testowania, więc wciąż go rozważam, ponieważ wygląda to bardzo pomocne.
Matt_JD,

@Matt_JD dzięki za opinie. którą wolisz licencję?
nelsonic

2
@Matt_JD Zaktualizowaliśmy licencję do MIT. Powodzenia w pracy! :-)
nelsonic

1
zadziałało to niesamowicie! Wystąpienie w tym repozytorium i poprawienie tej odpowiedzi.
aholt

1
bardzo polecam, działając dobrze na najnowszym v14.2.0 od dzisiaj
Thomazella

28

Dla każdego, kto natknie się na tego, kto używa Jest, ponieważ Jest to buforowanie własnego modułu, jest do tego wbudowana funkcja - po prostu upewnij się, że jest.resetModulesdziała np. po każdym z testów:

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

Znalazłem to po próbie użycia odszyfrowywania, jak sugerowano inną odpowiedź. Dzięki Anthony Garvan .

Dokumentacja funkcji tutaj .


1
Dziękuję bardzo za tę notatkę!
mjgpy3

2
Boże, jak długo eksperymentowałem, zanim to znalazłem .... dziękuję!
Tiago,

16

Rozwiązaniem jest użycie:

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

Znajdź tutaj kilka podstawowych wyjaśnień dla tych, którzy, podobnie jak ja, są nieco nowi:

Załóżmy, że masz example.jskatalog zastępczy w katalogu głównym:

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

To ci się require()podoba:

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

Jeśli następnie dodasz taką linię do example.js:

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

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

I kontynuuj w konsoli, moduł nie jest aktualizowany:

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

Wtedy możesz użyć delete require.cache[require.resolve()]wskazanego w odpowiedzi Luffa :

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

Pamięć podręczna jest czyszczona i ponownie require()przechwytuje zawartość pliku, ładując wszystkie bieżące wartości.


IMHO To jest najbardziej odpowiednia odpowiedź
Piyush Katariya

5

rewire świetnie nadaje się do tego przypadku użycia, do każdego połączenia dostajesz nową instancję. Łatwe wstrzykiwanie zależności do testowania jednostkowego node.js.

rewire dodaje specjalny moduł ustawiający i pobierający do modułów, dzięki czemu można modyfikować ich zachowanie w celu lepszego testowania jednostek. Możesz

wstrzykiwanie próbnych dla innych modułów lub globałów, takich jak prywatne wycieki procesu, zastępują zmienne w module. rewire nie ładuje pliku i nie analizuje zawartości w celu naśladowania mechanizmu wymaganego przez węzeł. W rzeczywistości wykorzystuje własny węzeł do załadowania modułu. W ten sposób moduł zachowuje się dokładnie tak samo w środowisku testowym, jak w normalnych okolicznościach (z wyjątkiem modyfikacji).

Dobra wiadomość dla wszystkich uzależnionych od kofeiny: rewire działa również z Coffee-Script. Zauważ, że w tym przypadku CoffeeScript musi być wymieniony w twoich devDependencies.


4

Dodałbym do odpowiedzi Luffa jeszcze jedną linię i zmieniłbym nazwę parametru:

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

Czy to ma sprawić, że funkcja będzie działać w submodułach? Miły! Krótszym sposobem na usunięcie modułu z tablicy module.children jest użycie funkcji filtrującej: module.children = module.children.filter (function (child) {return child.id! == requ.resolve (_module);}) ;
luff

4

Tak, możesz unieważnić pamięć podręczną.

Pamięć podręczna jest przechowywana w obiekcie o nazwie requ.cache, do którego można uzyskać bezpośredni dostęp według nazw plików (np. - /projects/app/home/index.jsw przeciwieństwie do tego, ./homektórego użyłbyś w require('./home')instrukcji).

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

Nasz zespół uznał następujący moduł za przydatny. Aby unieważnić niektóre grupy modułów.

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


3

Nie mogłem starannie dodać kodu w komentarzu odpowiedzi. Ale użyłbym odpowiedzi @Ben Barkay, a następnie dodałbym to do require.uncachefunkcji.

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

Powiedzmy, że potrzebujesz modułu, następnie go odinstalowałeś, a następnie ponownie zainstalowałeś ten sam moduł, ale użyłeś innej wersji, która ma inny skrypt główny w pakiecie.json, następne wymaganie zakończy się niepowodzeniem, ponieważ ten skrypt główny nie istnieje, ponieważ jest buforowany w Module._pathCache


3

Nie jestem w 100% pewien, co rozumiesz przez „unieważnienie”, ale możesz dodać poniższe requireinstrukcje, aby wyczyścić pamięć podręczną:

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

Zaczerpnięte z komentarza @ Dancrumb tutaj


2

requireUncached ze ścieżką względną: 🔥

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

module.exports = requireUncached;

wywołanie metody wymaga użycia ze ścieżką względną:

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

1

Poniższa dwuetapowa procedura działa dla mnie idealnie.

Po zmianie Modelpliku, tj. 'mymodule.js'Dynamicznie, musisz najpierw usunąć wstępnie skompilowany model w modelu mangusty, a następnie ponownie go załadować za pomocą wymagania wymagającego przeładowania

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

Jeśli chodzi o testy jednostkowe, innym dobrym narzędziem do użycia jest proxyquire . Za każdym razem, gdy będziesz prosić o moduł, spowoduje to unieważnienie pamięci podręcznej modułu i buforowanie nowego. Pozwala także modyfikować moduły wymagane przez testowany plik.


0

Zrobiłem mały moduł, aby usunąć moduł z pamięci podręcznej po załadowaniu. Wymusza to ponownej oceny modułu przy następnym użyciu. Zobacz 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: możesz również umieścić „samozniszczenie” w samym module. Zobacz https://github.com/bahmutov/unload-me

PSS: więcej sztuczek z Node wymaga w moim https://glebbahmutov.com/blog/hacking-node-require/

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.