node.js требуют () кэш - возможно сделать недействительным?


325

Из документации по node.js:

Модули кэшируются после первой загрузки. Это означает (среди прочего), что каждый вызов require ('foo') будет получать точно такой же возвращаемый объект, если он будет преобразован в тот же файл.

Есть ли способ аннулировать этот кеш? т.е. для модульного тестирования, я бы хотел, чтобы каждый тест работал на новом объекте.


8
Модуль NPM github.com/gajus/require-new
Gajus

Еще один модуль NPM с наблюдателем: npmjs.com/package/updated-require
Хорхе Фуэнтес Гонсалес,

Можно кешировать содержимое файла без использования require и проверить его для разных областей действия stackoverflow.com/questions/42376161/…
lonewarrior556

Ответы:


305

Вы всегда можете безопасно удалить запись в require.cache, даже если существуют циклические зависимости. Поскольку при удалении вы просто удаляете ссылку на кэшированный объект модуля, а не сам объект модуля, объект модуля не будет GCed, потому что в случае циклических зависимостей все еще существует объект, ссылающийся на этот объект модуля.

Предположим, у вас есть:

скрипт 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
почему не определено?
Джефф П Чакко

4
Поздний ответ, но из того, что я b[a]понял, впервые не определено, поскольку существует круговая зависимость. a.jsтребует b.jsчего в свою очередь требует a.js. a.jsеще не полностью загружен и exports.aеще не определен, так что b.jsget ничего.
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')вместо требовать.


6
Это идеально в сочетании с fs.watchметодом, который прослушивает изменения файла.
ph3nx

2
какой риск?
Scarass

У меня такой же вопрос, каков риск использования этого решения, а не принятого ответа?
rotimi-best

1
Это действительно так. В зависимости от того, как структурирован код, при повторной его инициализации может произойти сбой. Ex. если модуль запускает сервер и прослушивает порт. В следующий раз, когда вам потребуется отключить модуль, он не будет работать, так как этот порт уже открыт, и так далее.
шкаторины

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 не был разработан, чтобы мешать своим пользователям делать глупости, так как это также мешало бы им делать умные вещи». - Дуг Гвин

Я думаю, что должен быть способ выполнить явную загрузку некэшированного модуля.


17
+1 только за цитату Дуга. Мне нужен был кто-то, чтобы выразить то, во что я тоже верил :)
Пони

1
Отличный ответ! Если вы хотите запустить repl узла с включенной перезагрузкой, ознакомьтесь с этой сущностью .
Глейц

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, следующее требование не будет выполнено, потому что этот основной сценарий не существует, потому что это кешируется вModule._pathCache
bentael

дерьмо. мой комментарий ужасен Я не мог аккуратно добавить код в этот комментарий, и уже слишком поздно редактировать, поэтому я ответил. @ Бен Баркай, если бы вы могли отредактировать свой вопрос, чтобы добавить небольшой фрагмент кода в свойrequire.uncache
bentael

Спасибо @bentael, я добавил это в свой ответ.
Бен Баркай

39

Для этого есть простой модуль ( с тестами )

У нас возникла именно эта проблема при тестировании нашего кода ( удалите кэшированные модули, чтобы их можно было повторно запрашивать в новом состоянии ), поэтому мы рассмотрели все предложения людей по различным вопросам и ответам StackOverflow и собрали простой модуль node.js ( с тестами ):

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

Как и следовало ожидать, работает как для опубликованных пакетов 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 спасибо за ваш отзыв. какую лицензию вы бы предпочли?
Нельсоник

2
@Matt_JD Мы обновили лицензию до MIT. Удачи в работе! :-)
Нельсоник

1
это сработало потрясающе! В ролях это репо и голосование против этого ответа.
Ахолт

1
очень рекомендую, работает на последней версии 14.2.0 на сегодняшний день
Thomazella

28

Для тех, кто сталкивается с этим, кто использует Jest, потому что Jest имеет свой собственный модуль кеширования, для этого есть встроенная функция - просто убедитесь, что он jest.resetModulesработает, например. после каждого из ваших тестов:

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

Нашел это после попытки использовать decache, как предложил другой ответ. Спасибо Энтони Гарвану .

Функциональная документация здесь .


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

Вот тогда вы можете использовать delete require.cache[require.resolve()]указанное в ответе Луффа :

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

Таким образом, кэш очищается и require()снова захватывает содержимое файла, загружая все текущие значения.


ИМХО Это самый подходящий ответ
Пиюш Катария

5

rewire отлично подходит для этого варианта использования, вы получаете новый экземпляр с каждым вызовом. Легкое внедрение зависимостей для модульного тестирования node.js.

rewire добавляет специальный установщик и получатель для модулей, чтобы вы могли изменить их поведение для лучшего модульного тестирования. Ты можешь

вставлять макеты для других модулей или глобальных переменных, таких как частные переменные утечки процесса, переопределяющие переменные внутри модуля. rewire не загружает файл и не оценивает содержимое, чтобы эмулировать механизм запроса узла. Фактически он использует собственные требования узла для загрузки модуля. Таким образом, ваш модуль ведет себя точно так же в вашей тестовой среде, как и при обычных обстоятельствах (кроме ваших модификаций).

Хорошие новости для всех зависимых от кофеина: rewire работает также с Coffee-Script. Обратите внимание, что в этом случае CoffeeScript должен быть указан в ваших devDependencies.


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

4

Да, вы можете сделать кэш недействительным.

Кэш хранится в объекте под названием 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


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

1

Следующая двухэтапная процедура работает идеально для меня.

После изменения Modelфайла, т.е. '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');

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/

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.