dwyl / decache

:shipit: Delete Cached node_modules useful when you need to "un-require" during testing for a fresh state.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Decache removing all child dependencies of target module from cache

andrsnn opened this issue · comments

Currently when a module with child dependencies is removed from the require cache via decache, all of its child dependencies are also removed from the cache.

For example:

When requiring a module that requires several other modules.

a.js
    -- b.js
    -- c.js
        -- d.js

The require.cache will then contain references to each module.
e.g. Object.keys(require.cache) will look like:

[ '/Users/my-user/Documents/git/decache/my-test-file.js',
  '/Users/my-user/Documents/git/decache/decache.js',
  '/Users/my-user/Documents/git/decache/node_modules/callsite/index.js',
  '/Users/my-user/Documents/git/decache/a.js',
  '/Users/my-user/Documents/git/decache/b.js',
  '/Users/my-user/Documents/git/decache/c.js',
  '/Users/my-user/Documents/git/decache/d.js',
  '/Users/my-user/Documents/git/decache/e.js' ]

after calling decache on the a.js module

decache('./a')

The require cache will no longer contain references to the a.js module specified or any of its dependencies

e.g. Object.keys(require.cache) will look like:

[ '/Users/my-user/Documents/git/decache/my-test-file.js',
  '/Users/my-user/Documents/git/decache/decache.js',
  '/Users/my-user/Documents/git/decache/node_modules/callsite/index.js' ]

This is due to the fact that the decache module recursively DFS the target modules dependency tree. Deleting each module passed by the callback iterator.

// Run over the cache looking for the files
// loaded by the specified module name
require.searchCache(moduleName, function (mod) {
    delete require.cache[mod.id];
});

....

require.searchCache = function (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 no else so #ignore else http://git.io/vtgMI
    /* istanbul ignore else  */
    if (mod && ((mod = require.cache[mod]) !== undefined)) {
        // Recursively go over the results
        (function run(mod) {
            // Go over each of the module's children and
            // run over it
            mod.children.forEach(function (child) {
                run(child);
            });

            // Call the specified callback providing the
            // found module
            callback(mod);
        })(mod);
    }
};

Is this behavior intended? Should the DFS instead be removing all references to child.parent? I would assume that some people using this module are depending on this behavior or have worked around it. Curious as to thoughts on this.

Thanks!

@andrsnn this was our desired behaviour as we want the "child" dependencies to be "fresh".
if you would prefer that they were not removed, we could consider adding that as an optional parameter...

@andrsnn If you don't need it to be recursive, then you just need to manually delete the key on the require.cache object:

delete require.cache['/Users/my-user/Documents/git/decache/a.js'];

That should be enough.

@albertogasparin @andrsnn Note you're better off resolving the path:

delete require.cache[require.resolve('/Users/my-user/Documents/git/decache/a.js')];

Source: https://stackoverflow.com/questions/9210542/node-js-require-cache-possible-to-invalidate

delete require.cache[require.resolve('/Users/my-user/Documents/git/decache/a.js')];

I assume we also need to run:

Object.keys(module.constructor._pathCache).forEach(function(cacheKey) {
    if (cacheKey.indexOf(moduleName)>0) {
        delete module.constructor._pathCache[cacheKey];
    }
});

From decache.js#L38... or not? What's that for, anyway?

Could we add to the documentation the explicit info that it does this? This is in face the desired behavior, but I had to dig in the issues to figure it out, or else look in the source or download and try. Would be much preferred if you pointed out in the readme that this is what it does.