Why does a module level return statement work in Node.js?
TL;DR
The modules are wrapped by Node.js within a function, like this:
(function (exports, require, module, __filename, __dirname) {
// our actual module code
});
So the above shown code is actually executed by Node.js, like this
(function (exports, require, module, __filename, __dirname) {
console.log("Trying to reach");
return;
console.log("dead code");
});
That is why the program prints only Trying to reach
and skips the console.log
following the return
statement.
Internals
This is where we need to understand how Node.js processes Modules. When you run your .js file with Node.js, it treats that as a module and compiles it with the v8 JavaScript engine.
It all starts with runMain
function,
// bootstrap main module.
Module.runMain = function() {
// Load the main module--the command line argument.
Module._load(process.argv[1], null, true);
// Handle any nextTicks added in the first tick of the program
process._tickCallback();
};
In the Module._load
function, a new Module object is created and it is loaded.
var module = new Module(filename, parent);
...
...
try {
module.load(filename);
hadException = false;
The Module
function's load
does this,
// Given a file name, pass it to the proper extension handler.
Module.prototype.load = function(filename) {
debug('load ' + JSON.stringify(filename) +
' for module ' + JSON.stringify(this.id));
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
};
Since our file's extension is js
, we see what the Module._extensions
has for .js
. It can be seen here
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
};
The module
object's _compile
is invoked in that function and this is where the magic happens,
// 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.
This is where the require
function, used by our node modules's is created first.
function require(path) {
return self.require(path);
}
require.resolve = function(request) {
return Module._resolveFilename(request, self);
};
Object.defineProperty(require, 'paths', { get: function() {
throw new Error('require.paths is removed. Use ' +
'node_modules folders, or the NODE_PATH ' +
'environment variable instead.');
}});
require.main = process.mainModule;
// Enable support to add extra extension types
require.extensions = Module._extensions;
require.registerExtension = function() {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
};
require.cache = Module._cache;
And then there is something about wrapping the code,
// create wrapper function
var wrapper = Module.wrap(content);
We set out to find what Module.wrap
does, which is nothing but
Module.wrap = NativeModule.wrap;
which is defined in src/node.js
file and that is where we find this,
NativeModule.wrap = function(script) {
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};
NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
This is how our programs have access to the magic variables, exports
, require
, module
, __filename
and __dirname
Then the wrapped function is compiled and executed here with runInThisContext
,
var compiledWrapper = runInThisContext(wrapper, { filename: filename });
And then finally, the module's compiled wrapped function object is invoked like this, with values populated for exports
, require
, module
, __filename
and __dirname
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
This is how our modules are processed and executed by Node.js and that is why the return
statement works without failing.
Use of return keyword in Javascript scripts out of functions
This woke my curiosity upon what else does returning in the middle of a Node module, apart from terminating execution, does.
When you run a Javascript script file with node.js, it is wrapped in a module wrapper function as you can see here in the node.js docs for Modules.
(function (exports, require, module, __filename, __dirname) {
// Your module code actually lives in here
});
So, your return
is a return from that module wrapper function and it does nothing except stop the execution of any further code in that module. Modules in node.js don't have any documented behavior for returning a value from the module. So, return;
or return 10;
or no return
value at all, all have the same behavior. The return value is not used.
As always in a Javascript function, you could use a plain return
to skip the execution of the rest of the code in the module, though it is probably better to just use an if/else to more clearly execute only the code you want to execute.
My expectation was that this would make the return status of the process be equal to 10. However, upon asking the return status of the last utility ran under bash, by echo $?, I would receive 0, so that is not the case.
If you want to set a return value for the process, you should use:
process.exit(10);
In node modules, if you want to share data with other modules, you can use module.exports = some object or value
, but that has no effect on the main module since the node loader is not paying any attention to either the return value of the module.exports
from the main module. That is only useful with other modules that you explicitly require()
in.
Also, I wonder what happens in the context of browser
<script>s
.
Using a return
statement at the global level (which is what the top level of a browser <script>
tag would be is not permitted by the language. return
is only appropriate inside a function. Doing so generates this error:
Uncaught SyntaxError: Illegal return statement
What is the purpose of Node.js module.exports and how do you use it?
module.exports
is the object that's actually returned as the result of a require
call.
The exports
variable is initially set to that same object (i.e. it's a shorthand "alias"), so in the module code you would usually write something like this:
let myFunc1 = function() { ... };
let myFunc2 = function() { ... };
exports.myFunc1 = myFunc1;
exports.myFunc2 = myFunc2;
to export (or "expose") the internally scoped functions myFunc1
and myFunc2
.
And in the calling code you would use:
const m = require('./mymodule');
m.myFunc1();
where the last line shows how the result of require
is (usually) just a plain object whose properties may be accessed.
NB: if you overwrite exports
then it will no longer refer to module.exports
. So if you wish to assign a new object (or a function reference) to exports
then you should also assign that new object to module.exports
It's worth noting that the name added to the exports
object does not have to be the same as the module's internally scoped name for the value that you're adding, so you could have:
let myVeryLongInternalName = function() { ... };
exports.shortName = myVeryLongInternalName;
// add other objects, functions, as required
followed by:
const m = require('./mymodule');
m.shortName(); // invokes module.myVeryLongInternalName
What does this mean in a nodejs module?
this
(in the context of a module) is the same as exports
in node.js. However you should generally use exports
/module.exports
instead, so that it's explicitly clear what you're modifying.
Meaning of this in node.js modules and functions
Here's a few fundamental facts you must understand to clarify the situation:
In the top-level code in a Node module,
this
is equivalent tomodule.exports
. That's the empty object you see.When you use
this
inside of a function, the value ofthis
is determined anew before each and every execution of the function, and its value is determined by how the function is executed. This means that two invocations of the exact same function object could have differentthis
values if the invocation mechanisms are different (e.g.aFunction()
vs.aFunction.call(newThis)
vs.emitter.addEventListener("someEvent", aFunction);
, etc.) In your case,aFunction()
in non-strict mode runs the function withthis
set to the global object.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 athis
set tomodule.exports
. (Recall, above, a function may be run with an abitrarythis
value.)
Thus, you get different this
values because each this
resides inside a different function: the first is inside of the Node-created module-wrapper function and the second is inside of aFunction
.
Node.js modules VS IIFE functions' scope
The scopes that a function has access to depend on where the function is declared, not where the function is called.
The two JS modules are different scopes. The function you create in test2
doesn't have access to any variables declared in test1
.
require
ing a module makes its exports available in test1
, but it doesn't change which scopes it has access to.
If you want to use data from test1
in the exported function, you'll need to change that function to accept an argument and then pass it.
module.exports vs exports in Node.js
Setting module.exports
allows the database_module
function to be called like a function when required
. Simply setting exports
wouldn't allow the function to be
exported because node exports the object module.exports
references. The following code wouldn't allow the user to call the function.
module.js
The following won't work.
exports = nano = function database_module(cfg) {return;}
The following will work if module.exports
is set.
module.exports = exports = nano = function database_module(cfg) {return;}
console
var func = require('./module.js');
// the following line will **work** with module.exports
func();
Basically node.js doesn't export the object that exports
currently references, but exports the properties of what exports
originally references. Although Node.js does export the object module.exports
references, allowing you to call it like a function.
2nd least important reason
They set both module.exports
and exports
to ensure exports
isn't referencing the prior exported object. By setting both you use exports
as a shorthand and avoid potential bugs later on down the road.
Using exports.prop = true
instead of module.exports.prop = true
saves characters and avoids confusion.
Does Importing(require) module in JS also executes whole code as well?
Yes. The first time a CommonJS module is loaded with require()
, any top level code is executed. As you can see, this has to be the case so that exports.calcModule = calc
runs and establishes the exports for the module and your console.log('hellow from module')
would also run.
Once loaded, the module is cached so any other calls to require()
for that same module will just return the export object from the original execution of the module and the top level code will not run again. So, the top level code only runs once, no matter how many times the module is loaded within your program.
does importing a module includes executing whole module file?
Yes, it executes all top level code in the module you are loading.
Related Topics
Twitter Bootstrap Alert Message Close and Open Again
How to Call a Parent Method from Child Class in JavaScript
JavaScript Design Pattern: Difference Between Module Pattern and Revealing Module Pattern
How to Detect with JavaScript/Jquery If the User Is Currently Active on the Page
Export Table from Database to CSV File
React.Js Replace Img Src Onerror
How to Serve a File for Download with Angularjs or JavaScript
What Does the @ Symbol Do in JavaScript Imports
Does JavaScript Process Using an Elastic Racetrack Algorithm
Batchsize Field Name Ignored in Field Projection
How to Get the Back Button to Work with an Angularjs Ui-Router State MAChine
Vue V-On:Click Does Not Work on Component
Creating a JavaScript Cookie on a Domain and Reading It Across Sub Domains
Auto-Refreshing Div with Jquery - Settimeout or Another Method
How to Get the First Element of an Array
Use Jquery to Scroll to the Bottom of a Div with Lots of Text