node.js memerlukan () cache - mungkin untuk membatalkan


325

Dari dokumentasi node.js:

Modul di-cache setelah pertama kali di-load. Ini berarti (antara lain) bahwa setiap panggilan yang diperlukan ('foo') akan mendapatkan objek yang sama persis dikembalikan, jika itu akan menyelesaikan ke file yang sama.

Apakah ada cara untuk membatalkan cache ini? yaitu untuk pengujian unit, saya ingin setiap tes bekerja pada objek baru.




Dimungkinkan untuk melakukan cache konten file tanpa menggunakan
persyaratan

Jawaban:


305

Anda selalu dapat menghapus entri dengan aman di cache.cache tanpa masalah, bahkan ketika ada dependensi melingkar. Karena ketika Anda menghapus, Anda hanya menghapus referensi ke objek modul yang di-cache, bukan objek modul itu sendiri, objek modul tidak akan GCed karena dalam kasus dependensi melingkar, masih ada objek referensi objek modul ini.

Misalkan Anda memiliki:

skrip a.js:

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

dan skrip b.js:

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

saat kamu melakukan:

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

kamu akan mendapatkan:

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

sekarang jika Anda mengedit b.js Anda:

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

dan lakukan:

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

kamu akan mendapatkan:

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

===

Di atas berlaku jika langsung menjalankan node.js. Namun, jika menggunakan alat yang memiliki sistem caching modul sendiri, seperti jest , pernyataan yang benar adalah:

jest.resetModules();

2
bisa tolong jelaskan mengapa { ... a: undefined}ketika membutuhkan b.jsuntuk pertama kalinya? Saya harapkan sama 'a from a.js'. Terima kasih
ira

1
mengapa tidak terdefinisi?
Jeff P Chacko

4
Terlambat membalas, tetapi dari apa yang saya kumpulkan b[a]tidak terdefinisi untuk pertama kalinya karena ada ketergantungan melingkar. a.jsmembutuhkan b.jsyang pada gilirannya membutuhkan a.js. a.jsbelum dimuat penuh dan exports.abelum ditentukan, jadi b.jsdapatkan apa-apa.
nik10110

cara apa pun untuk melakukan ini jika saya menggunakan require.main.require(path)seperti yang dijelaskan di sini? stackoverflow.com/questions/10860244/…
Flion

186

Jika Anda selalu ingin memuat ulang modul Anda, Anda bisa menambahkan fungsi ini:

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

dan kemudian gunakan requireUncached('./myModule')alih-alih membutuhkan.


6
Ini sempurna dalam kombinasi dengan fs.watchmetode yang mendengarkan perubahan file.
ph3nx

2
apa risikonya?
Scarass

Pertanyaan yang sama saya miliki, apa risiko menggunakan solusi ini dan bukan jawaban yang diterima?
rotimi-best

1
Sama saja kok. Bergantung pada bagaimana kode disusun, hal-hal mungkin macet ketika Anda mencoba menginisialisasi ulang Ex. jika modul memulai server, dan mendengarkan port. Lain kali Anda membutuhkan modul Terpasang, ia akan gagal karena port itu sudah dibuka, dan sebagainya.
luff

133

Ya, Anda dapat mengakses cache melalui require.cache[moduleName]mana moduleNameadalah nama dari modul yang ingin Anda akses. Menghapus entri dengan menelepon delete require.cache[moduleName]akan menyebabkan requirememuat file yang sebenarnya.

Ini adalah cara Anda menghapus semua file dalam cache yang terkait dengan modul:

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

Penggunaan akan:

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

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

Karena kode ini menggunakan resolver yang sama require, tentukan apa saja yang Anda perlukan.


"Unix tidak dirancang untuk menghentikan penggunanya dari melakukan hal-hal bodoh, karena itu juga akan menghentikan mereka dari melakukan hal-hal yang pintar." - Doug Gwyn

Saya pikir seharusnya ada cara untuk melakukan pemuatan modul yang tidak di-cache secara eksplisit.


17
+1 hanya untuk kutipan Doug. Saya membutuhkan seseorang untuk mengungkapkan apa yang saya juga yakini :)
Poni

