O node.js requer cache () - é possível invalidar?


325

Na documentação do node.js.

Os módulos são armazenados em cache após a primeira vez que são carregados. Isso significa (entre outras coisas) que toda chamada a ser solicitada ('foo') receberá exatamente o mesmo objeto retornado, se resolver para o mesmo arquivo.

Existe uma maneira de invalidar esse cache? ou seja, para testes de unidade, eu gostaria que cada teste trabalhasse em um objeto novo.



Outro módulo NPM com um observador: npmjs.com/package/updated-require
Jorge Fuentes González

É possível armazenar em cache o conteúdo do arquivo sem usar exigem e eval-lo para diferentes escopos stackoverflow.com/questions/42376161/...
lonewarrior556

Respostas:


305

Você sempre pode excluir com segurança uma entrada no require.cache sem problemas, mesmo quando houver dependências circulares. Como quando você exclui, você exclui apenas uma referência ao objeto do módulo em cache, não ao próprio objeto do módulo, o objeto do módulo não será submetido ao GC porque, no caso de dependências circulares, ainda há um objeto referenciando esse objeto do módulo.

Suponha que você tenha:

script a.js:

var b=require('./b.js').b;
exports.a='a from a.js';
exports.b=b;

e script b.js:

var a=require('./a.js').a;
exports.b='b from b.js';
exports.a=a;

quando você faz:

var a=require('./a.js')
var b=require('./b.js')

você vai ter:

> a
{ a: 'a from a.js', b: 'b from b.js' }
> b
{ b: 'b from b.js', a: undefined }

Agora, se você editar seu b.js:

var a=require('./a.js').a;
exports.b='b from b.js. changed value';
exports.a=a;

e fazer:

delete require.cache[require.resolve('./b.js')]
b=require('./b.js')

você vai ter:

> a
{ a: 'a from a.js', b: 'b from b.js' }
> b
{ b: 'b from b.js. changed value',
  a: 'a from a.js' }

===

O acima é válido se estiver executando diretamente o node.js. No entanto, se estiver usando ferramentas que possuem seu próprio sistema de armazenamento em cache de módulo, como o gracejo , a instrução correta seria:

jest.resetModules();

2
você poderia explicar por que { ... a: undefined}ao solicitar b.jspela primeira vez? Eu esperaria ser igual 'a from a.js'. Obrigado
ira

1
porque é indefinido?
Jeff P Chacko

4
Resposta tardia, mas pelo que entendi b[a]é indefinida pela primeira vez, pois há uma dependência circular. a.jsrequer o b.jsque por sua vez exige a.js. a.jsainda não está totalmente carregado e exports.aainda está para ser definido, portanto, b.jsget's nothing.
Nik10110

alguma maneira de fazer isso se eu estiver usando require.main.require(path)como descrito aqui? stackoverflow.com/questions/10860244/…
Flion

186

Se você sempre deseja recarregar seu módulo, você pode adicionar esta função:

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

e use em requireUncached('./myModule')vez de exigir.


6
Isso é perfeito em combinação com o fs.watchmétodo que escuta as alterações no arquivo.
Ph3nx

2
qual é o risco?
Scarass

Mesma pergunta que tenho, qual é o risco de usar esta solução e não a resposta aceita?
rotimi-best 11/02/19

1
É o mesmo mesmo. Dependendo de como o código está estruturado, as coisas podem falhar quando você tenta inicializá-lo novamente. Ex. se o módulo iniciar um servidor e ouvir uma porta. Na próxima vez em que você precisar desanexar o módulo, ele falhará, pois essa porta já está aberta e assim por diante.
luff

133

Sim, você pode acessar o cache via require.cache[moduleName]onde moduleNameestá o nome do módulo que você deseja acessar. Excluir uma entrada chamando delete require.cache[moduleName]fará com requireque o arquivo real seja carregado.

É assim que você removeria todos os arquivos em cache associados ao módulo:

