This is a series of posts that will illustrate the what, why and how of Node. I'll be sharing my learnings from a course on Advanced NodeJS by Samer Buna offered on PluralSight. Any code samples tagged or attached will be available at the following repo.
jscomplete / advanced-nodejs
For help, ask in #questions at slack.jscomplete.com
Node Modules
Modularity is a first-class concept in Node.
There are two core modules involved.
require - It is a global function, but each module gets its own
require
functionmodule - It is also available globally and is used to manage all the modules we require with
require
.
Requiring a module in node is very simple concept.
To execute a require
call, node goes through a following sequence of steps:
- Resolving: Find the absolute file path of the module required.
- Loading: Determined by the content of the file at the resolved path.
- Wrapping: Gives every module its
private scope
and what makesrequire
local to every module. - Evaluating: Eventually, VM does something to code.
- Caching: When we require again, we don't go over all steps above mentioned.
module
Object
Some interesting properties:
- id: String Identifier, Usually full path to the module except for the root module.
.
identifier is used for the root module. - filename: String path to the file containing the module. So when you require a module from
node_modules
, it loads the content of a file into memory. - path: Array of paths that will used to find a module that is required. It starts with
node_modules
folder in current directory and goes all the way to the root directory. If it can't find a module in any of those directories, it will throw aCannot find module 'module'
error. Core node modules are an exception. When you require a core node module, it resolves immediately.
Let's consider following example.
// ./index.js
console.log("In ./index.js");
require("find-me");
// ./node_modules/find-me.js
console.log("In find-me.js");
This will result in output
In ./index.js
In find-me.js
Note, not only it loads the file, it also evaluates when you require it.
If you only want to load a file, and not evaluate it, you can use require.resolve(package)
. It will also throw an error if it can't find the package in any of the paths. This is used to determine if an optional package is installed or not.
If you have package
installed in multiple folders present in path
property, it will only resolve the first one it finds.
Usually, packages are not files, but are folders, with multiple files. It will use index.js
or main
property in package.json
of the package. We can require any module, with a relative or absolute path.
Note module
object available in index.js
and in package index.js
are different. module
object in package index.js
will have a reference to root index.js
, and will be attached to it's parent
property.
Module Wrapper
index.js
exports.id = 1 // this is ok
exports = { id: 1 } // this is not ok
module.exports = { id: 1 } // this is ok, why?
var = 42; // local to this file
Only the things we export are available outside the module. How come variables we declare are magically scope. The answer is simple.
Before compiling a module, Node will wraps the module code in a function as follows.
> require('module').wrapper
(function (exports, require, module, __filename, __dirname) { ',
'\n});
This is how each module gets its own require
, exports
and module
object. These are just function arguments that are provided by wrapped function by node.
To see the values of these arguments you can just run the following code.
// index.js
console.log(arguments);
This will print all 5 arguments passed to the wrapper function.
The wrapping function's return value is the exports
object reference. Note, exports
is just a variable reference to module.exports
. So, if we modify the whole of exports
by assignment operator, we lose the module.exports
module.
So, there's nothing special about require
function. It takes the module name or path and returns the exports
object. So in test case scenarios, where one might need to overwrite/mock require
, we can do quite easily as follows.
require = () => {
return { mocked: true };
};
console.log(require("somepackage")) // { mocked: true }
Let's say we have this simple function that takes an integer and a string and prints something.
// printStars.js
const print = (stars, header) => {
console.log("*".repeat(stars));
console.log(header);
console.log("*".repeat(stars));
}
We want to run this function in two ways.
- Through the command line as follows
$ node printStars.js 5 hello
- Through
require
in another file as a module as follows.
// index.js
const printStars = require("printStars");
print(5, "hello");
To achieve this, we can leverage wrapping.
When it is run through Node CLI, require.main
will be the same as module
.
//printStars.js
const print = (stars, header) => {
console.log("*".repeat(stars));
console.log(header);
console.log("*".repeat(stars));
}
if (require.main == module) {
// When run as script
print(process.argv[2], process.argv[3]);
} else {
// being required by other file
// export the module
module.exports = print;
}
Caching
Imagine this case
// index.js
require("printFancy"); // Prints
console.log(require.cache); // Entry for `printFancy` module
require("printFancy"); // Nothing happens
//printFancy.js
console.log("Hello Fancy");
Note, when we require printFancy
first time, it will resolve, load, evaluate and cache the module.
However, when we require again, node has cached the module and so will repeat earlier steps again.
To circumvent this we can delete the cache on require.cache
object, before the second call, with delete require.cache['absModulePath']
and printFancy
will be called twice. But it is not the most efficient solution.
The easiest solution is to wrap the console log
in printFancy.js
in a function and export it.
// printFancy.js
module.exports = () => {
console.log("Hello Fancy");
};
Now every time you require the module, just execute the exports.
// index.js
require('printFancy')() // Prints
require('printFancy')() // Prints
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.