node.js gerektirir () önbellek - geçersiz kılmak mümkün mü?


325

Node.js belgelerinden:

Modüller ilk yüklendikten sonra önbelleğe alınır. Bu, (diğer şeylerin yanı sıra), ihtiyaç duyulan her çağrının ('foo') aynı dosyaya çözümlenmesi durumunda, tam olarak aynı nesneyi döndüreceği anlamına gelir.

Bu önbelleği geçersiz kılmanın bir yolu var mı? birim testi için, her testin yeni bir nesne üzerinde çalışmasını istiyorum.



Bir gözlemciye sahip başka bir NPM modülü: npmjs.com/package/updated-require
Jorge Fuentes González

Dosya içeriğini ihtiyaç kullanmadan önbelleğe almak ve farklı kapsamlar için değerlendirmek mümkündür stackoverflow.com/questions/42376161/…
lonewarrior556

Yanıtlar:


305

Dairesel bağımlılıklar olsa bile, requir.cache içindeki bir girdiyi sorunsuz bir şekilde silebilirsiniz. Sildiğinizde, modül nesnesinin kendisini değil, yalnızca önbelleğe alınmış modül nesnesine bir başvuruyu sildiğiniz için, dairesel bağımlılıklar söz konusu olduğunda, hala bu modül nesnesine başvuran bir nesne vardır.

Varsayalım:

komut dosyası a.js:

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

ve b.js komut dosyası:

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

ne zaman yaparsın:

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

Alacaksın:

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

şimdi b.js'nizi düzenlerseniz:

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

ve yap:

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

Alacaksın:

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

===

Yukarıdakiler doğrudan node.js'yi çalıştırıyorsa geçerlidir. Ancak, jest gibi kendi modül önbellekleme sistemine sahip araçlar kullanılıyorsa , doğru ifade şöyle olacaktır:

jest.resetModules();

2
İlk kez neden { ... a: undefined}ihtiyaç duyduğunuzu açıklar b.jsmısınız? Eşit olmayı beklerdim 'a from a.js'. Teşekkür ederim
ira

1
neden tanımsız?
Jeff P Chacko

4
Geç cevap, ancak topladığım şeyden b[a], dairesel bir bağımlılık olduğu için ilk kez tanımsız. a.jsgerektiren b.jsgerektirir a.js. a.jshenüz tam olarak yüklenmedi ve exports.ahenüz tanımlanmadı, bu yüzden b.jsget hiçbir şey.
nik10110

require.main.require(path)burada açıklandığı gibi kullanıyorsam bunu yapmanın herhangi bir yolu var mı? stackoverflow.com/questions/10860244/…
Flion

186

Modülünüzü her zaman yeniden yüklemek istiyorsanız, bu işlevi ekleyebilirsiniz:

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

ve sonra requir requireUncached('./myModule')yerine kullanın .


6
Bu, fs.watchdosya değişikliklerini dinleyen yöntemle birlikte mükemmeldir .
ph3nx

2
risk nedir?
Scarass

Aynı sorum var, kabul edilen yanıtı değil, bu çözümü kullanma riski nedir?
rotimi-best

1
Gerçekten aynı. Kodun nasıl yapılandırıldığına bağlı olarak, kodu yeniden başlatmaya çalıştığınızda işler bozulabilir. Ör. modül bir sunucu başlatırsa ve bir bağlantı noktasını dinlerse. Modülü bir sonraki açışınızda, bu bağlantı noktası zaten açıldığından beri başarısız olur ve bu böyle devam eder.
luff

133

Evet, önbelleğe erişmek istediğiniz modülün adının require.cache[moduleName]bulunduğu yerden moduleNameerişebilirsiniz. Bir girdiyi arayarak silmek gerçek dosyanın yüklenmesine delete require.cache[moduleName]neden olur require.

Modülle ilişkili tüm önbellek dosyalarını şu şekilde kaldırabilirsiniz:

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

Kullanımı:

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

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

Bu kod aynı çözümleyiciyi kullandığı için require, gereksinim duyduğunuz her şeyi belirtin.


"Unix, kullanıcılarının aptalca şeyler yapmasını engellemek için tasarlanmadı, çünkü bu onların akıllıca şeyler yapmalarını da engelleyecektir." - Doug Gwyn

