node.js require () cache - posible invalidar?


325

De la documentación de node.js:

Los módulos se almacenan en caché después de la primera vez que se cargan. Esto significa (entre otras cosas) que cada llamada a requerir ('foo') obtendrá exactamente el mismo objeto devuelto, si se resuelve en el mismo archivo.

¿Hay alguna manera de invalidar este caché? es decir, para pruebas unitarias, me gustaría que cada prueba funcione en un objeto nuevo.



Otro módulo NPM con un observador: npmjs.com/package/updated-require
Jorge Fuentes González

Es posible almacenar en caché el contenido del archivo sin usar require y evaluarlo para diferentes ámbitos stackoverflow.com/questions/42376161/…
lonewarrior556

Respuestas:


305

Siempre puede eliminar de forma segura una entrada en require.cache sin problemas, incluso cuando hay dependencias circulares. Porque cuando elimina, simplemente elimina una referencia al objeto del módulo en caché, no el objeto del módulo en sí, el objeto del módulo no será GCed porque en caso de dependencias circulares, todavía hay un objeto que hace referencia a este objeto del módulo.

Supongamos que tienes:

script a.js:

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

y script b.js:

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

Cuando tu lo hagas:

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

conseguirás:

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

ahora si editas tu b.js:

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

y hacer:

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

conseguirás:

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

===

Lo anterior es válido si se ejecuta directamente node.js. Sin embargo, si usa herramientas que tienen su propio sistema de almacenamiento en caché de módulos, como jest , la declaración correcta sería:

jest.resetModules();

2
¿podría explicar por qué { ... a: undefined}cuando lo requiere b.jspor primera vez? Yo esperaría igualar 'a from a.js'. Gracias
ira

1
¿Por qué es un indefinido?
Jeff P Chacko

44
Respuesta tardía, pero por lo que deduzco b[a]no está definido la primera vez ya que hay una dependencia circular. a.jsrequiere b.jsque a su vez requiere a.js. a.jsaún no está completamente cargado y exports.aaún no se ha definido, por lo b.jsque no obtiene nada.
nik10110

alguna forma de hacer esto si estoy usando require.main.require(path)como se describe aquí? stackoverflow.com/questions/10860244/…
Flion

186

Si siempre desea volver a cargar su módulo, puede agregar esta función:

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

y luego usar en requireUncached('./myModule')lugar de requerir.


66
Esto es perfecto en combinación con el fs.watchmétodo que escucha los cambios de archivo.
ph3nx

2
¿cual es el riesgo?
Scarass

La misma pregunta que tengo, ¿cuál es el riesgo de usar esta solución y no la respuesta aceptada?
Rotimi-best

1
Es lo mismo realmente. Dependiendo de cómo esté estructurado el código, las cosas podrían fallar cuando intente inicializarlo nuevamente. Ex. si el módulo inicia un servidor y escucha un puerto. La próxima vez que requiera Abrir el módulo, fallará ya que ese puerto ya está abierto, y así sucesivamente.
luff

133

Sí, puede acceder a la memoria caché a través de require.cache[moduleName]donde moduleNameestá el nombre del módulo al que desea acceder. Eliminar una entrada al llamar delete require.cache[moduleName]hará requireque se cargue el archivo real.

Así es como eliminaría todos los archivos en caché asociados con el 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));
    }
};

El uso sería:

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

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

Dado que este código usa el mismo resolutor require, simplemente especifique lo que necesite.


"Unix no fue diseñado para evitar que sus usuarios hagan cosas estúpidas, ya que eso también les impediría hacer cosas inteligentes". - Doug Gwyn

Creo que debería haber una forma de realizar una carga explícita de módulo sin caché.


17
+1 solo por la cita de Doug. Necesitaba a alguien para expresar lo que también creía :)
Poni

1
Excelente respuesta! Si desea iniciar una respuesta de nodo con la recarga habilitada, consulte esta esencia .
gleitz

