Skip to Content
πŸŽ‰ Welcome to my notes πŸŽ‰
Node.js1. Module System

Module Systems in Node.js

Common JS Modules (Node.js)

The CommonJS module system is one of the most widely used module systems in JavaScript, particularly in Node.js. It is a way to organize and manage code in modular, reusable pieces, which makes it easier to maintain and scale applications. Modules are self-contained and have their own scope. They can expose functionality using module.exports and import functionality using require.

Key Features of CommonJS Modules

  1. Synchronous Loading: Modules are loaded synchronously (at runtime) in the order in which they are required. This means that the script waits until the module is fully loaded before moving on to the next step.

  2. Exports Object: Each file is treated as a module. Anything you want to expose from a module is added to the module.exports object. Other modules can import this exposed functionality.

  3. Single Instance: When a module is loaded, it is executed once and cached. Subsequent calls to require will return the same instance.

  4. File-Based Modules: Every .js file is treated as a separate module.

How CommonJS Works

Exporting Code: A module exports its functionality using module.exports. You can export functions, objects, classes, or values.

greet.js
function greet(name) { return `Hello, ${name}!`; } // Exporting the function module.exports = greet;

Alternatively, you can use the exports shorthand (a reference to module.exports) to export multiple values.

math.js
exports.add = (a, b) => a + b; exports.subtract = (a, b) => a - b;

Importing Code: To use a module in another file, you use the require function. It takes the path of the module (relative or absolute) and returns the exported functionality. Anything that is exported using module.exports or exports will be an argument to the require function.

app.js
const greet = require("./utils"); const math = require("./math"); console.log(greet("Alice")); // Hello, Alice! console.log(math.add(2, 3)); // 5

How Modules are Loaded

When require is called:

  1. Node.js resolves the module path.

    • If it’s a core module (e.g., fs, path), it loads it from Node’s core library.
    • If it’s a file path (e.g., ./module), Node resolves the path relative to the file requiring it.
    • If it’s a package (e.g., express), Node looks for it in the node_modules directory.
  2. Node wraps the code in a function to provide isolation and pass in special variables like exports, require, module, __filename, and __dirname.

  3. The module is executed, and its exports object is returned.

Limitations of CommonJS

  1. Synchronous Nature: Since require is synchronous, it can slow down applications if the required modules are large or involve heavy computations.

  2. Not Suitable for Browsers: CommonJS is designed for server-side environments like Node.js and doesn’t work in browsers without a bundler like Webpack or Browserify.

  3. Modern Alternative: ES Modules (ESM) are the new standard for JavaScript modules, offering a more flexible and dynamic approach to module loading. Node.js has also added support for ESM.

Understanding the module object

In Node.js and JavaScript, the module object is a built-in object that represents the current module. It is part of the CommonJS module system, which is the default module system in Node.js. Every JavaScript file in Node.js is treated as a separate module, and the module object provides information and functionality specific to that module.

console.log(module);

Key Features of the module Object

  1. Represents the Current Module: The module object provides details about the module file in which it is being accessed.

  2. module.exports: The module.exports property is the object that is exported from a module and made available to other modules when it is required using require(). By default, module.exports is an empty object . You can assign functions, objects, or values to module.exports to expose them.

  3. exports Alias: Node.js also provides a shorthand called exports, which is a reference to module.exports. However, you cannot reassign exports directly; you should modify it like an object.

// Correct usage exports.sayHi = function () { console.log("Hi!"); }; // Incorrect usage (breaks the reference to `module.exports`) exports = { sayHi: function () { console.log("Hi!"); }, };
  1. Accessing Module Metadata: The module object contains metadata about the current module. Some important properties include:

    • module.id: The identifier for the module (usually the absolute path to the file).
    • module.filename: The filename of the module.
    • module.loaded: A boolean flag indicating whether the module has been loaded.
    • module.parent: The module that required the current module.
    • module.children: An array of modules required by the current module.
    • module.paths: An array of paths that Node.js searches when looking for modules.
  2. Dependency Management: The module object is tightly integrated with Node.js’s module resolution system (require). It keeps track of which modules are loaded and caches them, ensuring that each module is loaded only once. Example of caching:

a.js
console.log("Module A is loaded"); module.exports = { value: 42 };
b.js
const a = require("./a"); // Logs "Module A is loaded" const aAgain = require("./a"); // Does not log anything, as it's cached console.log(a === aAgain); // true

Module Wrapper Function

