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

Состояние сборки Test Coverage Code Climate Состояние зависимостей 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/