node.js require()缓存-可能无效?


325

从node.js文档中:

第一次加载模块后将对其进行缓存。这意味着(除其他事项外)每次对require('foo')的调用都将获得完全相同的返回对象,如果它可以解析为相同的文件。

有没有办法使该缓存无效?即对于单元测试,我希望每个测试都可以在一个新对象上进行。



另一个带有观察程序的NPM模块:npmjs.com/package/updated-require
Jorge

可以在不使用require的情况下缓存文件内容,并将其评估为不同的范围stackoverflow.com/questions/42376161/…–
lonewarrior556

Answers:


304

即使存在循环依赖关系,也始终可以安全地删除require.cache中的条目。因为在删除时,您只删除对缓存的模块对象的引用,而不是对模块对象本身的引用,所以不会对GC对象进行GC,因为在循环依赖的情况下,仍然有一个对象引用此模块对象。

假设您有:

脚本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),则正确的语句将是:

jest.resetModules();

2
你能解释一下为什么第一次{ ... a: undefined}需要b.js吗?我期望平等'a from a.js'。谢谢
ira

1
为什么是未定义的?
杰夫P Chacko

4
回复较晚,但是b[a]由于存在循环依赖关系,因此我第一次收集到的信息是不确定的。a.js要求b.js反过来要求a.jsa.js尚未完全加载并且exports.a尚未定义,因此b.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')而不是require。


6
这与fs.watch侦听文件更改的方法完美结合。
ph3nx 2014年

2
有什么风险?
Scarass's

我有同样的问题,使用此解决方案而不是接受的答案有什么风险?
rotimi-best,

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并非旨在阻止其用户执行愚蠢的事情,因为这也将阻止他们执行聪明的事情。” –道格·格温(Doug Gwyn)

我认为应该有一种方法可以执行显式的未缓存模块加载。


17
+1仅用于道格的报价。我需要有人说出我也相信的内容:)
Poni

1
很好的答案!如果您想在启用重新加载的情况下启动节点repl,请查看此要点
gleitz

1
太棒了 我将其添加到require.uncache功能。```//参见github.com/joyent/node/issues/8266 Object.keys(module.constructor._pathCache).forEach(function(k){if(k.indexOf(moduleName)> 0)删除module.constructor ._pathCache [k];}); 假设您需要一个模块,然后将其卸载,然后重新安装相同的模块,但使用了另一个版本,该版本的package.json中具有不同的主脚本,则下一个require将失败,因为该主脚本不存在,因为它被缓存在Module._pathCache
bentael 2014年

废话 我的评论太糟糕了。我无法在此注释中整齐地添加代码,现在编辑为时已晚,所以我回答了。@Ben Barkay,如果您可以编辑问题以将少量代码片段添加到您的文件中require.uncache
bentael 2014年

谢谢@bentael,我已将此添加到我的答案中。
Ben Barkay 2014年

39

为此有一个简单的模块带有测试

我们在测试代码时遇到了这个确切的问题(删除缓存的模块,以便可以在新的状态下重新请求它们),因此我们回顾了人们对各种StackOverflow问题与解答的所有建议,并整理了一个简单的 node.js模块与测试):

https://www.npmjs.com/package/ 缓存

如您所料,它既可以用于已发布的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
这很棒!为此回购加注星标,并对该答案进行投票。
aholt

1
强烈建议您使用,直到今天最新的v14.2.0都运行良好
Thomazella

28

对于使用Jest的任何人,因为Jest进行自己的模块缓存,所以有一个内置函数-只需确保jest.resetModules运行例如。在每个测试之后:

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

尝试使用decache像建议的另一个答案后,发现了这一点。感谢Anthony 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] }

那是您可以使用Luff答案中delete require.cache[require.resolve()]指示的时间

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

因此,清除了缓存,并require()再次捕获了文件的内容,并加载了所有当前值。


恕我直言,这是最合适的答案
Piyush Katariya '18 -10-26

5

rewire在此用例中非常有用,每次调用都会获得一个新实例。轻松的依赖注入,用于node.js单元测试。

rewire在模块中添加了特殊的setter和getter,因此您可以修改它们的行为以进行更好的单元测试。你可以

为其他模块或全局变量(例如进程泄漏专用变量)注入模拟,将覆盖模块内的变量。rewire不会加载文件并评估内容以模拟节点的require机制。实际上,它使用节点自身的要求来加载模块。因此,您的模块在测试环境中的行为与常规情况下完全相同(修改除外)。

对所有咖啡因上瘾者来说是个好消息:rewire也可以在Coffee-Script中使用。请注意,在这种情况下,需要在devDependencies中列出CoffeeScript。


4

我会再增加一行,并更改参数名称:

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

因此,这是为了使功能在子模块中工作?真好!从module.children数组中删除模块的更短方法是使用过滤器函数:module.children = module.children.filter(function(child){return child.id!== require.resolve(_module);}) ;
变幅

4

是的,您可以使缓存无效。

缓存存储在一个名为require.cache的对象中,您可以根据文件名直接访问该对象(例如,/projects/app/home/index.js./homerequire('./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');


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/中进行

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.