In Node.js, every JavaScript file (module) is wrapped in a special Module Wrapper Function before it is executed. This wrapper function provides a private scope to each module, ensuring that variables, functions, and objects declared in one module do not interfere with others. This mechanism is part of the CommonJS module system in Node.js.

(function (exports, require, module, __filename, __dirname) { // Module code actually lives here });

This wrapper function is invoked immediately after wrapping, passing in specific arguments like exports, require, module, __filename, and __dirname.

Purpose of the Module Wrapper Function

  1. Encapsulation: The wrapper function encapsulates the module code, preventing variables and functions from leaking into the global scope. For example, if you define a variable const x = 10; in file1.js, it won’t be accessible in file2.js.

  2. Provides Useful Variables: The wrapper function provides useful variables like exports, require, module, __filename, and __dirname to the module. These variables help in defining and exporting module functionality.

  3. Supports Module Loading: The wrapper function is essential for loading and executing modules in Node.js. It ensures that each module is executed in its own isolated scope.

    • exports: An object that is used to export functionality from a module. Initially, it is an empty object {}.
    • require: A function to import functionality from other modules.
    • module: An object that represents the current module.
    • __filename: The absolute path to the current module file.
    • __dirname: The absolute path to the directory containing the current module file.

How the Wrapper Function Works

  1. When you write a module like this:

    console.log("This is my module"); const greeting = "Hello, world!"; module.exports = greeting;
  2. Node.js wraps it in a function like this:

    (function (exports, require, module, __filename, __dirname) { console.log("This is my module"); const greeting = "Hello, world!"; module.exports = greeting; });
  3. Then, Node.js calls the function, passing the appropriate arguments:

    (function (exports, require, module, __filename, __dirname) { console.log("This is my module"); const greeting = "Hello, world!"; module.exports = greeting; })(module.exports, require, module, __filename, __dirname);

ES6 Modules (ESM)

The ES Module System (ESM) is the standardized module system introduced in ECMAScript 2015 (ES6) for organizing and sharing JavaScript code across files. It replaces non-standardized solutions like CommonJS (Node.js) and AMD (browsers) with a unified syntax supported natively in modern browsers and Node.js.

  • Each JavaScript file is treated as a separate module.
  • Code is shared via export and import statements.
  • Imports/exports are resolved at parse time (enabling optimizations like tree-shaking ).
  • Modules are loaded asynchronously, allowing for better performance in web applications. (but executed synchronously)
  • Modules automatically enforce strict mode ('use strict').
  • Modules have their own scope, meaning variables and functions declared in a module are not accessible in the global scope or other modules unless explicitly exported.

To use ES6 modules in Node.js, you can either:

  1. Use the .mjs file extension for your module files.
  2. Set "type": "module" in your package.json file, allowing you to use the .js extension for ES6 modules.

Syntax

  • Exporting Code: You can export variables, functions, or classes using the export keyword. There are two types of exports: named exports and default exports.
math.js (named export methods)
export const PI = 3.14159; export function add(a, b) { return a + b; } // export as group const PI = 3.14159; function add(a, b) { return a + b; } export { PI, add }; // Named exports export { add as sum }; // Rename while exporting
math.js (default export methods)
export default function add(a, b) { return a + b; } // or function add(a, b) { return a + b; } export default add;
  • Importing Code: You can import code from other modules using the import statement. You can import named exports and default exports.
app.js (named import methods)
import { PI, add } from "./math.js"; console.log(PI); // 3.14159 console.log(add(2, 3)); // 5
app.js (rename named import)
// Renaming imports import { add as sum } from "./math.js"; console.log(sum(2, 3)); // 5
app.js (default import)
// Default import import add from "./math.js"; console.log(add(2, 3)); // 5
app.js (importing all exports)
import * as math from "./math.js"; console.log(math.PI); // 3.14159 console.log(math.add(2, 3)); // 5
app.js (importing side effects)
import "./math.js"; // No variables are imported, but the module is executed
app.js (dynamic import)
const modulePath = "./math.js"; import(modulePath).then((math) => { console.log(math.PI); // 3.14159 });

Accessing filename and dirname in ES6 Modules

In Node.js, CommonJS modules traditionally use __filename and __dirname to get the current file’s path and directory. However, ES Modules (ESM) do not have these variables by default. Instead, you must use the import.meta object and Node.js’s built-in modules to achieve the same functionality.

Accessing __filename in ESM: Use import.meta.url (which returns the file URL of the current module) and convert it to a file path with the url module.


Accessing __dirname in ESM: Use fileURLToPath to get the file path, then extract the directory with path.dirname.

import { fileURLToPath } from "url"; import { dirname } from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); console.log(__filename); // Absolute path to the current file console.log(__dirname); // Absolute path to the directory containing the current file

import.meta.url is a file URL string like file:///path/to/your/module.js. In browsers: It’s the URL from which the module was loaded (e.g., https://example.com/module.js)

Difference Between Common.js and ES6 Modules

FeatureCommonJS (CJS)ES Modules (ESM)
LoadingSynchronous (at runtime)Asynchronous (at parse time)
Syntaxrequire() and module.exportsimport and export
File Extension.js (or .cjs).js (or .mjs)
ScopeModule scopeBlock scope
HoistingNoYes
this ContextGlobal object (in non-strict mode)undefined (in strict mode)
__filename and __dirnameAvailableNot available (use import.meta)
Dynamic ImportsNot supportedSupported (using import())
Top-Level awaitNot supportedSupported
Tree ShakingNot supportedSupported (with bundlers)
InteroperabilityRequires esm package for ESMCan import CJS modules directly
BindingCopy by reference (mutable)Live bindings (read-only)
Circular DependenciesHandled with cachingHandled with live bindings

Module Types

  • Native modules: These are built-in modules provided by Node.js, such as fs, http, path, etc. They are part of the Node.js core library and do not require installation.

  • User modules: These are custom modules created by developers. They can be either CommonJS or ES modules, depending on the syntax used.

  • third-party modules: These are modules published on the npm registry. They can be installed using npm or yarn and can be either CommonJS or ES modules. Examples include express, axios, mongoose, etc.

Understanding package.json

The package.json file is a fundamental part of Node.js projects and JavaScript applications. It serves as the manifest file for your project, containing metadata and configuration information.

package.json
// The basic structure of package.json { "name": "my-project", "version": "1.0.0", "description": "A sample Node.js project", "main": "index.js", "scripts": { "start": "node app.js", "test": "jest", "dev": "nodemon app.js" }, "dependencies": { "express": "^4.17.1" }, "devDependencies": { "eslint": "^7.32.0" } }
  • name: Your package/project name (required)
  • version: The version of your package (required). Follows semantic versioning (semver) format (e.g., β€œ1.0.0”).
  • description: A brief description of your package (optional).
  • main: The entry point of your application (optional). It specifies the main file to be loaded when your package is required. Default is β€œindex.js”.
  • scripts: A set of scripts that can be run using npm run <script-name>. Common scripts include β€œstart”, β€œtest”, and β€œbuild”.
  • dependencies: A list of packages that your project depends on. These packages will be installed when you run npm install.
  • devDependencies: A list of packages that are only needed for development (e.g., testing frameworks, build tools). These packages are not required in production.
package.json (extended)
{ "engines": { "node": ">=12.0.0" }, "repository": { "type": "git", "url": "https://github.com/user/repo.git" }, "bugs": { "url": "https://github.com/user/repo/issues" }, "license": "MIT", "private": true, "keywords": ["node", "javascript", "example"], "author": "your name", "contributors": [ { "name": "contributor1 name", "email": "cont.1@mail.com" }, { "name": "contributor2 name", "email": "cont.2@mail.com" } ], "homepage": "https://example.com" }
  • engines: Specifies the version of Node.js that your package is compatible with (optional).
  • repository: Information about the source code repository (optional). It can include the type (e.g., β€œgit”) and URL of the repository.
  • bugs: Information about where to report issues (optional). It can include a URL or an email address.
  • license: The license under which your package is distributed (optional). Common licenses include β€œMIT”, β€œApache-2.0”, etc.
  • private: A boolean flag that, when set to true, prevents the package from being accidentally published to the npm registry. This is useful for private projects (optional).
  • keywords: An array of keywords that describe your package (optional). These keywords help users find your package in the npm registry.
  • author: The author of the package (optional). It can be a string or an object with name and email properties.
  • contributors: An array of contributors to the package (optional). Each contributor can be represented as a string or an object with name and email properties.
  • homepage: The URL of the package’s homepage (optional). This can be a website or documentation page related to the package.

How package.json is Used

  • Dependency Management: Running npm install reads this file to download all required packages and their dependencies.
  • Project Metadata: It provides essential information about the project, such as its name, version, and description.
  • Scripts: You can define custom scripts to automate tasks (e.g., testing, building) that can be run using npm run <script-name>.
  • Publishing: When you publish your package to the npm registry, the package.json file is included, allowing others to install and use your package easily.
  • Project Configuration: Tools like ESLint, Babel, Jest read their config from here, allowing you to customize their behavior.
Last updated on