Node.js Module Resolution Algorithm
Every time we import anything in Node.js, there is an algorithm Node.JS runs to the detriment where this module exists in our file system.
When talking about modules, I'm talking about files, directories, or anything outside the current running file.
Overview
We will talk about all the ways Node.js checks to find where is the module that you imported.
- core modules
- relative files/folders
- imports & exports alias
- node_modules
Core modules
First, Node.js checks if your import is a core Node.js module, anything like, os
, node:asserts
, fs
, etc.
Relative modules
Secondly, Node.js checks if our imports start with /
, ./
, or ../
. If it does, Node.js checks this path for files and directories and treats the import as a path.
If the path starts, /
Node.js will look for the module from the root of our file system.
If the path begins with ./
of ../
Node.js will search for the module relative to the current file's location.
Let's say we are trying to import this module:
Node.js will run the following checks
import note that if one of these checks passes, the Node.js will not continue to the next steps
- if
foo
exists, execute by file extension.
in this case, Node.js will throw anERR_UNKNOWN_FILE_EXTENSION
becausefoo
doesn't have any file extension. - if
foo.js
exists, executes as a javascript module - if
foo.json
exists, parses json, and returns JS object - if
foo.node
exists, runs as a binary add-on. - if
foo/package.json
Node.JS will search for themain
field. If present, Node.js will run steps 1–4 and 6–8 for the given path inmain
- if
foo/index.js
exists, executes as a javascript module - if
foo/index.json
exists, parse json, and rerun object - if
foo/index.node
exists, runs as a binary add-on.
If everything fails, Nodejs will throw ERR_MODULE_NOT_FOUND
Alias Modules
Thirdly Node.js checks if our imports start with #
. This import is like an alias to other files in our project, like the Webpack alias or TypeScript path. You can read more in this article of mine.
In general, you can define an alias that starts with #
to a given path in your project
and now we can import bar.js anywhere in the project like this:
When Node.js resolve an import like this, it starts to go through all the parent folders of the current file and process the first package.json it finds
Given this directory tree
If we will run node workspace/app-1/index.js
Node.js will process only the package.json in workspace/app-1/pacakge.json
.
But if we will run node workspace/app-2/index.js
Node.js will check the package.json workspace/package.json
Let's say we are trying to import this module:
Node.js will run the following checks
import note. If one of these checks fails Node.js, it will not continue to the next steps
- Scans for the closes package.json
- Search for the
imports
field - Return resolved path that is configured. Throw an error that fails to resolve.
Read more about import config here
Self Import Modules
Fourthly, Node.js checks if the imports start with the name of our current scope.
Given this package.json
If we try to import my-site/foo
We will run bar.js
.
The way that Node.js resolve this config is very similar to the resolution algorithm of the imports field. We can still import foo
form anywhere in the project, but instead of starting with #
the import must start this with our package name.
Node Modules
And Finally, if none of our previous checks passed, Node.js will try to resolve the import from the node_modules
directory.
Given this workspace,
Node.js will check the flowing paths to see if they contain node_moduels
directory before throwing an error
In general, Node.js will check every parent folder in the tree until they get to the root, meaning every package installed on the roots node_modules
will be available to all the Node.js programs in your machine.
Let's say we import this module.
Node.js will run the following checks for each parent directory in the tree until the root.
import note that if one of these checks passes, the Node.js will not continue to the next steps
- if
node_modules/foo/package.json
exists and has an exports field, resolve the exports field, like in the Self Import Modules section but with foo module instead of our workspace - if
node_modules/foo
exists run the same file/directory resolving algorithm as in the relative modules section
If everything fails, and Nodejs fails and can't find any modules, it will throw ERR_MODULE_NOT_FOUND
.
Given this workspace
Node.js will check all of these paths when trying to find our module.
conclusion
it's important to know how Node.js deals with external libraries to help us identify import issues or a miss match of external modules. This information can help us reduce debugging time and make us better developers overall.