100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Modules in JavaScript (import/export)

How ES modules split code into reusable files using import and export, and how they differ from CommonJS.

ES6+ FeaturesIntermediate11 min readJul 8, 2026
Analogies

1. Introduction

As applications grow, keeping all code in one file becomes unmanageable. Modules let you split code into separate files, each with its own private scope, and explicitly share functionality between them using export and import. ES6 introduced a native module system (ES Modules, or ESM) that is now supported by all modern browsers and Node.js. Before ESM, Node.js relied on CommonJS (require/module.exports), which is still common in older codebases and some npm packages.

🏏

Cricket analogy: Like splitting a huge tour handbook into separate chapters per department — batting, bowling, fielding — each with its own private notes, sharing only the official drills via an explicit export list, versus the old single shared master binder (CommonJS) still used by older touring squads.

2. Syntax

javascript
// math.js -- named exports
export const PI = 3.14159;
export function add(a, b) {
  return a + b;
}

// math.js -- default export (one per module)
export default function multiply(a, b) {
  return a * b;
}

// app.js -- importing
import multiply, { PI, add } from './math.js';
import * as MathUtils from './math.js';

// package.json needs: { "type": "module" }  (Node.js)
// or use a <script type="module"> tag in the browser

3. Explanation

A module can have any number of *named exports* (exported with export const, export function, export class, or a trailing export { a, b }) plus at most one *default export*. Named exports must be imported using the exact same name (optionally renamed with as), while a default export can be imported under any name you choose. import * as Name gathers every named export into a single namespace object.

🏏

Cricket analogy: Like a squad sheet with many named specialist roles (opener, keeper, spinner — named exports) imported by their exact title, plus one designated captain (default export) who can be called anything you like, and a full squad roster gathered under one name (import * as Squad).

ES modules are statically analyzed: import/export declarations must appear at the top level of a file (not inside an if or a function), and their bindings are resolved at parse time, before any code runs. This static structure is what allows bundlers like Webpack, Rollup, and esbuild to perform *tree-shaking* — removing exports that are never imported anywhere. CommonJS, by contrast, uses require() calls that execute at runtime and can appear anywhere (even conditionally), so it cannot be statically analyzed the same way.

🏏

Cricket analogy: Like a tour selector finalizing the entire squad list before the season starts, letting the board trim unused reserves in advance (tree-shaking), versus an old-school system where players could be called up mid-match on demand (runtime require) making advance trimming impossible.

Gotcha — ES modules vs CommonJS is not just a syntax difference: import/export are static and top-level only, evaluated asynchronously, and always run in strict mode. require/module.exports are dynamic, can run anywhere at runtime (including inside conditionals or functions), and execute synchronously. Mixing the two systems in the same project (e.g. require()-ing an ESM-only package) is a frequent source of 'Cannot use import statement outside a module' or 'require() of ES Module not supported' errors.

Because import is hoisted and static, this is invalid: if (condition) { import { x } from './x.js'; } — you must use the dynamic import('./x.js') function (which returns a Promise) if you need conditional or lazy loading.

4. Example

javascript
// geometry.js
export const SHAPE = 'circle';
export function area(radius) {
  return (Math.PI * radius * radius).toFixed(2);
}
export default function describe(radius) {
  return `${SHAPE} with area ${area(radius)}`;
}

// main.js
import describe, { SHAPE, area } from './geometry.js';

console.log(SHAPE);
console.log(area(2));
console.log(describe(2));

5. Output

text
circle
12.57
circle with area 12.57

6. Key Takeaways

  • export exposes named bindings; export default exposes one primary value per module.
  • import brings named exports in by exact name and the default export under any chosen name.
  • import/export declarations must be at the top level and are statically analyzed at parse time.
  • ES Modules run in strict mode automatically and are evaluated asynchronously.
  • CommonJS (require/module.exports) is dynamic, synchronous, and resolved at runtime — mixing it with ESM causes common errors.
  • Use the dynamic import() function (returns a Promise) for conditional or lazy-loaded modules.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#JavaScriptProgrammingStudyNotes#Programming#ModulesInJavaScriptImportExport#Modules#Import#Export#Syntax#StudyNotes#SkillVeris