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

在node.js文档中:

模块在第一次加载后被缓存。这意味着(除其他外)每次调用require('foo')都会得到完全相同的返回对象,如果它将解析到相同的文件。

是否有一种方法使这个缓存无效?例如,对于单元测试,我希望每个测试都在一个新鲜的对象上工作。

184319 次浏览

您总是可以安全地删除require中的条目。缓存没有问题,即使有循环依赖。因为当你删除时,你只是删除了缓存的模块对象的引用,而不是模块对象本身,模块对象不会被GCed,因为在循环依赖的情况下,仍然有一个对象引用这个模块对象。

假设你有:

脚本a.js:

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

脚本研究:

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();

是的,你可以通过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相同的解析器,所以只需为require指定所需的内容。


"Unix不是被设计来阻止它的用户做愚蠢的事情,就像 这也会阻止他们做一些聪明的事情。”- Doug Gwyn

我认为应该已经有一种方法来执行显式的非缓存模块加载。

如果你总是想重新加载你的模块,你可以添加这个函数:

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

然后使用requireUncached('./myModule')代替require。

我将在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)
}

重新连接对于这个用例很有用,每次调用都会得到一个新实例。简单的依赖注入node.js单元测试。

Rewire为模块添加了一个特殊的setter和getter,这样您就可以修改它们的行为以进行更好的单元测试。你可能

为其他模块或全局变量(如process)注入模拟 泄露私有变量 重写模块内的变量。 Rewire不会加载文件并计算内容以模拟节点的require机制。实际上,它使用节点自己的require来加载模块。因此,您的模块在测试环境中的行为与常规情况下完全相同(除了您的修改)

对所有咖啡因成瘾者来说,这是个好消息:重新连接也适用于Coffee-Script。注意,在这种情况下CoffeeScript需要被列在你的devDependencies中。

我不能在回答的注释中整齐地添加代码。但是我会使用@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 ];
}
});

假设您需要一个模块,然后卸载它,然后重新安装相同的模块,但使用了不同的版本,其包中有不同的主脚本。json,下一个require将失败,因为主脚本不存在,因为它缓存在Module._pathCache

简单模块 (与测试)

我们在测试我们的代码(删除缓存的模块,以便在刷新状态下重新使用它们)时遇到了这个确切的问题,所以我们在各种StackOverflow问题上审查了所有人的建议。答案和放在一起简单的 < >强node . js模块< / >强 (<强劲> < / >强测试):

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

如你所料,适用于这两个发布的npm包和在本地定义的模块。Windows, Mac, Linux等等。

Build Status < a href = " http://codecov.io/github/dwyl/decache?Branch =master" rel="nofollow noreferrer"> codecov。io < / > Code气候可维护性 . Dependencies Status devDependencies Status < / p >

怎么做?(使用)

用法非常简单:

安装

从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 < / p >

以下两步程序对我来说非常有效。

在动态更改Model文件i-e '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');

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

缓存存储在一个名为require的对象中。缓存,你可以直接根据文件名访问(例如- /projects/app/home/index.js而不是./home,你会在require('./home')语句中使用)。

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

我们的团队发现以下模块很有用。使某些模块组无效。

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

解决方法是:

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()]:

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

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

如果它是用于单元测试,另一个很好的工具是proxyquire。每次你代理模块时,它都会使模块缓存失效,并缓存一个新的模块。它还允许您修改正在测试的文件所需的模块。

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

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

在尝试使用decache像另一个答案建议后发现这一点。感谢安东尼Garvan

函数文档在这里

我做了一个小模块来删除加载后缓存中的模块。这将强制在下次需要该模块时重新计算它。看到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:更多的技巧与节点需要在我的https://glebbahmutov.com/blog/hacking-node-require/

我不是100%确定你说的“invalidate”是什么意思,但你可以在require语句上面添加以下内容来清除缓存:

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

选自@Dancrumb的评论在这里

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');

的文档说:

当需要模块时,模块缓存在这个对象中。通过从该对象中删除键值,下一个require将重新加载该模块。这不适用于本机插件,重新加载将导致错误。

如果你想让一个模块永远不被缓存(有时对开发有用,但记得在完成时删除它!),你可以在模块中放入delete require.cache[module.id];

这是我的这个答案版本,它处理不加载文件,如果它有(例如)语法错误

function reacquire(module) {
const fullpath  = require.resolve(module);
const backup = require.cache[fullpath];
delete require.cache[fullpath];


try {
const newcopy = require(module);
console.log("reqcquired:",module,typeof newcopy);
return newcopy;
} catch (e) {
console.log("Can't reqcquire",module,":",e.message);
require.cache[fullpath] = backup;
return backup;
}


}