Search code examples
javascriptnode.jsrequire

Why is function wrapper called with module.exports instead of module?


Snipper from source code

1
Like the answer below says:
https://stackoverflow.com/a/22771432
When JavaScript files are require()d as Node modules, the Node engine runs the module code inside of a wrapper function. That module-wrapping function is invoked with a this set to module.exports

This is what I don't understand, why calling it module.exports rather than just module? The wrapped function is passed module.exports as this keyword but why don't we just pass module


Solution

  • You can look at the loader code yourself here. Where the this value for a module is set is actually here.

    I can't say why they decided it should be the exports object rather than module object. Perhaps because that's what they expect you to assign to and it's certainly more common to reference exports than it is module.

    Both the module object and the exports objects are passed as an argument to the wrapper and are in scope for your code so you can reference either one without using this and, in my opinion, it makes for clearer code to use module or module.exports directly rather than using this.


    Here's a copy of the relevant code from the above link. The this value is set in the ReflectApply() call near the bottom of this block in the variable named thisValue.

    // Run the file contents in the correct scope or sandbox. Expose
    // the correct helper variables (require, module, exports) to
    // the file.
    // Returns exception, if any.
    Module.prototype._compile = function(content, filename) {
      let moduleURL;
      let redirects;
      if (policy?.manifest) {
        moduleURL = pathToFileURL(filename);
        redirects = policy.manifest.getDependencyMapper(moduleURL);
        policy.manifest.assertIntegrity(moduleURL, content);
      }
    
      maybeCacheSourceMap(filename, content, this);
      const compiledWrapper = wrapSafe(filename, content, this);
    
      let inspectorWrapper = null;
      if (getOptionValue('--inspect-brk') && process._eval == null) {
        if (!resolvedArgv) {
          // We enter the repl if we're not given a filename argument.
          if (process.argv[1]) {
            try {
              resolvedArgv = Module._resolveFilename(process.argv[1], null, false);
            } catch {
              // We only expect this codepath to be reached in the case of a
              // preloaded module (it will fail earlier with the main entry)
              assert(ArrayIsArray(getOptionValue('--require')));
            }
          } else {
            resolvedArgv = 'repl';
          }
        }
    
        // Set breakpoint on module start
        if (resolvedArgv && !hasPausedEntry && filename === resolvedArgv) {
          hasPausedEntry = true;
          inspectorWrapper = internalBinding('inspector').callAndPauseOnStart;
        }
      }
      const dirname = path.dirname(filename);
      const require = makeRequireFunction(this, redirects);
      let result;
      const exports = this.exports;
      const thisValue = exports;
      const module = this;
      if (requireDepth === 0) statCache = new SafeMap();
      if (inspectorWrapper) {
        result = inspectorWrapper(compiledWrapper, thisValue, exports,
                                  require, module, filename, dirname);
      } else {
        result = ReflectApply(compiledWrapper, thisValue,
                              [exports, require, module, filename, dirname]);
      }
      hasLoadedAnyUserCJSModule = true;
      if (requireDepth === 0) statCache = null;
      return result;
    };