node.js вимагає () кеш - можливо визнати недійсним?


325

З документації на node.js:

Модулі кешуються після першого завантаження. Це означає (серед іншого), що кожен виклик, який потрібно вимагати ('foo'), поверне точно той самий об’єкт, якщо він буде вирішувати один і той же файл.

Чи є спосіб визнати недійсним цей кеш? тобто для тестування одиниць, я хотів би, щоб кожен тест працював над свіжим об'єктом.


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

Ще один модуль NPM з спостерігачем: npmjs.com/package/update-require
Хорхе Фуентес Гонсалес

Можна кешувати вміст файлів без використання вимагають і Eval його для різних сфер stackoverflow.com/questions/42376161 / ...
lonewarrior556

Відповіді:


305

Ви завжди можете без проблем видалити запис у Requ.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.resetModules();

2
Ви можете, будь ласка, пояснити, чому, { ... a: undefined}коли вимагаєте b.jsвперше? Я б очікував рівних 'a from a.js'. Дякую
ira

1
чому невизначений?
Jeff P Chacko

4
Пізня відповідь, але те, що я збираю, b[a]не визначене вперше, оскільки існує кругова залежність. a.jsвимагає, b.jsщо в свою чергу вимагає a.js. a.jsще не повністю завантажений і exports.aйого ще потрібно визначити, тому не b.jsотримуйте нічого.
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
Це насправді так само. Залежно від того, як структурований код, при спробі ініціалізації його знову можуть вийти з ладу. Вих. якщо модуль запускає сервер і слухає порт. Наступного разу, коли вам потрібно буде скасувати модуль, він вийде з ладу, оскільки цей порт вже відкритий тощо.
шкаторини

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 вузла з включеним перезавантаженням, ознайомтеся з цим суттю .
gleitz

1
приголомшливий Я додав би це до require.uncacheфункції. `` // див. github.com/joyent/node/isissue/8266 Object.keys (module.constructor._pathCache) .forEach (функція (k) {if (k.indexOf (moduleName)> 0) delete module.constructor ._pathCache [k];}); `` `Скажіть, вам потрібен модуль, потім його видалили, потім перевстановили той самий модуль, але використовували іншу версію, яка має інший головний скрипт у своєму пакеті.json, наступна вимога буде відмовлена, оскільки цього основного сценарію не існує, оскільки це в кеш-пам’ятіModule._pathCache
bentael

лайно. мій коментар жахливий. Я не зміг акуратно додати код до цього коментаря, і це вже пізно для редагування, тому я відповів. @Ben Barkay, якщо ви можете відредагувати своє запитання, щоб додати маленький фрагмент коду до свогоrequire.uncache
bentael

Дякую @bentael, я додав це до своєї відповіді.
Бен Баркай

39

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

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

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

Як можна було очікувати, працює як для опублікованих пакетів npm, так і для локально визначених модулів. Windows, Mac, Linux тощо.

Статус складання codecov.io Кодекс збереження клімату Стан залежностей devDependitions Status

Як? ( використання )

Використання досить просте:

встановити

Встановіть модуль з 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/isissue


1
Я дивився на це, і мені здається, це дуже чудово використовувати при тестуванні, щоб я міг вивантажувати та перезавантажувати модуль за певних умов, але, на жаль, я працюю, і моя компанія ухиляється від ліцензій GPL. Я хочу лише використовувати його для тестування, тому я все ще розглядаю це, оскільки це виглядає так корисно.
Matt_JD

@Matt_JD дякую за відгук. яку ліцензію ви б віддали перевагу?
нельсонік

2
@Matt_JD Ми оновили ліцензію на MIT. Удачі вам у роботі! :-)
nelsonic

1
це спрацювало дивовижно! Взяти участь у цьому репо і підтвердити цю відповідь.
aholt

1
настійно рекомендую, відмінно працює на останній версії v14.2.0 на сьогоднішній день
Thomazella

28

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

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

Виявив це після спроби використовувати декаш, як запропонована інша відповідь. Завдяки Ентоні Гарвану .

Функціональна документація тут .


1
Дуже дякую за цю замітку!
mjgpy3

2
Боже, як довго я експериментував, перш ніж знайшов це .... дякую!
Тіаго

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()]вказане у відповіді luff :

> 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 потрібно вказати у ваших розробниках.


4

Я додав би до відповіді 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 (функція (дочірня) {return child.id! == Requ.resolve (_module);}) ;
luff

4

Так, ви можете визнати недійсним кеш.

Кеш зберігається в об'єкті під назвою Requ.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 ];
        }
    }); 

Скажімо, вам потрібен модуль, потім його видалили, потім перевстановили той самий модуль, але використовували іншу версію, яка має інший основний скрипт у своєму пакеті.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;

викликати RequUncached з відносним шляхом:

const requireUncached = require('../helpers/require_uncached')(require);
const myModule = requireUncached('./myModule');

1

Після двох кроків процедура для мене прекрасно працює.

Після зміни Modelфайлу, тобто 'mymodule.js'динамічно, потрібно спочатку видалити попередньо складену модель у мангустній моделі, а потім перезавантажити її за допомогою перезавантаження

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

Якщо це для одиничних тестів, ще одним хорошим інструментом для використання є проксіквайр . Кожен раз, коли ви проксируєте модуль, він скасовує кеш-пам'ять модуля та кешує новий. Це також дозволяє змінювати модулі, необхідні файлу, який ви тестуєте.


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.