/**
 * 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));
    }
};

O uso seria:

// Load the package
var mypackage = require('./mypackage');

// Purge the package from cache
purgeCache('./mypackage');

Como esse código usa o mesmo resolvedor require, basta especificar o que você exigiria.


"O Unix não foi projetado para impedir seus usuários de fazerem coisas estúpidas, pois isso também os impediria de fazer coisas inteligentes". - Doug Gwyn

Eu acho que deveria ter havido uma maneira de executar um carregamento explícito de módulo não armazenado em cache.


17
+1 apenas para a citação de Doug. Eu precisava de alguém para a frase o que eu também acreditava em :)
Poni

1
Excelente resposta! Se você deseja iniciar uma substituição de nó com o recarregamento ativado, confira esta lista .
Gleitz

1
impressionante. Eu adicionaria isso à require.uncachefunção. `` // consulte github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) delete module.constructor ._pathCache [k];}); `` Digamos que você tenha exigido um módulo, depois o desinstalou e reinstalou o mesmo módulo, mas usou uma versão diferente que possui um script principal diferente em seu pacote.json, o próximo requisito falhará porque esse script principal não existe porque está armazenado em cacheModule._pathCache
bentael

porcaria. meu comentário é terrível. Como não consegui adicionar código nesse comentário, é muito tarde para editar, então respondi. @ Ben Barkay se você pudesse editar sua pergunta para adicionar um pequeno trecho de código ao seurequire.uncache
bentael

Obrigado @bentael, adicionei isso à minha resposta.
Ben Barkay

39

Existe um módulo simples para isso ( com testes )

Tivemos esse problema exato ao testar nosso código ( exclua os módulos em cache para que possam ser solicitados novamente em um estado novo ). Analisamos todas as sugestões de pessoas nas várias perguntas e respostas do StackOverflow e montamos um módulo node.js simples ( com testes ):

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

Como seria de esperar, funciona para ambos os pacotes NPM publicados e localmente módulos definidos. Windows, Mac, Linux, etc.

Status da compilação codecov.io Código Sustentabilidade climática Status das dependências Status de devDependencies

Quão? ( uso )

O uso é bem simples:

instalar

Instale o módulo a partir do npm:

npm install decache --save-dev

Use-o no seu código:

// 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)

Se você tiver alguma dúvida ou precisar de mais exemplos, crie um problema no GitHub: https://github.com/dwyl/decache/issues


1
Eu estive analisando isso e me parece realmente ótimo usar durante os testes para poder descarregar e recarregar um módulo sob condições específicas, mas infelizmente estou no trabalho e minha empresa evita as licenças GPL. Eu só quero usá-lo para testes, por isso continuo considerando porque parece muito útil.
Matt_JD

@Matt_JD obrigado pelo seu feedback. qual licença você prefere?
Nelsonic 9/03/16

2
@Matt_JD Atualizamos a licença para o MIT. Boa sorte com o seu trabalho! :-)
nelsonic

1
isso funcionou incrivelmente! Estrelando este repo e votando esta resposta.
aholt

1
recomendo, rodando bem na v14.2.0 mais recente a partir de hoje
Thomazella

28

Para quem se deparar com alguém que esteja usando o Jest, porque o Jest faz seu próprio cache de módulo, há uma função interna para isso - apenas certifique-se de jest.resetModulesexecutar, por exemplo. após cada um de seus testes:

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

Encontrei isso depois de tentar usar o decache como outra resposta sugerida. Agradecimentos a Anthony Garvan .

Documentação de função aqui .


1
Muito obrigado por esta nota!
precisa saber é o seguinte

2
Deus, quanto tempo eu experimentei antes de encontrar isso .... obrigado!
Tiago

16

As soluções são usar:

delete require.cache[require.resolve(<path of your script>)]

Encontre aqui algumas explicações básicas para quem, como eu, é um pouco novo nisso:

Suponha que você tenha um example.jsarquivo fictício na raiz do seu diretório:

exports.message = "hi";
exports.say = function () {
  console.log(message);
}

Então você require()gosta disso:

$ node
> require('./example.js')
{ message: 'hi', say: [Function] }

Se você adicionar uma linha como esta a example.js:

exports.message = "hi";
exports.say = function () {
  console.log(message);
}

exports.farewell = "bye!";      // this line is added later on

E continue no console, o módulo não é atualizado:

> require('./example.js')
{ message: 'hi', say: [Function] }

É quando você pode usar o delete require.cache[require.resolve()]indicado na resposta do luff :

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

Portanto, o cache é limpo e require()captura o conteúdo do arquivo novamente, carregando todos os valores atuais.


IMHO Esta é a resposta mais adequada #
Piyush Katariya

5

rewire é ótimo para este caso de uso, você recebe uma nova instância a cada chamada. Injeção de dependência fácil para o teste de unidade node.js.

O rewire adiciona um setter e um getter especiais aos módulos para que você possa modificar o comportamento deles para um melhor teste de unidade. Você pode

injetar zombarias para outros módulos ou globais, como vazamento de processo, variáveis ​​privadas substituem variáveis ​​dentro do módulo. rewire não carrega o arquivo e avalia o conteúdo para emular o mecanismo de solicitação do nó. De fato, ele usa o próprio nó requerido para carregar o módulo. Assim, seu módulo se comporta exatamente no mesmo ambiente de teste e em circunstâncias regulares (exceto suas modificações).

Boas notícias para todos os viciados em cafeína: a religação também funciona com o Coffee-Script. Observe que, neste caso, o CoffeeScript precisa estar listado em suas devDependencies.


4

Eu adicionaria à resposta do luff mais uma linha e mudaria o nome do parâmetro:

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

Então isso é para fazer a função funcionar em submódulos? Agradável! Uma maneira mais curta de remover o módulo da matriz module.children é usando uma função de filtro: module.children = module.children.filter (função (filho) {return child.id! == require.resolve (_module);}) ;
luff

4

Sim, você pode invalidar o cache.

O cache é armazenado em um objeto chamado require.cache, que você pode acessar diretamente de acordo com os nomes dos arquivos (por exemplo /projects/app/home/index.js, ao contrário do ./homeque você usaria em uma require('./home')instrução).

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

Nossa equipe achou útil o seguinte módulo. Para invalidar certos grupos de módulos.

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


3

Não consegui adicionar o código ordenadamente no comentário de uma resposta. Mas eu usaria a resposta de @Ben Barkay e a adicionaria à require.uncachefunção.

    // 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 ];
        }
    }); 

Digamos que você tenha exigido um módulo, desinstalado e reinstalado, mas reinstalado o mesmo módulo, mas usado uma versão diferente com um script principal diferente em seu package.json, o próximo requisito falhará porque esse script principal não existe porque está armazenado em cache no Module._pathCache


3

Não tenho 100% de certeza do que você quer dizer com 'invalidar', mas você pode adicionar o seguinte acima das requireinstruções para limpar o cache:

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

Retirado do comentário de @ Dancrumb aqui


2

requireUncached com caminho relativo: 🔥

const requireUncached = require => module => {
  delete require.cache[require.resolve(module)];
  return require(module);
};

module.exports = requireUncached;

invoque requireUncached com caminho relativo:

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

1

O procedimento a seguir em duas etapas está funcionando perfeitamente para mim.

Após alterar o Modelarquivo, ou seja 'mymodule.js', dinamicamente, é necessário excluir primeiro o modelo pré-compilado no modelo mangusto e depois recarregá-lo usando 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

Se for para testes de unidade, outra boa ferramenta a ser usada é o proxyquire . Sempre que você solicitar o módulo, ele invalidará o cache do módulo e o cache de um novo. Também permite modificar os módulos exigidos pelo arquivo que você está testando.


0

Eu fiz um pequeno módulo para excluir o módulo do cache após o carregamento. Isso força a reavaliação do módulo na próxima vez que for necessário. Consulte 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: você também pode colocar "autodestruição" no próprio módulo. Consulte https://github.com/bahmutov/unload-me

PSS: mais truques com o Node exigem no meu https://glebbahmutov.com/blog/hacking-node-require/

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.