Açık bir önbelleksiz modül yükleme gerçekleştirmek için bir yol olması gerektiğini düşünüyorum .


17
Sadece Doug'un teklifi için +1. Ayrıca inandığım şeyi söyleyecek birine ihtiyacım vardı :)
Poni

1
Mükemmel cevap! Yeniden yükleme etkinken bir düğüm repl'i başlatmak istiyorsanız bu özeti inceleyin .
gleitz

1
müthiş. Bunu require.uncacheişleve eklerdim. `` // bkz. github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) module.constructor öğesini silin ._pathCache [k];}); `` Bir modüle ihtiyaç duyduğunuzu, ardından kaldırdığınızı, ardından aynı modülü yeniden kurduğunuzu, ancak paketinde farklı bir ana komut dosyasına sahip farklı bir sürüm kullandığınızı varsayalım, çünkü bu ana komut dosyası mevcut olmadığından bir sonraki gereksinim başarısız olacaktır. içinde önbelleklenmişModule._pathCache
bentael

bok. benim yorumum korkunç. Bu yoruma düzgün bir şekilde kod ekleyemedim ve düzenlemek için çok geç, bu yüzden cevap verdim. @Ben Barkay, küçük kod snippet'ini eklemek için sorunuzu düzenleyebilirsenizrequire.uncache
bentael

Teşekkürler @bentael, bunu cevabıma ekledim.
Ben Barkay

39

Bunun için Basit Bir Modül var ( testlerle )

Kodumuzu test ederken tam olarak bu sorunu yaşadık ( önbelleğe alınmış modülleri silin, böylece yeni bir durumda yeniden gerekli olabilirler ), bu nedenle çeşitli StackOverflow Sorular ve Yanıtları'ndaki tüm önerileri inceledik ve basit bir node.js modülünü bir araya getirdik ( testlerle ):

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

Beklediğiniz gibi, hem yayınlanan npm paketleri hem de yerel olarak tanımlanan modüller için çalışır. Windows, Mac, Linux vb.

Derleme Durumu codecov.io Kod İklim koruma Bağımlılıklar Durumu devDependencies Status

Nasıl? ( kullanım )

Kullanımı oldukça basit:

Yüklemek

Modülü npm'den kurun:

npm install decache --save-dev

Kodunuzda kullanın:

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

Herhangi bir sorunuz varsa veya daha fazla örneğe ihtiyacınız varsa, lütfen bir GitHub sorunu oluşturun: https://github.com/dwyl/decache/issues


1
Buna bakıyordum ve belirli koşullar altında bir modülü boşaltabilmem ve yeniden yükleyebilmem için test sırasında kullanmam gerçekten harika görünüyor, ancak maalesef işteyim ve şirketim GPL lisanslarından uzaklaşıyor. Ben sadece test için kullanmak istiyorum, bu yüzden hala çok yararlı görünüyor çünkü hala düşünüyorum.
Matt_JD

@ Matt_JD Geri bildiriminiz için teşekkür ederiz. hangi lisansı tercih edersin?
Nelsonel

2
@Matt_JD Lisansı MIT olarak güncelledik. Çalışmanızda bol şans! :-)
nelsonic

1
bu inanılmaz çalıştı! Bu repoya yıldız ekleyerek ve bu cevabı iptal ederek.
aholt

1
tavsiye ederim, bugün itibariyle son v14.2.0'da iyi çalışıyor
Thomazella

28

Jest kullanan herkesle karşılaşan herkes için, Jest kendi modül önbelleğe alma işlemini gerçekleştirdiğinden, bunun için yerleşik bir işlev vardır - jest.resetModulesörneğin çalıştığından emin olun . her testinizden sonra:

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

Başka bir yanıt önerilen decache kullanmaya çalıştıktan sonra bunu buldum . Anthony Garvan'a teşekkürler .

İşlev belgeleri burada .


1
Bu not için çok teşekkür ederim!
mjgpy3

2
Tanrım bunu bulmadan önce ne kadar deney yaptım .... teşekkür ederim!
Tiago

16

Çözümler:

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

Benim gibi bu konuda biraz yeni olanlar için bazı temel açıklamaları burada bulabilirsiniz:

Dizininizin example.jskökünde sahte bir dosyanız olduğunu varsayalım :

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

