Working with Module Systems

1. Using CommonJS Modules

Example: CommonJS

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

// index.js
const { add } = require("./math");
console.log(add(2, 3));
AspectCommonJS
LoadingSynchronous
File ext.js (default) or .cjs
Top-level awaitNot supported
Globals availablerequire, module, exports, __dirname, __filename

2. Understanding module.exports vs exports

Reference Model

module.exports ─────► { } ◄───── exports
                       ▲
                       │ same object initially
                       │
exports.x = 1  →  modifies that object  →  visible
exports = {...} →  rebinds local var    →  IGNORED
      

3. Using Module Wrapper Function

Note: Each CJS module is wrapped: (function (exports, require, module, __filename, __dirname) { /* code */ }). That's why these "globals" exist.
Wrapper ParamPurpose
exportsShortcut to module.exports
requireModule loader
moduleReference to current module
__filename / __dirnamePath info

4. Understanding Module Resolution

StepLookup Order
1Core module (fs, http, …)
2Relative path (./, ../, /) with extensions .js, .json, .node, .mjs, .cjs
3node_modules walking up to root
4package.json "main" / "exports" field

5. Using ES Modules

Example: ESM

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

// index.mjs
import add, { add as addNamed } from "./math.mjs";
const fs = await import("node:fs/promises"); // dynamic
console.log(add(2, 3));
AspectESM
LoadingAsync, statically analyzable
File ext.mjs or .js with "type":"module"
Top-level awaitSupported v14.8+
CJS interopimport x from "cjs-pkg" works

6. Configuring Module Type

package.jsonEffect
"type": "module".js treated as ESM
"type": "commonjs" (default).js treated as CJS
Force per fileUse .mjs (ESM) or .cjs (CJS)

7. Using Dynamic Imports (import())

Example: Lazy load

async function loadHeavy() {
  const { parse } = await import("./heavy-parser.js");
  return parse(input);
}
PropertyDetail
ReturnsPromise resolving to module namespace
Works inBoth CJS and ESM
Use casesConditional load, code splitting, ESM-from-CJS

8. Using Import Assertions

Note: Replaced by import attributes using the with keyword v20.10+. Old assert syntax DEPRECATED.

Example: JSON import

import config from "./config.json" with { type: "json" };

9. Loading JSON Files

SystemSyntax
CJSconst cfg = require("./config.json");
ESMimport cfg from "./config.json" with { type: "json" };
AsyncJSON.parse(await fs.readFile(p, "utf8"))

10. Creating Conditional Exports

Example: package.json exports

{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "default": "./dist/index.cjs"
    },
    "./utils": "./dist/utils.js"
  }
}
ConditionMatches
importESM consumer
requireCJS consumer
node / browser / denoRuntime
typesTypeScript
defaultFallback

11. Creating Dual Packages

StrategyDetail
Two buildsOutput both .mjs and .cjs
Conditional exportsMap import/require
Avoid dual instance hazardDon't store state in module scope

12. Creating Module Aliases

ApproachHow
Subpath imports"imports": { "#utils/*": "./src/utils/*.js" } in package.json
tsconfig pathsFor TypeScript projects (compile-time only)
Node --import loaderCustom resolution hooks