node.js ต้องการแคช () - เป็นไปได้ที่จะทำให้ใช้ไม่ได้?


325

จากเอกสารคู่มือ node.js:

โมดูลจะถูกแคชหลังจากครั้งแรกที่พวกเขาจะโหลด ซึ่งหมายความว่า (เหนือสิ่งอื่นใด) ที่การเรียกร้องให้ทุกคนต้องการ ('foo') จะได้รับวัตถุเดียวกันกลับคืนมาถ้ามันจะแก้ไขเป็นไฟล์เดียวกัน

มีวิธีในการทำให้แคชนี้ใช้ไม่ได้หรือไม่ เช่นสำหรับการทดสอบหน่วยฉันต้องการให้การทดสอบแต่ละครั้งทำงานกับวัตถุใหม่


8
โมดูล NPM github.com/gajus/require-new
Gajus

โมดูล NPM อีกตัวพร้อมตัวเฝ้าดู: npmjs.com/package/updated-require
Jorge Fuentes González

มันเป็นไปได้ที่จะแคชเนื้อหาของแฟ้มโดยไม่ต้องใช้ต้องการและ eval มันสำหรับขอบเขตที่แตกต่างกันstackoverflow.com/questions/42376161/...
lonewarrior556

คำตอบ:


305

คุณสามารถลบรายการใน require.cache ได้อย่างปลอดภัยตลอดเวลาโดยไม่มีปัญหาแม้ว่าจะมีการอ้างอิงแบบวนซ้ำ เพราะเมื่อคุณลบคุณเพียงแค่ลบการอ้างอิงไปยังวัตถุโมดูลแคชไม่ใช่วัตถุโมดูลตัวเองวัตถุโมดูลจะไม่ถูก GCed เพราะในกรณีของการอ้างอิงแบบวงกลมยังมีวัตถุที่อ้างอิงวัตถุโมดูลนี้

สมมติว่าคุณมี:

สคริปต์ a.js:

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

และสคริปต์ b.js:

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

เมื่อคุณทำ:

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

คุณจะได้รับ:

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

ตอนนี้ถ้าคุณแก้ไข b.js ของคุณ:

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

และทำ:

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

คุณจะได้รับ:

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

===

ข้างต้นถูกต้องหากรันโดยตรง node.js อย่างไรก็ตามหากใช้เครื่องมือที่มีระบบแคชโมดูลของตัวเองเช่นตัวตลกคำสั่งที่ถูกต้องจะเป็น:

jest.resetModules();

2
คุณช่วยอธิบายได้ไหมว่าทำไม{ ... a: undefined}เมื่อต้องการb.jsเป็นครั้งแรก 'a from a.js'ผมคาดว่าจะเท่ากับ ขอบคุณ
ira

1
ทำไมถึงไม่ได้กำหนดไว้?
Jeff P Chacko

4
การตอบกลับล่าช้า แต่จากสิ่งที่ฉันรวบรวมb[a]ไม่ได้กำหนดเป็นครั้งแรกเนื่องจากมีการอ้างอิงแบบวนรอบ a.jsต้องซึ่งจะต้องใช้b.js ยังไม่โหลดเต็มที่และยังไม่ได้กำหนดดังนั้นจึงไม่มีอะไรเลย a.jsa.jsexports.ab.js
nik10110

มีวิธีใดบ้างถ้าฉันใช้require.main.require(path)ตามที่อธิบายไว้ที่นี่? stackoverflow.com/questions/10860244/…
Flion

186

หากคุณต้องการโหลดโมดูลของคุณใหม่คุณสามารถเพิ่มฟังก์ชั่นนี้ได้:

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

แล้วใช้requireUncached('./myModule')แทนต้องการ


6
นี่เป็นการผสมผสานที่สมบูรณ์แบบกับfs.watchวิธีการที่รับฟังการเปลี่ยนแปลงไฟล์
ph3nx

2
ความเสี่ยงคืออะไร?
Scarass

คำถามเดียวกันฉันมีความเสี่ยงในการใช้โซลูชันนี้และไม่ใช่คำตอบที่ยอมรับคืออะไร
rotimi ที่ดีที่สุด

