node.js require () 캐시-무효화 할 수 있습니까?


325

node.js 문서에서 :

모듈은 처음로드 된 후 캐시됩니다. 이것은 require ( 'foo')에 대한 모든 호출이 동일한 파일로 해석되면 정확히 동일한 객체를 반환한다는 것을 의미합니다.

이 캐시를 무효화하는 방법이 있습니까? 즉, 단위 테스트의 경우 각 테스트가 새로운 객체에서 작동하기를 원합니다.




require를 사용하지 않고 파일 내용을 캐시하고 다른 범위로
평가할 수 있습니다.

답변:


305

순환 종속성이있는 경우에도 문제없이 require.cache의 항목을 항상 안전하게 삭제할 수 있습니다. 삭제하면 모듈 오브젝트 자체가 아닌 캐시 된 모듈 오브젝트에 대한 참조 만 삭제하므로 순환 종속성의 경우이 모듈 오브젝트를 참조하는 오브젝트가 여전히 있으므로 모듈 오브젝트가 GC되지 않습니다.

당신이 가지고 있다고 가정 해보십시오 :

스크립트 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
정의되지 않은 이유는 무엇입니까?
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')require 대신 사용하십시오 .


6
fs.watch파일 변경을 수신 하는 방법 과 함께 사용하면 완벽 합니다.
ph3nx

2
위험은 무엇입니까?
Scarass

내가 가진 동일한 질문,이 솔루션을 사용할 때의 위험은 무엇입니까?
rotimi-best

1
정말 동일합니다. 코드의 구조에 따라 다시 초기화하려고하면 작동이 중단 될 수 있습니다. 전의. 모듈이 서버를 시작하고 포트를 청취하는 경우 다음에 모듈을 캐시 해제해야 할 경우 해당 포트가 이미 열려 있으므로 모듈이 실패합니다.
luff

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필요한 것을 지정하십시오.


"유닉스는 사용자가 어리석은 일을하는 것을 막기 위해 고안된 것이 아니었다. – 더그 기윈

캐시되지 않은 모듈로드를 명시 적으로 수행 할 수있는 방법 이 있어야 한다고 생각합니다 .


17
Doug의 인용문에 대해서만 +1. 나는 또한 내가 믿었던 것을
표현할

1
훌륭한 답변! 리로딩이 활성화 된 상태에서 노드 repl을 시작하려면 이 요점을 확인하십시오 .
gleitz

1
대박. 이것을 require.uncache함수에 추가 할 것 입니다. ```// github.com/joyent/node/issues/8266 참조 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) delete module.constructor ._pathCache [k];}); ```당신이 모듈을 필요로했다가 제거했다가 같은 모듈을 다시 설치했지만 package.json에 다른 주 스크립트를 가진 다른 버전을 사용했다고 가정하면 다음 주 스크립트는 존재하지 않기 때문에 실패합니다. 그것은 캐시에Module._pathCache
bentael

쓰레기. 내 의견은 끔찍하다. 이 주석에 코드를 깔끔하게 추가 할 수 없었고 편집하기에는 너무 늦었습니다. 그래서 대답했습니다. @Ben Barkay 당신은 당신의 질문에 작은 코드를 추가하기 위해 질문을 편집 할 수 있다면require.uncache
bentael

감사합니다 @bentael, 나는 이것을 내 대답에 추가했습니다.
Ben Barkay

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

의견을 보내 주셔서 감사합니다. 어떤 라이센스를 원하십니까?
nelsonic

2
@Matt_JD 라이센스를 MIT로 업데이트했습니다. 당신의 일에 행운을 빕니다! :-)
nelsonic

1
이것은 놀랍게 작동했습니다! 이 저장소에 별표를 표시하고이 답변을지지합니다.
alt

1
오늘은 최신 v14.2.0에서 정상적으로 실행하는 것이 좋습니다
Thomazella

28

Jest를 사용하는 사람이라면 Jest가 자체 모듈 캐싱을 수행하기 때문에 내장 기능이 jest.resetModules있습니다. 각 테스트 후 :

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

제안 된 다른 답변처럼 decache 를 사용하려고 시도한 후에 이것을 발견 했습니다. Anthony Garvan 에게 감사합니다 .

기능 문서는 여기에 있습니다 .


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()파일의 내용을 다시 캡처하여 모든 현재 값을로드합니다.


IMHO 이것은 가장 적합한 답변입니다
Piyush Katariya

5

rewire 는이 사용 사례에 적합하며 각 호출마다 새 인스턴스를 얻습니다. node.js 단위 테스트를위한 쉬운 의존성 주입.

rewire는 모듈에 특수 setter 및 getter를 추가하여 더 나은 단위 테스트를 위해 해당 동작을 수정할 수 있습니다. 당신은 할 수있다

프로세스 누출 개인 변수와 같은 다른 모듈 또는 전역에 대한 모의는 모듈 내의 변수보다 우선합니다. rewire는 파일을로드하지 않고 내용을 평가하여 노드의 요구 메커니즘을 에뮬레이트합니다. 실제로 그것은 모듈을로드하기 위해 노드 자신의 요구를 사용합니다. 따라서 모듈은 테스트 환경에서 일반 환경 (수정 사항 제외)과 동일하게 작동합니다.

모든 카페인 중독자에게 희소식 : 재 와이어는 Coffee-Script에서도 작동합니다. 이 경우 CoffeeScript가 devDependencies에 나열되어 있어야합니다.


4

luff 's answer에 한 줄을 더 추가하고 매개 변수 이름을 변경합니다.

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것과 반대 ). ./homerequire('./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 ];
        }
    }); 

모듈이 필요했다가 제거한 다음 동일한 모듈을 다시 설치했지만 패키지에 다른 기본 스크립트가있는 다른 버전을 사용했다고 가정 해보십시오. 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'동적으로 변경 한 후 몽구스 모델 에서 사전 컴파일 된 모델을 먼저 삭제 한 다음 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

추신 : "자체 파괴"를 모듈 자체에 넣을 수도 있습니다. 참조 https://github.com/bahmutov/unload-me를

PSS : 내 https://glebbahmutov.com/blog/hacking-node-require/ 에서 Node에 대한 추가 트릭

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.