DEV Community

Matias Torre
Matias Torre

Posted on

Understanding require function (Node.js)

require is a function that uses CommonJS which is the way that Node.js uses to load a module, when we do require(‘my-file.js’) it executes the function only once and is cached in memory, if we call the same require in other files, isn’t loaded again but uses the one you loaded previously, which is why they are considered singleton each file.

By default, node wraps the code in a function, this allows all the variables that are defined in the file not to colliding with others in other files, unless one exports some.

(function(exports, require, module, __filename, __dirname) {
 // my code
});
Enter fullscreen mode Exit fullscreen mode

This is similar to what was done before with Closure or IIFEE to prevent variables from colliding in js files on a web page.

A simple example to give context

Example index.html

<!DOCTYPE html>
<html lang=”en”>
 <head>
   <title>Simple example to check this problem</title>
 </head>
 <body>
   <script src=”script_1.js”></script>
   <script src=”script_2.js”></script>
 </body>
</html>
Enter fullscreen mode Exit fullscreen mode

File script_1.js

const my_variable = “hello in script 1”;
console.log(my_variable);
Enter fullscreen mode Exit fullscreen mode

File script_2.js

const my_variable = “hello in script 2”;
console.log(my_variable);
Enter fullscreen mode Exit fullscreen mode

In this case there are 2 scripts, which both define the same variable “my_variable”, you can see how there is an error, due to a collision of a variable already declared.

Uncaught SyntaxError: Identifier ‘my_variable’ has already been declared (at script_2.js:1:1)

How do you fix this before? similar to what the node wrapper does, creating a closure or an IIFE to encapsulate the variables so that there are no problems between the files.

File script_1.js

(() => {
 const my_variable = ‘hello in script 1’;
 console.log(my_variable);
})();
Enter fullscreen mode Exit fullscreen mode

File script_2.js

(() => {
 const my_variable = ‘hello in script 2’;
 console.log(my_variable);
})();
Enter fullscreen mode Exit fullscreen mode

With this change now in the console, we will see the 2 logs

  • hello in script 1
  • hello in script 2

Currently, as ES6 has existed for several years, you can do the same thing by adding only the {} between the code, this is because it is a block scope, the variables live in the scope of the curly braces “{}”

File script_1.js

{
 const my_variable = ‘hello in script 1’;
 console.log(my_variable);
}
Enter fullscreen mode Exit fullscreen mode

File script_2.js

{
 const my_variable = ‘hello in script 2’;
 console.log(my_variable);
}
Enter fullscreen mode Exit fullscreen mode

With this change, the same result is reached

  • hello in script 1
  • hello in script 2

require, being a function, allows you to perform dynamic requires, such as require(/${locale}/translations.json); where locale is a variable, it’s a good feature, however, it’s a disadvantage vs ESmodules, since being dynamic and sync, it is difficult to know in advance what is being requested, for frontend applications that have libraries with CommonJS such as final bundler, it does not allow three-shaking, which is to avoid loading code that is not used, dead code, for this reason the libs that use CommonJS are always heavier than those that use ES modules with import, since it allows you to use this feature, that promoted Rollup at the time.

Simple example to understand three-shaking

Suppose we have a util file, with 3 methods exposed

File utils.js

function track() {}
function trackingGTM() {}
function trackingPrivateSolution() {}

export { track, trackingGTM, trackingPrivateSolution };
Enter fullscreen mode Exit fullscreen mode

We have another my-view.js file that consumes this util.js, but it only uses one function only “track”, if it is configured correctly, it should add only the track util.

File my-view.js

import { track } from ‘./util’;

console.log(track());
Enter fullscreen mode Exit fullscreen mode

What happens when we require a module?

There are several steps

Resolving: It is responsible for resolving the path that one sends it, roughly speaking, if the path does not exist locally, it searches in “node_modules” and if not, it throws the module not found error, this can be validated using Node’s require.resolve function .js to validate if a module exists.

try {
 require.resolve(“fake_module”);
} catch (err) {
 if (err.code === “MODULE_NOT_FOUND”) {
   console.log(“The current module not exists”);
 }
}
Enter fullscreen mode Exit fullscreen mode

Loading: Responsible for loading the module.

Wrapping: The step of wrapping the function to avoid collisions between files.

Evaluating: Performs the execution of the file, which is why we see errors when loading a file, for example from a non-existent path or an undefined variable, or some method that we load to warm up an object in memory.

Caching: If everything went ok, it remains in memory to avoid executing all the returned flow by requiring the same file elsewhere.

Top comments (0)