stackademic

The leading education platform for anyone with an interest in software development.

JavaScript Modules

Organize code with ES module imports, exports, and dynamic loading

Overview

ES modules let you split code across files with explicit import and export statements. Each module has its own scope, so top-level variables do not leak into the global namespace. Modules are loaded once, cached, and evaluated in a well-defined order, which makes dependencies predictable.

Syntax / Usage

A module can have any number of named exports and at most one default export. Imports are static and hoisted, so they resolve before the module body runs.

// math.js
export const PI = 3.14159
export function square(x) {
  return x * x
}
export default function add(a, b) {
  return a + b
}

// app.js
import add, { PI, square } from './math.js'
import * as math from './math.js'   // namespace import

console.log(add(2, 3)) // 5
console.log(square(PI)) // ~9.87
console.log(math.square(4)) // 16

Examples

Re-export from a barrel file to create a single entry point:

// shapes/index.js
export { Circle } from './circle.js'
export { Square } from './square.js'

// consumer.js
import { Circle, Square } from './shapes/index.js'

Dynamic import() returns a promise, enabling lazy loading and code splitting:

async function loadEditor() {
  const { createEditor } = await import('./editor.js')
  return createEditor()
}

button.addEventListener('click', () => {
  loadEditor().then((editor) => editor.focus())
})

Aliasing avoids naming collisions when combining modules:

import { format as formatDate } from './date.js'
import { format as formatMoney } from './currency.js'

console.log(formatDate(new Date()))
console.log(formatMoney(1999))

Common Mistakes

  • Forgetting the file extension in relative imports where the runtime requires it
  • Mixing CommonJS require and ES import in the same file without a build step
  • Expecting import statements to run conditionally—static imports always execute
  • Assuming a module runs each time it is imported; it is evaluated once and cached
  • Creating circular imports that leave bindings temporarily undefined

See Also

functions objects javascript-event-loop