Skip to main content

How the module system, CommonJS and require works

In this chapter of the web you are about to learn how the Node.js and CommonJS module system works and what the function does require.

CommonJS to the rescue

The JavaScript language did not have a native way of organizing code prior to the ES2015 standard. Node.js filled this gap with the CommonJS module format. In this article, we will learn how the Node.js module system works, how you can organize your modules, and what the new ES standard means for the future of Node.js.

What is the module system?

Modules are the fundamental building blocks of the code structure. The module system allows you to organize your code, hide information and only expose the public interface of a component using module.exports. Every time you use the call require; therefore you are loading another module.

The simplest example can be the following using CommonJS:

// add.js function add (a, b) {return a + b} module.exports = add

To use the add module we just created, we must ask for it.

// index.js const add = require ('./ add') console.log (add (4, 5)) // 9

Although it is not noticeable, add.js it's wrapped by Node.js like this:

(function (exports, require, module, __filename, __dirname) {function add (a, b) {return a + b} module.exports = add})

This is why you can access global type variables like require and module. It also ensures that your variables are within the scope of your module rather than the global object.

How does require work?

The module loading mechanism in Node.js caches modules on the first call to require. It means that every time you use: require ('awesome-module'), you will get the same instance of awesome-module, which guarantees that the modules are of type singleton and have the same state throughout your application.

You can load native modules and path references from your filesystem or installed modules. If the identifier passed to the function require It is not a native module, or a file reference (starting with /, ../, ./ or similar), so Node.js will look for installed modules. It will go through your file system looking for the referenced module in the folder node_modules. It starts from the main directory of your current module and then moves to the main directory until it finds the correct module, or until the root of the filesystem is reached.

Require under the hood - module.js

The module that deals with the loading of modules in the Node kernel is called module.js, and can be found in lib / module.js in the Node.js repository.

The most important functions to check here are the functions _loady and  _compile.

_Load module

This function checks if the module is already in the cache; if so, it returns the export object.

If the module is native, call the NativeModule.require () with the filename and returns the result.

Otherwise, it creates a new module for the file and caches it. It then loads the contents of the file before returning your export object.

_Compile module

The build function executes the contents of the file in the correct scope or sandbox, as well as exposes the helper variables like require, module or exports to the file.

How to organize the code?

In our applications, we need to find the correct balance of cohesion and coupling when creating modules. The desirable scenario is to achieve high cohesion and loose coupling of the modules.

Maybe you are interested >>>  Why doesn't mapping an array built in JavaScript work?

A module must focus only on a part of the functionality to have a high cohesion. Loose coupling means that modules should not have a global or shared state. They just need to be communicated by passing the parameters, and they are easily replaceable without touching your broader code base.

We usually export named or constant functions as follows:

'use strict' const CONNECTION_LIMIT = 0 function connect () {/ * ... * /} module.exports = {CONNECTION_LIMIT, connect}

What's in your node_modules?

Folder node_modules is where Node.js looks for modules. npm v2 and npm v3 they install their dependencies differently. You can find out which version of npm you are using by running:

npm --version

npm v2

npm v2 install all dependencies in a nested way, where main package dependencies are in your folder node_modules.

npm v3

npm v3 try to flatten these child dependencies and install them in the root folder node_modules. This means that you can't tell when looking at node_modules what packages are its explicit or implicit dependencies. It is also possible that the installation order changes the structure of your folder because npm v3 it is not deterministic in this way.

You can make sure your directory node_modules always be the same by installing packages only from a package.json. In this case, install your dependencies in alphabetical order, which also means that you will get the same folder tree. This is important because modules are cached using your path as the search key. Each package can have its own subfolder node_modules, which can result in multiple instances of the same package and the same module.

How to handle your modules?

There are two main ways to wire modules. One of them is using hard-coded dependencies, explicitly loading one module into another using a function call require. The other method is to use a dependency injection pattern, where we pass the components as a parameter, or we have a global container (known as IoC, or Inversion of Control container), which centralizes the administration of the modules.

We can allow Node.js to manage the lifecycle of modules by loading coded modules. Organize your packages intuitively, making it easy to understand and debug.

Dependency injection is rarely used in a Node.js environment, although it is a useful concept. The DI pattern can result in improved decoupling of modules. Instead of explicitly defining the dependencies for a module, they are received from the outside. Therefore, they can be easily replaced with modules that have the same interfaces.

Let's look at an example for DI modules using the factory pattern:

class Cart {constructor (options) {this.engine = options.engine} start () {this.engine.start ()}} function create (options) {return new Cart (options)} module.exports = create

The ES2015 module system

As we saw earlier, the CommonJS module system uses runtime evaluation of modules, wrapping them in a function before execution. It is not necessary to wrap the ES2015 modules as the links import / export they are created before evaluating the module. This incompatibility is the reason why there is currently no JavaScript runtime that supports ES modules. There was a lot of discussion on the subject and one proposal is in the DRAFT state, so we hope to have support in future versions of Node.

error: Attention: Protected content.