node.jsmodule.exports

How to detect if a script exports something when loaded?


Assuming I have a script 'helpers.js' in the same directory as 'main.js', and load the first in the latter, 'helpers.js' will return an empty object {} when not exporting anything, no matter what the content is, which is a default value for the module.exports variable, I guess.

Is there a way to check, if the empty object was intentionally exported by 'helpers.js'?

Example 1, no export:

// helpers.js

// Any valid Javascript here, just no exports.



// main.js

const help = require('./helpers')

console.log(help)

>>> {}

Example 2, with export:

// helpers.js

let exportMe = require('./you').getNewestMe

module.exports = exportMe()



// main.js

const help = require('./helpers')

console.log(help)

>>> {}

Solution

  • I had something different in mind but I realized that my original solution wouldn't work, so let's see...

    When a CommonJS module is loaded (i.e. required for the first time), Node.js runs a special function - a loader. The loader function has access to the state of a module before it is actually loaded, so it can inspect and modify the original values of some properties of a module like module.exports. The idea is that if we can somehow mark that original value, we can distinguish it from an empty object assigned later while the module was being loaded.

    The loader function can be retrieved and changed with Module._extensions['.js'] where Module is the module constructor, i.e. require('module') or module.constructor.(Module._extensions is not the same as the deprecated require.extensions, but that's another story...).

    The loader function is invoked with two arguments: the module object and the filename. Then the original exports object is unsurprisingly, module.exports. In the code below, I'm going to use a symbol to mark the original empty exports object:

    const noExports = Symbol('no exports');
    const extensions = module.constructor._extensions;
    const jsLoader = extensions['.js'];
    extensions['.js'] = (module, filename) => {
        module.exports[noExports] = filename;
        jsLoader(module, filename);
    };
    
    const help1 = require('./helpers-without-exports.js');
    const help2 = require('./helpers-with-empty-exports.js');
    
    console.log(noExports in help1); // true
    console.log(noExports in help2); // false
    

    This method only works for modules that are loaded after the loader function is patched, and it only determines if module.exports is changed after the module is created, so it does not detect values exported with exports.myFunction = ... (in which case the exported object would be non-empty anyway).

    Another special case is a module that re-exports another module that does not export anything (e.g. module.exports = require('./foo'); where foo.js is an empty file). To detect those cases too, if necessary, one could compare the value of the symbol property with the module filename assigned to the symbol, and see if they match:

    console.log(help1[noExports] === require.resolve('./helpers-without-exports.js')); // true
    console.log(help2[noExports] === require.resolve('./helpers-with-empty-exports.js')); // false