1
Jawaban yang sangat bagus! Jika Anda ingin memulai penggantian simpul dengan memuat ulang yang diaktifkan periksa inti ini .
Gleitz

1
luar biasa. Saya akan menambahkan ini ke require.uncachefungsi. `` `// lihat github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (fungsi (k) {if (k.indexOf (moduleName)> 0) hapus module.constructor ._pathCache [k];}); `` `Katakanlah Anda memerlukan sebuah modul, lalu uninstall, lalu instal kembali modul yang sama tetapi gunakan versi berbeda yang memiliki skrip utama berbeda dalam paketnya. Json, permintaan berikutnya akan gagal karena skrip utama itu tidak ada karena itu di-cache diModule._pathCache
bentael

sampah. komentar saya mengerikan. Saya tidak dapat menambahkan kode dengan rapi dalam komentar ini dan sudah terlambat untuk mengedit, jadi saya menjawab. @ Ben Barkay jika Anda dapat mengedit pertanyaan Anda untuk menambahkan potongan kecil kode ke Andarequire.uncache
bentael

Terima kasih @Bentael, saya telah menambahkan ini ke jawaban saya.
Ben Barkay

39

Ada Modul Sederhana untuk itu ( dengan tes )

Kami memiliki masalah yang tepat saat menguji kode kami ( hapus modul yang di-cache sehingga mereka dapat diminta kembali dalam keadaan baru ) sehingga kami meninjau semua saran orang pada berbagai Pertanyaan & Jawaban StackOverflow dan menyusun modul node.js sederhana ( dengan tes ):

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

Seperti yang Anda harapkan, bekerja untuk kedua paket NPM diterbitkan dan lokal modul didefinisikan. Windows, Mac, Linux, dll.

Status Bangun codecov.io Kode pemeliharaan iklim Status Ketergantungan Status devDependencies

Bagaimana? ( penggunaan )

Penggunaannya cukup sederhana:

Install

Instal modul dari npm:

npm install decache --save-dev

Gunakan dalam kode Anda:

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

Jika Anda memiliki pertanyaan atau memerlukan lebih banyak contoh, silakan buat masalah GitHub: https://github.com/dwyl/decache/issues


1
Saya telah melihat ini dan itu terlihat sangat bagus untuk saya gunakan saat pengujian sehingga saya dapat membongkar dan memuat kembali modul dalam kondisi tertentu, tetapi sayangnya saya sedang bekerja dan perusahaan saya menjauh dari lisensi GPL. Saya hanya ingin menggunakannya untuk pengujian jadi saya masih mempertimbangkannya karena terlihat sangat membantu.
Matt_JD

@Matt_JD terima kasih atas tanggapan Anda. lisensi mana yang Anda inginkan?
nelsonic

2
@Matt_JD Kami telah memperbarui lisensi untuk MIT. Semoga berhasil dengan pekerjaan Anda! :-)
nelsonic

1
ini bekerja luar biasa! Membintangi repo ini dan memutakhirkan jawaban ini.
aholt

1
sangat merekomendasikan, berjalan baik pada v14.2.0 terbaru pada hari ini
Thomazella

28

Bagi siapa pun yang menemukan ini yang menggunakan Jest, karena Jest melakukan caching modulnya sendiri, ada fungsi bawaan untuk ini - pastikan jest.resetModulesdijalankan misalnya. setelah setiap tes Anda:

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

Menemukan ini setelah mencoba menggunakan decache seperti jawaban lain yang disarankan. Terima kasih kepada Anthony Garvan .

Dokumentasi fungsi di sini .


1
Terima kasih banyak atas catatan ini!
mjgpy3

2
Tuhan berapa lama saya bereksperimen sebelum saya menemukan ini .... terima kasih!
Tiago

16

Solusinya adalah menggunakan:

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

Temukan di sini beberapa penjelasan dasar bagi mereka yang, seperti saya, agak baru dalam hal ini:

Misalkan Anda memiliki example.jsfile dummy di root direktori Anda:

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

Maka Anda require()suka ini:

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

Jika Anda menambahkan baris seperti ini ke example.js:

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

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

Dan lanjutkan di konsol, modul tidak diperbarui:

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