1
มันเหมือนกันจริงๆ สิ่งต่าง ๆ อาจมีปัญหาเมื่อคุณพยายามเริ่มต้นใหม่อีกครั้งทั้งนี้ขึ้นอยู่กับวิธีการจัดโครงสร้างรหัส อดีต ถ้าโมดูลเริ่มต้นเซิร์ฟเวอร์และฟังพอร์ต ในครั้งต่อไปที่คุณต้องการใช้โมดูลที่เก็บไว้มันจะล้มเหลวเนื่องจากพอร์ตนั้นเปิดอยู่แล้วเป็นต้น
luff

133

ใช่คุณสามารถเข้าถึงแคชผ่านทางrequire.cache[moduleName]ที่moduleNameเป็นชื่อของโมดูลที่คุณต้องการเข้าถึง การลบรายการโดยการโทรdelete require.cache[moduleName]จะทำให้requireโหลดไฟล์จริง

นี่คือวิธีที่คุณจะลบไฟล์แคชทั้งหมดที่เกี่ยวข้องกับโมดูล:

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

การใช้งานจะเป็น:

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

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

เนื่องจากรหัสนี้ใช้โปรแกรมแก้ไขตัวเดียวกันrequireทำเพียงระบุสิ่งที่คุณต้องการ


"Unix ไม่ได้ถูกออกแบบมาเพื่อหยุดผู้ใช้จากการทำสิ่งที่โง่เขลาเพราะมันจะหยุดพวกเขาจากการทำสิ่งที่ฉลาด" - ดั๊กกวิน

ฉันคิดว่าควรจะมีวิธีสำหรับการโหลดโมดูลที่ไม่ได้ล้างอย่างชัดเจน


17
+1 เพียงเพื่ออ้างอิงของดั๊ก ฉันต้องการใครสักคนที่จะวลีสิ่งที่ฉันยังเชื่อใน :)
Poni

1
คำตอบที่ยอดเยี่ยม! หากคุณต้องการเริ่มต้นโหนดแทนที่ด้วยการเปิดใช้งานการโหลดซ้ำตรวจสอบส่วนสำคัญนี้
gleitz

1
น่ากลัว ฉันจะเพิ่มสิ่งนี้ในrequire.uncacheฟังก์ชั่น `` `// ดูgithub.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (ฟังก์ชัน (k) {ถ้า (k.indexOf (moduleName)> 0) ลบ module.constructor ._pathCache [k];}); `` `สมมติว่าคุณต้องการโมดูลแล้วถอนการติดตั้งแล้วติดตั้งโมดูลเดิมอีกครั้ง แต่ใช้รุ่นอื่นที่มีสคริปต์หลักที่แตกต่างกันใน package.json ความต้องการถัดไปจะล้มเหลวเพราะสคริปต์หลักไม่มีอยู่เพราะ มันถูกเก็บไว้ในModule._pathCache
bentael

อึ. ความคิดเห็นของฉันแย่มาก ฉันไม่สามารถเพิ่มรหัสในความคิดเห็นนี้ได้อย่างเรียบร้อยและมันสายเกินไปที่จะแก้ไขดังนั้นฉันจึงตอบ @ Ben Barkay ถ้าคุณสามารถแก้ไขคำถามของคุณเพื่อเพิ่มข้อมูลเล็ก ๆ น้อย ๆ ของรหัสของคุณrequire.uncache
bentael

ขอบคุณ @bentael ฉันได้เพิ่มสิ่งนี้ในคำตอบของฉัน
Ben Barkay

39

มีโมดูลง่าย ๆ สำหรับเรื่องนั้น ( พร้อมการทดสอบ )

เรามีปัญหาตรงนี้ในขณะที่ทดสอบรหัสของเรา ( ลบโมดูลแคชเพื่อให้สามารถเรียกใช้ซ้ำได้ในสถานะใหม่ ) ดังนั้นเราจึงตรวจสอบคำแนะนำทั้งหมดของผู้คนในคำถามและคำตอบ StackOverflow ที่หลากหลายและรวบรวมโมดูล node.js ที่เรียบง่าย ( ด้วยการทดสอบ ):

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

อย่างที่คุณคาดหวัง, ใช้ได้กับทั้งแพ็กเกจ npm ที่เผยแพร่และโมดูลที่กำหนดแบบโลคัล Windows, Mac, Linux, ฯลฯ

สถานะการสร้าง codecov.io รหัสการบำรุงรักษาสภาพภูมิอากาศ สถานะการพึ่งพา สถานะ devDependencies

อย่างไร? ( การใช้งาน )

การใช้งานค่อนข้างง่าย:

ติดตั้ง

ติดตั้งโมดูลจาก npm:

npm install decache --save-dev

ใช้ในรหัสของคุณ:

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

หากคุณมีคำถามหรือต้องการตัวอย่างเพิ่มเติมโปรดสร้างปัญหา GitHub: https://github.com/dwyl/decache/issues


1
ฉันดูที่นี่และมันยอดเยี่ยมมากสำหรับฉันที่ใช้เมื่อทำการทดสอบเพื่อให้ฉันสามารถยกเลิกการโหลดและโหลดโมดูลภายใต้เงื่อนไขที่เฉพาะเจาะจงได้ แต่น่าเสียดายที่ฉันอยู่ในที่ทำงานและ บริษัท ของฉันแตกต่างจากใบอนุญาต GPL ฉันต้องการใช้เพื่อการทดสอบเท่านั้นฉันยังคงพิจารณาเพราะมันมีประโยชน์มาก
Matt_JD

@ Matt_JD ขอบคุณสำหรับความคิดเห็นของคุณ ใบอนุญาตใดที่คุณต้องการ?
nelsonic

2
@Matt_JD เราได้อัปเดตใบอนุญาตเป็น MIT แล้ว ขอให้โชคดีกับการทำงานของคุณ! :-)
nelsonic

1
มันทำงานได้อย่างน่าอัศจรรย์! นำแสดงโดย repo นี้และถอนคำตอบนี้
aholt

1
แนะนำเป็นอย่างยิ่งวิ่งได้ดีใน v14.2.0 ล่าสุด ณ วันนี้
Thomazella

28

สำหรับทุกคนที่เจอสิ่งนี้ซึ่งใช้ Jest เนื่องจาก Jest ทำแคชโมดูลของตัวเองมีฟังก์ชันในตัวสำหรับสิ่งนี้ - เพียงแค่ให้แน่ใจว่าjest.resetModulesทำงานเช่น หลังจากการทดสอบแต่ละครั้ง:

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

พบสิ่งนี้หลังจากพยายามใช้decacheเหมือนคำตอบอื่นที่แนะนำ ขอขอบคุณที่แอนโธนี Garvan

เอกสารฟังก์ชั่นที่นี่


1
ขอบคุณมากสำหรับบันทึกนี้!
mjgpy3

2
พระเจ้านานแค่ไหนที่ฉันทำการทดลองก่อนที่ฉันจะพบสิ่งนี้ .... ขอบคุณ!
Tiago

16

การแก้ปัญหาคือการใช้:

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

ค้นหาคำอธิบายพื้นฐานสำหรับผู้ที่ชอบฉันที่นี่เป็นเรื่องใหม่ในเรื่องนี้:

สมมติว่าคุณมีexample.jsไฟล์จำลองในรูทของไดเรกทอรี:

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

จากนั้นคุณrequire()ชอบสิ่งนี้:

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

หากคุณเพิ่มบรรทัดลักษณะนี้ลงในexample.js:

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

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

และดำเนินการต่อในคอนโซลโมดูลจะไม่ถูกอัพเดต:

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

นั่นคือเมื่อคุณสามารถใช้delete require.cache[require.resolve()]ระบุไว้ในคำตอบของ luff :

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

ดังนั้นแคชจึงถูกทำความสะอาดและrequire()รวบรวมเนื้อหาของไฟล์อีกครั้งโหลดค่าปัจจุบันทั้งหมด


IMHO นี่เป็นคำตอบที่เหมาะสมที่สุด
Piyush Katariya

5

rewireนั้นยอดเยี่ยมสำหรับกรณีการใช้งานนี้คุณจะได้รับอินสแตนซ์ใหม่ของการโทรแต่ละครั้ง การฉีดพึ่งพาง่ายสำหรับการทดสอบหน่วย node.js

rewire เพิ่ม setter และ getter พิเศษให้กับโมดูลเพื่อให้คุณสามารถปรับเปลี่ยนพฤติกรรมของพวกเขาเพื่อการทดสอบหน่วยที่ดีขึ้น คุณอาจ

ฉีด mocks สำหรับโมดูลหรือกลมอื่น ๆ เช่นตัวแปรส่วนตัวรั่วกระบวนการแทนที่ตัวแปรภายในโมดูล rewire ไม่ได้ทำการโหลดไฟล์และทำการ eval content เพื่อเลียนแบบความต้องการของโหนด ในความเป็นจริงมันใช้โหนดของตัวเองต้องการโหลดโมดูล ดังนั้นโมดูลของคุณจะทำงานเหมือนกันในสภาพแวดล้อมการทดสอบของคุณเหมือนกับในสถานการณ์ปกติ (ยกเว้นการดัดแปลงของคุณ)

