[Monorepo] Node JS module resolution

2024-01-23 hit count image

I will explain how NodeJS loads modules as basic knowledge to understand Monorepo.

Outline

In this blog post, I will explain how NodeJS loads modules as basic knowledge to understand Monorepo.

Blog series

This blog is a series. Please check other blog posts through the following links.

Module

In JavaScript, modules are features used to organize and separate code into reusable units. Modules make it easier to maintain and extend code. A module system that was originally not in JavaScript was introduced from ECMAScript 2015(ES6).

Modules encapsulate code at the file level and allow you to import the required functionality from other files. Modules prevent conflicts in the global scope and allow you to clearly manage code dependencies.

To use modules in JavaScript, you can use the following keywords.

  • import: Imports features exported from other modules into the current module.
  • export: Specifies functions, variables, classes, etc. to be exported from the current module to the outside.

You can create modules in JavaScript as follows.

// math.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

Modules created in this way can be used as follows.

// main.js
import { add, subtract } from './math';

console.log(add(5, 3)); // 8
console.log(subtract(7, 2)); // 5

Loading modules

There are two ways to load modules in JavaScript.

  • Using File path
  • Using Package name

File path

When adding a module using File path, you can use relative path or absolute path as follows.

// Relative path
import { add, subtract } from '../some/file/path/math';

// Absolute path
import { add, subtract } from '/src/math';

Package name

Modules in the node_modules folder can be loaded using Package name.

// react module exists in node_modules
import React from 'react';

Module resolution priority

When NodeJS loads a module, first it checks if the module exists in the same folder, and then checks if the module exists in the node_modules in the same folder. If it does not exist in the same folder, it checks the node_modules folder int the parent folder, and if it does not exist in the parent folder, it checks the node_modules in the parnet folder of the parent folder.

Example

To check how NodeJS loads modules, let’s create the following folder structure.

.
└── src/
    ├── module-a/
    │   ├── index.js
    │   └── package.json
    └── module-b/
        ├── index.js
        └── package.json

The package.json of module-a is as follows.

// src/module-a/package.json
{
  "name": "module-a",
  "version": "1.0.0",
  "main": "index.js"
}

The index.js of module-b is as follows.

// src/module-b/package.json
{
  "name": "module-b",
  "version": "1.0.0",
  "main": "index.js"
}

The index.js of module-a is as follows.

// src/module-b/index.js
console.log('module-b');

Lastly, the index.js of module-a is as follows.

// src/module-a/index.js
console.log('module-a');

require('module-b');

After configuring the files like this, run the following command to check if the module is loaded well.

node src/module-a/index.js

Then, you can see the following error.

module-a
node:internal/modules/cjs/loader:1073
  throw err;
  ^

Error: Cannot find module 'module-b'
Require stack:
- /Users/deku/temp/temp/src/module-a/index.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1070:15)
    at Module._load (node:internal/modules/cjs/loader:923:27)
    at Module.require (node:internal/modules/cjs/loader:1137:19)
    at require (node:internal/modules/helpers:121:18)
    at Object.<anonymous> (/Users/deku/temp/temp/src/module-a/index.js:3:1)
    at Module._compile (node:internal/modules/cjs/loader:1255:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1309:10)
    at Module.load (node:internal/modules/cjs/loader:1113:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/Users/deku/temp/temp/src/module-a/index.js' ]
}

Next, change the folder name src to node_modules and run it again.

node node_modules/module-a/index.js

Then, you can see that it runs without any problems as follows.

module-a
module-b

In the case that the folder name is src, when module-a loads module-b by require('module-b');, it can not load the file path, so it looks for module-b in the node_modules of the same folder. Since node_modules does not exist in the current folder, it checks the node_modules in the parent folder. Since there is no node_modules in the parent folder, the MODULE_NOT_FOUND error occurs.

In the case that the folder name is node_modules, when module-a loads module-b by require('module-b');, it can not load the file path, so it looks for module-b in the node_modules of the same folder. Since node_modules does not exist in the current folder, it checks the node_modules in the parent folder. In the parent folder, there is a node_modules folder that we’ve changed the name, and the module-b exists in the node_modules folder, so it can be loaded without any problems.

Completed

Done! we’ve senn how NodeJS loads modules. How NodeJS loads modules is knowledge that helps to configure a project as a Monorepo, so I introduced it in this blog post. I hope you take this opportunity to understand how NodeJS loads modules again.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.

Posts