Node.js require() cache - можно сделать недействительным?
Из документации node.js:
Модули кэшируются после первого раза загрузки. Это означает (среди прочего), что каждый вызов, требуемый ('foo'), получит точно тот же объект, который был бы возвращен, если он разрешит один и тот же файл.
Есть ли способ сделать недействительным этот кеш? то есть для модульного тестирования, я бы хотел, чтобы каждый тест работал над новым объектом.
Ответы
Ответ 1
Вы всегда можете безопасно удалить запись в require.cache без проблем, даже если есть круговые зависимости. Поскольку при удалении вы просто удаляете ссылку на объект кэшированного модуля, а не на сам объект модуля, объект модуля не будет GCed, потому что в случае круговых зависимостей все еще существует объект, ссылающийся на этот объект модуля.
Предположим, что у вас есть:
script 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' }
Ответ 2
Да, вы можете получить доступ к кешу через 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 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 не был предназначен для того, чтобы не допустить, чтобы его пользователи делали глупые вещи, поскольку что также помешало бы им делать умные вещи". - Дуг Гвин
Я думаю, что должен был быть способ для выполнения явной загрузки незашифрованного модуля.
Ответ 3
Если вы всегда хотите перезагрузить модуль, вы можете добавить эту функцию:
function requireUncached(module){
delete require.cache[require.resolve(module)]
return require(module)
}
а затем используйте requireUncached('./myModule')
вместо require. На свой страх и риск, конечно.
Ответ 4
EDIT:
Я стою исправлено. Как отметил seppo0010, вы можете принудительно перезагрузить, удалив кешированный модуль из require.cache
: http://nodejs.org/docs/latest/api/globals.html#globals_require_cache
Тем не менее, я бы порекомендовал не делать этого по причинам, упомянутым ниже. Опять же, если вы делаете это только в своем слое unit test, вы можете писк без каких-либо бесконечных цепочек зависимостей.
оригинальный ответ:
Нет, нет никакого способа сделать это. Также из документации:
Несколько вызовов, требующих ('foo'), не могут вызывать код модуля выполняется несколько раз. Это важная функция. С этим, "частично выполненные" объекты могут быть возвращены, что позволяет зависимостей для загрузки, даже если они будут вызывать циклы.
Если вы хотите, чтобы модуль выполнял код несколько раз, затем экспортируйте функцию и вызов этой функции.
Два пункта:
-
Причина, по которой это необходимо, - разрешить циклы. Вы можете увидеть пример этого здесь: http://nodejs.org/docs/latest/api/modules.html#modules_cycles. Если вы каким-то образом можете сделать недействительным кеш, вы можете вызвать бесконечный цикл из-за круговых зависимостей. Даже если вы можете быть достаточно уверенны в том, что ваш код приложения не вызовет этого, это может произойти в любых используемых вами библиотеках.
-
Как указывается в документации, вы можете просто обернуть функциональность в функции, которую вы можете вызвать в каждом тесте. Это, как правило, довольно хороший дизайн.
Ответ 5
Там есть простой модуль для этого (с тестами)
У нас была эта точная проблема при тестировании нашего кода (удалить кешированные модули, чтобы их можно было повторно потребовать в новом состоянии), поэтому мы рассмотрели все предложения людей о различных кешированных кешках require()
(оба пакета npm и локально определенные модули), связанный с StackOverflow Q & A, и собрал простой node/io.js модуль (с тестами):
https://www.npmjs.com/package/ decache
![devDependency Status]()
Как? (Использование)
Использование довольно просто:
установки
Установите модуль из npm:
npm install decache --save-dev
Используйте его в своем коде:
// require the decache module:
var decache = require('decache');
// require a module that you wrote"
var 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
Ответ 6
Решения должны использовать:
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 answer:
> delete require.cache[require.resolve('./example.js')]
true
> require('./example.js')
{ message: 'hi', say: [Function], farewell: 'bye!' }
Итак, кеш очищается, а require()
снова захватывает содержимое файла, загружая все текущие значения.
Ответ 7
rewire отлично подходит для этого случая использования, вы получаете новый экземпляр с каждым вызовом. Легкая инъекция зависимостей для node.js модульного тестирования.
rewire добавляет специальный сеттер и getter в модули, поэтому вы можете изменить их поведение для лучшего модульного тестирования. Вы можете
вводят mocks для других модулей или глобалов, таких как процесс
личные переменные утечки
переопределить переменные в модуле.
rewire не загружает файл и не анализирует содержимое для эмуляции механизма node require. Фактически для использования модуля используется node. Таким образом, ваш модуль ведет себя точно так же в вашей тестовой среде, как при обычных обстоятельствах (кроме ваших изменений).
Хорошая новость для всех наркоманов, работающих с кофеином: rewire работает также с Coffee- Script. Обратите внимание, что в этом случае CoffeeScript должен быть указан в ваших devDependencies.
Ответ 8
Да, вы можете аннулировать кеш.
Кэш хранится в объекте с именем 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
Ответ 9
Для всех, кто сталкивается с тем, кто использует Jest, потому что Jest делает свое собственное кэширование модулей, для этого есть встроенная функция - просто убедитесь, что jest.resetModules
работает, например. после каждого из ваших тестов:
afterEach( function() {
jest.resetModules();
});
Обнаружено это после попытки использовать decache, как и другой ответ. Благодаря Энтони Гарван.
Документация по функциям здесь.
Ответ 10
Я бы добавил к 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)
}
Ответ 11
Я не мог аккуратно добавить код в комментарий ответа. Но я бы использовал @Ben Barkay ответ, а затем добавьте это в функцию require.uncache
.
// see https://github.com/joyent/node/issues/8266
// use in it in @Ben Barkay 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 ];
}
});
Предположим, что вам нужен модуль, затем его удалили, а затем повторно установили тот же модуль, но использовали другую версию с другим основным script в своем пакете. json, следующий запрос будет терпеть неудачу, потому что основной script не существует, поскольку он кэшируется в Module._pathCache
Ответ 12
Следующая двухэтапная процедура отлично работает для меня.
После изменения динамического файла Model
ie ie 'mymodule.js'
необходимо сначала удалить предварительно скомпилированную модель в модель mongoose, а затем перезагрузить его, используя 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');
Ответ 13
Если это для модульных тестов, другой хороший инструмент для использования - proxyquire. Каждый раз, когда вы proxyquire модуль, это приведет к недействительности кэша модуля и кэширования нового. Он также позволяет изменять модули, необходимые для файла, который вы тестируете.
Ответ 14
Я сделал небольшой модуль для удаления модуля из кэша после загрузки. Это приводит к переоценке модуля в следующий раз, когда это необходимо. См. 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/