1
increíble. Yo agregaría esto a la require.uncachefunción. `` `// ver 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 ha requerido un módulo, luego lo desinstaló, luego reinstaló el mismo módulo pero usó una versión diferente que tiene un script principal diferente en su paquete.json, el siguiente requerimiento fallará porque ese script principal no existe porque está en cachéModule._pathCache
bentael

mierda. Mi comentario es terrible. No pude agregar código en este comentario y es demasiado tarde para editar, así que respondí. @Ben Barkay si pudieras editar tu pregunta para agregar el pequeño fragmento de código a turequire.uncache
bentael

Gracias @bentael, he agregado esto a mi respuesta.
Ben Barkay

39

Hay un módulo simple para eso ( con pruebas )

Tuvimos este problema exacto al probar nuestro código ( elimine los módulos en caché para que puedan volver a requerirse en un estado nuevo ), por lo que revisamos todas las sugerencias de las personas en las diversas preguntas y respuestas de StackOverflow y armamos un módulo simple node.js ( con pruebas ):

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

Como era de esperar, funciona tanto para paquetes npm publicados como para módulos definidos localmente . Windows, Mac, Linux, etc.

Estado de compilación codecov.io Código de mantenibilidad climática Estado de dependencias DevDependencies Status

¿Cómo? ( uso )

El uso es bastante simple:

Instalar en pc

Instale el módulo desde npm:

npm install decache --save-dev

Úselo en su 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)

Si tiene alguna pregunta o necesita más ejemplos, cree un problema de GitHub: https://github.com/dwyl/decache/issues


1
He estado mirando esto y se ve realmente genial para mí usarlo cuando lo pruebo para poder descargar y volver a cargar un módulo bajo condiciones específicas, pero desafortunadamente estoy en el trabajo y mi compañía evita las licencias GPL. Solo quiero usarlo para probar, así que todavía lo estoy considerando porque parece muy útil.
Matt_JD

@ Matt_JD gracias por sus comentarios. ¿Qué licencia preferirías?
nelsonic

2
@Matt_JD Hemos actualizado la licencia a MIT. ¡Buena suerte con tu trabajo! :-)
nelsonic

1
¡Esto funcionó increíblemente! Protagonizando este repositorio y votando esta respuesta.
aholt

1
muy recomendable, funcionando bien en la última v14.2.0 a partir de hoy
Thomazella

28

Para cualquiera que se encuentre con esto que esté usando Jest, porque Jest hace su propio almacenamiento en caché del módulo, hay una función incorporada para esto, solo asegúrese de jest.resetModulesejecutar, por ejemplo. después de cada una de tus pruebas:

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

Encontré esto después de tratar de usar decache como sugiere otra respuesta. Gracias a Anthony Garvan .

Documentación de funciones aquí .


1
¡Muchas gracias por esta nota!
mjgpy3

2
Dios, ¿cuánto tiempo experimenté antes de encontrar esto ... gracias!
Tiago

16

La solución es usar:

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

Encuentre aquí algunas explicaciones básicas para aquellos que, como yo, son un poco nuevos en esto:

Supongamos que tiene un example.jsarchivo ficticio en la raíz de su directorio:

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

Entonces te require()gusta esto:

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

Si luego agrega una línea como esta a example.js:

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

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

Y continuar en la consola, el módulo no se actualiza:

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

Ahí es cuando puedes usar lo delete require.cache[require.resolve()]indicado en la respuesta de luff :

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

Por lo tanto, el caché se limpia y require()captura el contenido del archivo nuevamente, cargando todos los valores actuales.


En mi humilde opinión, esta es la respuesta más apropiada
Piyush Katariya

5

rewire es ideal para este caso de uso, obtienes una nueva instancia con cada llamada. Fácil inyección de dependencia para la prueba de la unidad node.js.

rewire agrega un setter y getter especial a los módulos para que pueda modificar su comportamiento para una mejor prueba de la unidad. Puedes

inyectar simulacros para otros módulos o globales como las fugas privadas del proceso. Las variables privadas anulan las variables dentro del módulo. rewire no carga el archivo y evalúa el contenido para emular el mecanismo requerido del nodo. De hecho, utiliza los requisitos del nodo para cargar el módulo. Por lo tanto, su módulo se comporta exactamente igual en su entorno de prueba que en circunstancias normales (excepto sus modificaciones).

Buenas noticias para todos los adictos a la cafeína: rewire funciona también con Coffee-Script. Tenga en cuenta que en este caso CoffeeScript debe aparecer en sus devDependencies.


4

Agregaría a la respuesta de luff una línea más y cambiaría el nombre del 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)
}

Entonces, ¿esto es para que la función funcione en submódulos? ¡Agradable! Una forma más corta de eliminar el módulo de la matriz module.children es utilizando una función de filtro: module.children = module.children.filter (function (child) {return child.id! == require.resolve (_module);}) ;
luff

4

Sí, puedes invalidar el caché.

El caché se almacena en un objeto llamado require.cache al que puede acceder directamente de acuerdo con los nombres de archivo (por ejemplo, /projects/app/home/index.jsen lugar de ./homelo que usaría en una require('./home')declaración).

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

Nuestro equipo ha encontrado útil el siguiente módulo. Invalidar ciertos grupos de módulos.

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


3

No pude agregar código en el comentario de una respuesta. Pero usaría la respuesta de @Ben Barkay y luego agregaría esto a la require.uncachefunción.

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

Supongamos que ha requerido un módulo, luego lo desinstaló, luego reinstaló el mismo módulo pero usó una versión diferente que tiene un script principal diferente en su paquete.json, el siguiente requerimiento fallará porque ese script principal no existe porque está en caché Module._pathCache


3

No estoy 100% seguro de lo que quieres decir con 'invalidar', pero puedes agregar lo siguiente arriba de las requiredeclaraciones para borrar el caché:

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

Tomado del comentario de @ Dancrumb aquí


2

requireUncached con ruta relativa: 🔥

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

module.exports = requireUncached;

invocar requireUncached con ruta relativa:

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

1

El siguiente procedimiento de dos pasos me funciona perfectamente.

Después de cambiar el Modelarchivo, es decir 'mymodule.js', dinámicamente, primero debe eliminar el modelo precompilado en el modelo de mangosta y luego volver a cargarlo con 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

Si es para pruebas unitarias, otra buena herramienta para usar es proxyquire . Cada vez que solicita proxy el módulo, invalidará el caché del módulo y almacenará uno nuevo. También le permite modificar los módulos requeridos por el archivo que está probando.


0

Hice un pequeño módulo para eliminar el módulo del caché después de cargarlo. Esto fuerza la reevaluación del módulo la próxima vez que se requiera. Ver 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

PD: también puedes poner "autodestrucción" en el módulo mismo. Ver https://github.com/bahmutov/unload-me

PSS: se requieren más trucos con Node en mi https://glebbahmutov.com/blog/hacking-node-require/

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.