ข่าวดีสำหรับผู้เสพติดคาเฟอีนทั้งหมด: rewire สามารถใช้งานได้กับ Coffee-Script โปรดทราบว่าในกรณีนี้ CoffeeScript จะต้องมีอยู่ในรายการ devDependencies ของคุณ


4

ฉันต้องการเพิ่มคำตอบของ luff อีกหนึ่งบรรทัดและเปลี่ยนชื่อพารามิเตอร์:

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

ดังนั้นนี่คือการทำให้ฟังก์ชั่นการทำงานใน submodules? ดี! วิธีที่สั้นกว่าในการลบโมดูลออกจากอาร์เรย์ module.children คือการใช้ฟังก์ชั่นตัวกรอง: module.children = module.children.filter (ฟังก์ชั่น (child) {return child.id! == require.resolve (_module);}) ;
luff

4

ใช่คุณสามารถทำให้แคชใช้ไม่ได้

แคชถูกเก็บไว้ในวัตถุที่เรียกว่า require.cache ซึ่งคุณสามารถเข้าถึงได้โดยตรงตามชื่อไฟล์ (เช่น - /projects/app/home/index.jsซึ่งตรงข้ามกับ./homeที่คุณจะใช้ในrequire('./home')คำสั่ง)

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

ทีมของเราพบว่าโมดูลต่อไปนี้มีประโยชน์ ในการทำให้โมดูลกลุ่มบางกลุ่มใช้ไม่ได้

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


3

ฉันไม่สามารถเพิ่มรหัสในความคิดเห็นของคำตอบได้อย่างเรียบร้อย แต่ฉันจะใช้คำตอบของ @Ben Barkay จากนั้นเพิ่ม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 ];
        }
    }); 

สมมติว่าคุณต้องการโมดูลแล้วถอนการติดตั้งแล้วติดตั้งโมดูลเดิมอีกครั้ง แต่ใช้รุ่นอื่นที่มีสคริปต์หลักที่แตกต่างกันใน package.json ความต้องการต่อไปจะล้มเหลวเนื่องจากสคริปต์หลักนั้นไม่มีอยู่เพราะแคชใน Module._pathCache


3

ฉันไม่แน่ใจ 100% ในสิ่งที่คุณหมายถึงโดย 'โมฆะ' แต่คุณสามารถเพิ่มrequireคำสั่งด้านบนเพื่อล้างแคช:

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

นำมาจากความคิดเห็นของ @ Dancrumb ที่นี่


2

requireUncached ด้วยพา ธ ที่สัมพันธ์: 🔥

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

module.exports = requireUncached;

เรียกใช้ requireUncached กับเส้นทางญาติ:

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

1

ขั้นตอนสองขั้นตอนต่อไปนี้ทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน

หลังจากเปลี่ยนModelไฟล์นั่นคือ'mymodule.js'แบบไดนามิกคุณต้องลบโมเดลที่คอมไพล์แล้วในโมเดลพังพอนก่อนจากนั้นทำการรีโหลดโดยใช้require-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

ถ้ามันสำหรับการทดสอบหน่วยเครื่องมือที่ดีอีกครั้งเพื่อให้การใช้งานเป็นproxyquire ทุกครั้งที่คุณทำการพร็อกซีโมดูลจะทำให้โมดูแคชและแคชใหม่ไม่ถูกต้อง นอกจากนี้ยังช่วยให้คุณสามารถแก้ไขโมดูลที่ต้องการโดยไฟล์ที่คุณกำลังทดสอบ


0

ฉันทำโมดูลขนาดเล็กเพื่อลบโมดูลออกจากแคชหลังจากโหลด สิ่งนี้จะบังคับให้ประเมินค่าใหม่ของโมดูลในครั้งต่อไปที่จำเป็น ดู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: คุณยังสามารถใส่ "ทำลายตัวเอง" ลงในโมดูลได้ ดู https://github.com/bahmutov/unload-me

PSS: เคล็ดลับเพิ่มเติมกับ Node ต้องการในhttps://glebbahmutov.com/blog/hacking-node-require/ของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.