Sonra bunu require()beğendin:

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

Daha sonra şuna böyle bir satır eklerseniz example.js:

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

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

Konsolda devam edin, modül güncellenmez:

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

İşte o zaman luff cevabındadelete require.cache[require.resolve()] belirtilen kullanabilirsiniz :

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

Böylece önbellek temizlenir ve require()tüm geçerli değerleri yükleyerek dosyanın içeriğini tekrar yakalar.


IMHO Bu en uygun cevaptır
Piyush Katariya

5

rewire bu kullanım için harika, her çağrı ile yeni bir örnek alırsınız. Node.js birim testi için kolay bağımlılık enjeksiyonu.

rewire, modüllere özel bir ayarlayıcı ve alıcı ekler, böylece daha iyi birim testi için davranışlarını değiştirebilirsiniz. Yapabilirsin

diğer modüllere ya da süreç sızıntısı özel değişkenleri gibi globallere sahte enjekte etmek, modül içindeki değişkenleri geçersiz kılar. rewire, dosyayı yüklemez ve düğümün ihtiyaç mekanizmasını taklit etmek için içeriği değerlendirmez. Aslında modülü yüklemek için kendi düğümünü kullanır. Böylece modülünüz test koşullarınızda normal koşullarda olduğu gibi çalışır (değişiklikleriniz hariç).

Tüm kafein bağımlılarına müjde: rewire Coffee-Script ile de çalışır. Bu durumda CoffeeScript'in geliştirici bağımlılıklarınızda listelenmesi gerektiğini unutmayın.


4

Ben luff'ın cevabına bir satır daha ekleyip parametre adını değiştirirdim:

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

Yani bu fonksiyonun alt modüllerde çalışmasını sağlamak mı? Güzel! Modülü module.children dizisinden kaldırmanın daha kısa bir yolu bir filtre işlevi kullanmaktır: module.children = module.children.filter (function (child) {return child.id! == requir.resolve (_module);}) ;
luff

4

Evet, önbelleği geçersiz kılabilirsiniz.

Önbellek, dosya adlarına göre doğrudan erişebileceğiniz (örneğin , bir ifadede kullanacağınız durumun /projects/app/home/index.jsaksine) requir.cache adlı bir nesnede saklanır . ./homerequire('./home')

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

Ekibimiz aşağıdaki modülü yararlı buldu. Belirli modül gruplarını geçersiz kılmak.

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


3

Yanıtın yorumuna düzgün bir şekilde kod ekleyemedim. Ama @Ben Barkay'ın cevabını kullanırdım ve bunu işleve eklerdim require.uncache.

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

Bir modüle ihtiyaç duyduğunuzu, daha sonra kaldırdığınızı, ardından aynı modülü yeniden kurduğunuzu ancak paketinde farklı bir ana komut dosyası olan farklı bir sürüm kullandığınızı varsayalım. Module._pathCache


3

'Geçersiz' olarak kastettiğinizden% 100 emin değilim, ancak requireönbelleği temizlemek için aşağıdaki ifadeleri ekleyebilirsiniz :

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

Dancrumb yorumuna @ Alındığı burada


2

requireUncached göreli yol ile: 🔥

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

module.exports = requireUncached;

göreli yolla önbelleğe alınmış:

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

1

İki adımlı prosedürü takip etmek benim için mükemmel çalışıyor.

Değiştirdikten sonra Model, yani dosyayı 'mymodule.js'dinamik, içeri precompiled modelini sil gerek firavunfaresi modeli kullanarak yeniden ilk önce gerektirir-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

Birim testleri içinse, başka bir iyi araç da proxyquire'dir . Modülü her aradığınızda, modül önbelleğini geçersiz kılacak ve yenisini önbelleğe alacaktır. Ayrıca sınadığınız dosyanın gerektirdiği modülleri değiştirmenize de olanak tanır.


0

Yükledikten sonra modülü önbellekten silmek için küçük bir modül yaptım. Bu, bir dahaki sefere modülün yeniden değerlendirilmesini zorlar. Bkz. 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: Ayrıca modülün kendisine "kendini imha" koyabilirsiniz. Bkz. Https://github.com/bahmutov/unload-me

PSS: Düğüm ile ilgili daha fazla numara https://glebbahmutov.com/blog/hacking-node-require/

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.