ChatGPT解决这个技术问题 Extra ChatGPT

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

从 node.js 文档中:

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

有没有办法使这个缓存失效?即对于单元测试,我希望每个测试都在一个新的对象上工作。

另一个带有观察者的 NPM 模块:npmjs.com/package/updated-require
可以在不使用 require 的情况下缓存文件内容,并针对不同的范围对其进行评估 stackoverflow.com/questions/42376161/…

J
José Cabo

即使存在循环依赖项,您也始终可以安全地删除 require.cache 中的条目而不会出现问题。因为当你删除的时候,你只是删除了对缓存模块对象的引用,而不是模块对象本身,模块对象不会被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();

您能否在第一次要求 b.js 时解释为什么 { ... a: undefined} ?我希望等于 'a from a.js'。谢谢
为什么是未定义的?
迟到的回复,但从我收集到的 b[a] 第一次是未定义的,因为存在循环依赖。 a.js 需要 b.js,而 b.js 又需要 a.jsa.js 尚未完全加载,exports.a 尚未定义,因此 b.js 什么都没有。
如果我使用此处所述的 require.main.require(path),有什么方法可以做到这一点? stackoverflow.com/questions/10860244/…
感谢您在最后一段中添加“jest.resetModules();”,因为我遇到的大多数教程都使用了 Mocha,这真的很有帮助!
l
luff

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

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

然后使用 requireUncached('./myModule') 而不是 require。


这与侦听文件更改的 fs.watch 方法完美结合。
有什么风险?
我有同样的问题,使用这个解决方案而不是接受的答案有什么风险?
真的是一样的。根据代码的结构,当您尝试再次初始化它时,事情可能会崩溃。前任。如果模块启动一个服务器,并监听一个端口。下次你 requireUncached 模块时,它将失败,因为该端口已经打开,依此类推。
让我感到困惑的是,这个函数没有被称为 reacquire(),但这只是我。
B
Ben Barkay

是的,您可以通过 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 的设计不是为了阻止它的用户做愚蠢的事情,因为这也会阻止他们做聪明的事情。” – 道格·格温

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


+1 仅用于道格的报价。我需要有人说出我也相信的东西:)
优秀的答案!如果您想启动一个启用重新加载的节点 repl,请查看 this gist
惊人的。我会将其添加到 require.uncache 函数中。 ``` // 参见 github.com/joyent/node/issues/8266 Object.keys(module.constructor._pathCache).forEach(function(k) { if (k.indexOf(moduleName)>0) delete module.constructor._pathCache[k]; } ); ``` 假设你需要一个模块,然后卸载它,然后重新安装相同的模块,但使用了不同的版本,在它的 package.json 中有不同的主脚本,下一个 require 将失败,因为那个主脚本不存在,因为它缓存在 Module._pathCache
废话。我的评论很糟糕。我无法在此评论中整齐地添加代码,并且编辑为时已晚,所以我回答了。 @Ben Barkay 如果您可以编辑您的问题以将一小段代码添加到您的 require.uncache
谢谢@bentael,我已将此添加到我的答案中。
G
Glorfindel

有一个简单的模块(带有测试)

我们在测试我们的代码时遇到了这个确切的问题(删除缓存的模块,以便它们可以在新状态下重新需要),所以我们审查了人们在各种 StackOverflow 问题和答案中的所有建议,并组合了一个简单的 node.js 模块(带测试):

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

如您所料,适用于已发布的 npm 包和本地定义的模块。 Windows、Mac、Linux 等

https://img.shields.io/travis/dwyl/decache/master.svg?style=flat-square

如何? (用法)

用法很简单:

安装

从 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


我一直在研究这个,它看起来非常适合我在测试时使用,这样我就可以在特定条件下卸载和重新加载模块,但不幸的是我在工作,我的公司避开了 GPL 许可证。我只想将它用于测试,所以我仍在考虑它,因为它看起来很有帮助。
@Matt_JD 感谢您的反馈。您更喜欢哪个许可证?
@Matt_JD 我们已将许可证更新为 MIT。祝你工作顺利! :-)
这非常有效!为这个 repo 加注星标并支持这个答案。
强烈推荐,截至今天在最新的 v14.2.0 上运行良好
T
Tim Malone

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

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

在尝试使用 decache 后发现这个,就像另一个答案所建议的那样。感谢Anthony Garvan

函数文档 here


非常感谢您的注意!
天哪,在我发现这个之前我做了多久的实验……谢谢!
C
Community

解决方案是使用:

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's answer 中指示的 delete require.cache[require.resolve()]

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

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


恕我直言,这是最合适的答案
S
SavoryBytes

rewire 非常适合此用例,每次调用都会获得一个新实例。 node.js 单元测试的简单依赖注入。

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

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

所有咖啡因成瘾者的好消息:rewire 也适用于 Coffee-Script。请注意,在这种情况下,CoffeeScript 需要在您的 devDependencies 中列出。


K
Krzysztof Wende

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

所以这是为了让功能在子模块中工作?好的!从 module.children 数组中删除模块的一种更短的方法是使用过滤函数: module.children = module.children.filter(function(child){ return child.id !== require.resolve(_module); }) ;
f
fedorqui

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

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

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

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

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


a
atomh33ls

我不是 100% 确定您所说的“无效”是什么意思,但您可以在 require 语句上方添加以下内容以清除缓存:

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

摘自 @Dancrumb 的评论 here


Y
YairTawil

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

L
Lux

我无法在答案的评论中整齐地添加代码。但我会使用@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 中有不同主脚本的不同版本,下一个 require 将失败,因为该主脚本不存在,因为它缓存在Module._pathCache


K
Kyuuhachi

如果您希望模块永远不会被缓存(有时对开发很有用,但请记住在完成后将其删除!)您可以将 delete require.cache[module.id]; 放入模块中。


我在哪里添加这个?我的 js 没有通过热重载加载到浏览器中......
C
Community

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

动态更改 Model 文件即 'mymodule.js' 后,您需要先删除 mongoose model 中的预编译模型,然后使用 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');

O
OrangeDog

The documentation 说:

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


u
unsynchronized

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

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

}

C
Comtaler

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


g
gleb bahmutov

我做了一个小模块,在加载后从缓存中删除模块。这会在下次需要时强制重新评估模块。请参阅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/ 中需要更多使用 Node 的技巧