Saat itulah Anda dapat menggunakan delete require.cache[require.resolve()]ditunjukkan dalam jawaban luff :

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

Jadi cache dibersihkan dan require()menangkap konten file lagi, memuat semua nilai saat ini.


IMHO Ini adalah jawaban yang paling tepat
Piyush Katariya

5

rewire sangat bagus untuk kasus penggunaan ini, Anda mendapatkan contoh baru dengan setiap panggilan. Injeksi ketergantungan yang mudah untuk pengujian unit node.js.

rewire menambahkan setter dan pengambil khusus ke modul sehingga Anda dapat memodifikasi perilaku mereka untuk pengujian unit yang lebih baik. Kamu boleh

menyuntikkan ejekan untuk modul lain atau global seperti proses kebocoran variabel pribadi menimpa variabel dalam modul. rewire tidak memuat file dan mengevaluasi konten untuk meniru mekanisme yang dibutuhkan node. Bahkan ia menggunakan simpul yang dibutuhkan sendiri untuk memuat modul. Dengan demikian modul Anda berperilaku sama persis di lingkungan pengujian Anda seperti dalam keadaan biasa (kecuali modifikasi Anda).

Berita baik untuk semua pecandu kafein: rewire juga berfungsi dengan Coffee-Script. Perhatikan bahwa dalam hal ini CoffeeScript harus terdaftar dalam devDependencies Anda.


4

Saya akan menambahkan ke jawaban luff satu baris lagi dan mengubah nama parameter:

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

Jadi ini untuk membuat fungsi berfungsi dalam submodul? Bagus! Cara yang lebih singkat untuk menghapus modul dari array module.children adalah dengan menggunakan fungsi filter: module.children = module.children.filter (function (child) {return child.id! == require.resolve (_module);}) ;
luff

4

Ya, Anda dapat membatalkan cache.

Cache disimpan dalam objek yang disebut require.cache yang dapat Anda akses langsung sesuai dengan nama file (misalnya - /projects/app/home/index.jssebagai lawan ./homeyang akan Anda gunakan dalam require('./home')pernyataan).

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

Tim kami telah menemukan modul berikut bermanfaat. Untuk membatalkan kelompok modul tertentu.

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


3

Saya tidak bisa menambahkan kode dengan rapi dalam komentar jawaban. Tapi saya akan menggunakan jawaban @Ben Barkay kemudian menambahkan ini ke require.uncachefungsi.

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

Katakanlah Anda memerlukan sebuah modul, lalu hapus instalannya, lalu instal ulang modul yang sama tetapi gunakan versi berbeda yang memiliki skrip utama berbeda dalam paketnya. Module._pathCache


3

Saya tidak 100% yakin dengan apa yang Anda maksud dengan 'invalidate', tetapi Anda dapat menambahkan requirepernyataan di atas untuk menghapus cache:

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

Diambil dari komentar @ Dancrumb di sini


2

requireUncached dengan jalur relatif: 🔥

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

module.exports = requireUncached;

memanggil requireUnached dengan jalur relatif:

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

1

Mengikuti dua langkah prosedur bekerja dengan baik untuk saya.

Setelah mengubah Modelfile yaitu 'mymodule.js'secara dinamis, Anda harus Hapus model yang sudah dikompilasi dalam model luwak terlebih dahulu kemudian memuatnya menggunakan memerlukan-ulang

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

Jika itu untuk pengujian unit, alat lain yang baik untuk digunakan adalah proxyquire . Setiap kali Anda meminta modul, ia akan membatalkan cache modul dan menyimpan yang baru. Ini juga memungkinkan Anda untuk memodifikasi modul yang diperlukan oleh file yang Anda uji.


0

Saya membuat modul kecil untuk menghapus modul dari cache setelah memuat. Ini memaksa evaluasi ulang modul pada saat dibutuhkan. Lihat 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: Anda juga bisa memasukkan "penghancuran diri" ke dalam modul itu sendiri. Lihat https://github.com/bahmutov/unload-me

PSS: diperlukan lebih banyak trik dengan Node di https://glebbahmutov.com/blog/hacking-node-require/

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.