---
# System prepended metadata

title: 'ES6 Modules: Complete Guide'

---

# ES6 Modules: Complete Guide

## Introduction

ES6 modules (ECMAScript modules) are the official standard for JavaScript modules, providing a clean and efficient way to organize and share code between different parts of an application. They offer static analysis, tree-shaking capabilities, and better performance compared to previous module systems.

### Key Benefits

- **Static Analysis**: Import/export relationships are determined at compile time
- **Tree Shaking**: Unused code can be eliminated during bundling
- **Live Bindings**: Exported values maintain references to their original variables
- **Strict Mode**: All modules run in strict mode by default
- **Top-level Await**: Support for await at the module level

## Module Fundamentals

### What is a Module?

A module is a file containing JavaScript code that can export values and import values from other modules. Each module has its own scope and can only access what it explicitly imports.

### Module Scope

```javascript
// math.js - A simple module
const PI = 3.14159

function circleArea(radius) {
  return PI * radius * radius
}

function circleCircumference(radius) {
  return 2 * PI * radius
}

// Only exported values are accessible from outside
export { circleArea, circleCircumference }
export const PI_VALUE = PI
```

### Module Loading

```html
<!-- Loading a module in HTML -->
<script type="module" src="main.js"></script>
```

```javascript
// In Node.js with package.json
{
  "type": "module"
}
```

## Export Syntax

### Named Exports

Named exports allow you to export multiple values from a module:

```javascript
// Exporting individual declarations
export const name = 'square'
export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color
  ctx.fillRect(x, y, length, length)
  return { length, x, y, color }
}

// Exporting a list
const area = (length) => length * length
const perimeter = (length) => 4 * length

export { area, perimeter }

// Renaming exports
export { area as calculateArea, perimeter as calculatePerimeter }
```

### Default Exports

Each module can have only one default export:

```javascript
// Default export of a function
export default function randomSquare() {
  return Math.floor(Math.random() * 100);
}

// Default export of a class
export default class Square {
  constructor(length) {
    this.length = length;
  }

  area() {
    return this.length * this.length;
  }
}

// Default export of a value
const DEFAULT_CONFIG = {
  color: "blue",
  size: 100
};

export default DEFAULT_CONFIG;
```

### Mixed Exports

You can combine named and default exports:

```javascript
// math-utils.js
export const PI = 3.14159

export function circleArea(radius) {
  return PI * radius * radius
}

export default function calculateArea(shape, ...args) {
  switch (shape) {
    case 'circle':
      return circleArea(args[0])
    case 'square':
      return args[0] * args[0]
    default:
      return 0
  }
}
```

### Re-exporting

You can re-export values from other modules:

```javascript
// aggregator.js
export { Square } from './shapes/square.js'
export { Circle } from './shapes/circle.js'
export { Triangle } from './shapes/triangle.js'

// Re-exporting with renaming
export { Square as SquareShape } from './shapes/square.js'

// Re-exporting all exports
export * from './utils/math.js'
```

## Import Syntax

### Named Imports

```javascript
// Import specific named exports
import { name, draw, reportArea, reportPerimeter } from './modules/square.js'

// Import with renaming
import { name as squareName, draw as drawSquare } from './modules/square.js'

// Import multiple with mixed renaming
import {
  name as squareName,
  draw as drawSquare,
  reportArea as reportSquareArea
} from './modules/square.js'
```

### Default Imports

```javascript
// Import default export
import randomSquare from './modules/square.js'

// Import default with renaming
import { default as randomSquare } from './modules/square.js'

// Import default and named exports together
import randomSquare, { name, draw } from './modules/square.js'
```

### Namespace Imports

```javascript
// Import all exports as a namespace object
import * as Square from './modules/square.js'

// Usage
Square.draw(ctx, 50, 50, 100, 'blue')
Square.reportArea(100, reportList)
```

### Side-effect Imports

```javascript
// Import module for side effects only
import './modules/initialize.js'
```

## Module Types

### JavaScript Modules

Standard JavaScript modules with `.js` extension:

```javascript
// calculator.js
export function add(a, b) {
  return a + b
}

export function multiply(a, b) {
  return a * b
}
```

### JSON Modules

Import JSON files as modules:

```javascript
// config.json
{
  "apiUrl": "https://api.example.com",
  "timeout": 5000
}
```

```javascript
// main.js
import config from './config.json' with { type: 'json' }
console.log(config.apiUrl)
```

### CSS Modules

Import CSS files as modules:

```javascript
import styles from './styles.css' with { type: 'css' }
document.adoptedStyleSheets = [styles]
```

## Dynamic Imports

Dynamic imports allow you to load modules asynchronously at runtime:

### Basic Dynamic Import

```javascript
// Dynamic import returns a Promise
import('./modules/myModule.js').then((module) => {
  module.doSomething()
})

// Using async/await
async function loadModule() {
  const module = await import('./modules/myModule.js')
  module.doSomething()
}
```

### Conditional Loading

```javascript
// Load different modules based on conditions
async function loadModule() {
  let module

  if (typeof window === 'undefined') {
    // Server-side
    module = await import('./modules/server.js')
  } else {
    // Client-side
    module = await import('./modules/client.js')
  }

  return module
}
```

### Dynamic Module Paths

```javascript
// Load multiple modules dynamically
const modulePaths = [
  './modules/module1.js',
  './modules/module2.js',
  './modules/module3.js'
]

const modules = await Promise.all(modulePaths.map((path) => import(path)))

modules.forEach((module) => {
  if (module.initialize) {
    module.initialize()
  }
})
```

## Module Resolution

### Relative Paths

```javascript
// Relative to current file
import { helper } from './utils/helper.js'
import { config } from '../config/settings.js'
```

### Absolute URLs

```javascript
// HTTP URLs
import { library } from 'https://cdn.example.com/library.js'

// Data URLs
import data from 'data:text/javascript,export default 42;'
```

### Bare Specifiers

```javascript
// In Node.js or with import maps
import _ from 'lodash'
import { Component } from 'react'
```

## Import Maps

Import maps allow you to control how module specifiers are resolved:

### Basic Import Map

```html
<script type="importmap">
  {
    "imports": {
      "lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js",
      "react": "https://cdn.jsdelivr.net/npm/react@18/index.js"
    }
  }
</script>

<script type="module">
  import _ from 'lodash'
  import React from 'react'
</script>
```

### Scoped Import Maps

```html
<script type="importmap">
  {
    "imports": {
      "components/": "./src/components/",
      "utils/": "./src/utils/"
    }
  }
</script>

<script type="module">
  import { Button } from 'components/Button.js'
  import { helper } from 'utils/helper.js'
</script>
```

## Advanced Features

### Top-level Await

```javascript
// main.js
const data = await fetch('/api/data').then((r) => r.json())
const processedData = await processData(data)

export { processedData }
```

### Live Bindings

```javascript
// counter.js
export let count = 0

export function increment() {
  count++
}

// main.js
import { count, increment } from './counter.js'

console.log(count) // 0
increment()
console.log(count) // 1 - reflects the change
```

### Cyclic Dependencies

```javascript
// a.js
import { b } from './b.js'
export const a = 'a'

// b.js
import { a } from './a.js'
export const b = 'b'

// This works because of hoisting
```

### Import Attributes

```javascript
// Import with attributes
import data from './data.json' with { type: 'json' }
import styles from './styles.css' with { type: 'css' }
```

## Best Practices

### File Organization

```
src/
├── components/
│   ├── Button.js
│   └── Modal.js
├── utils/
│   ├── math.js
│   └── validation.js
├── services/
│   └── api.js
└── main.js
```

### Naming Conventions

```javascript
// Use descriptive names for exports
export const API_BASE_URL = 'https://api.example.com'
export function calculateTotalPrice(items) {
  /* ... */
}

// Use consistent naming patterns
export { Button as PrimaryButton } from './Button.js'
export { Button as SecondaryButton } from './Button.js'
```

### Error Handling

```javascript
// Handle import errors gracefully
try {
  const module = await import('./optional-module.js')
  module.initialize()
} catch (error) {
  console.warn('Optional module not available:', error.message)
}
```

## Common Patterns

### Barrel Exports

```javascript
// index.js - Barrel file
export { Button } from './Button.js'
export { Modal } from './Modal.js'
export { Input } from './Input.js'

// Usage
import { Button, Modal, Input } from './components'
```

### Factory Pattern

```javascript
// factory.js
export function createComponent(type, props) {
  switch (type) {
    case 'button':
      return new Button(props)
    case 'modal':
      return new Modal(props)
    default:
      throw new Error(`Unknown component type: ${type}`)
  }
}
```

### Plugin System

```javascript
// plugin-manager.js
const plugins = new Map()

export function registerPlugin(name, plugin) {
  plugins.set(name, plugin)
}

export function getPlugin(name) {
  return plugins.get(name)
}

// plugin.js
import { registerPlugin } from './plugin-manager.js'

registerPlugin('myPlugin', {
  initialize() {
    console.log('Plugin initialized')
  }
})
```

## Troubleshooting

### Common Errors

#### "Cannot use import statement outside a module"

```html
<!-- Solution: Add type="module" -->
<script type="module" src="main.js"></script>
```

#### "Module not found"

```javascript
// Ensure file extensions are included
import { helper } from './utils/helper.js' // ✅ Correct
import { helper } from './utils/helper' // ❌ Missing extension
```

#### "Circular dependency detected"

```javascript
// Break circular dependencies by restructuring
// or using dynamic imports
const { dependency } = await import('./dependency.js')
```

### Debugging Tips

```javascript
// Check if module loaded correctly
import * as module from './module.js'
console.log(module) // Inspect module object

// Use import.meta for debugging
console.log(import.meta.url) // Current module URL
console.log(import.meta.resolve('./utils.js')) // Resolved path
```

## Module Architecture Diagram

```mermaid
graph TB
    subgraph "Application Structure"
        A[main.js] --> B[components/]
        A --> C[utils/]
        A --> D[services/]

        B --> E[Button.js]
        B --> F[Modal.js]
        B --> G[Input.js]

        C --> H[math.js]
        C --> I[validation.js]

        D --> J[api.js]
        D --> K[auth.js]
    end

    subgraph "Module Types"
        L[JavaScript Modules]
        M[JSON Modules]
        N[CSS Modules]
    end

    subgraph "Import Methods"
        O[Static Import]
        P[Dynamic Import]
        Q[Namespace Import]
    end

    A -.-> L
    A -.-> M
    A -.-> N

    A -.-> O
    A -.-> P
    A -.-> Q

    style A fill:#ff4757,stroke:#2f3542,stroke-width:4px,color:#fff
    style B fill:#ff6348,stroke:#2f3542,stroke-width:3px,color:#fff
    style C fill:#ffa502,stroke:#2f3542,stroke-width:3px,color:#fff
    style D fill:#ff7675,stroke:#2f3542,stroke-width:3px,color:#fff
    style E fill:#fd79a8,stroke:#2f3542,stroke-width:2px,color:#fff
    style F fill:#fdcb6e,stroke:#2f3542,stroke-width:2px,color:#fff
    style G fill:#e17055,stroke:#2f3542,stroke-width:2px,color:#fff
    style H fill:#00b894,stroke:#2f3542,stroke-width:2px,color:#fff
    style I fill:#00cec9,stroke:#2f3542,stroke-width:2px,color:#fff
    style J fill:#6c5ce7,stroke:#2f3542,stroke-width:2px,color:#fff
    style K fill:#a29bfe,stroke:#2f3542,stroke-width:2px,color:#fff
    style L fill:#00b894,stroke:#2f3542,stroke-width:3px,color:#fff
    style M fill:#00cec9,stroke:#2f3542,stroke-width:3px,color:#fff
    style N fill:#55a3ff,stroke:#2f3542,stroke-width:3px,color:#fff
    style O fill:#fdcb6e,stroke:#2f3542,stroke-width:3px,color:#2f3542
    style P fill:#e84393,stroke:#2f3542,stroke-width:3px,color:#fff
    style Q fill:#74b9ff,stroke:#2f3542,stroke-width:3px,color:#fff
```

## Export/Import Flow Diagram

```mermaid
flowchart LR
    subgraph "Module A"
        A1[Export Declaration]
        A2[Named Export]
        A3[Default Export]
    end

    subgraph "Module B"
        B1[Import Statement]
        B2[Named Import]
        B3[Default Import]
        B4[Namespace Import]
    end

    A1 --> B1
    A2 --> B2
    A3 --> B3
    A1 --> B4
    A2 --> B4

    style A1 fill:#ff4757,stroke:#2f3542,stroke-width:3px,color:#fff
    style A2 fill:#00b894,stroke:#2f3542,stroke-width:3px,color:#fff
    style A3 fill:#6c5ce7,stroke:#2f3542,stroke-width:3px,color:#fff
    style B1 fill:#fdcb6e,stroke:#2f3542,stroke-width:3px,color:#2f3542
    style B2 fill:#e84393,stroke:#2f3542,stroke-width:3px,color:#fff
    style B3 fill:#00cec9,stroke:#2f3542,stroke-width:3px,color:#fff
    style B4 fill:#74b9ff,stroke:#2f3542,stroke-width:3px,color:#fff
```

## Dynamic Import Process

```mermaid
sequenceDiagram
    participant M as Main Module
    participant R as Runtime
    participant T as Target Module

    M->>R: import('./module.js')
    R->>R: Parse module specifier
    R->>T: Fetch module file
    T->>R: Return module code
    R->>R: Compile and instantiate
    R->>M: Return module object
    M->>M: Use module exports

    Note over M,T: Dynamic imports are asynchronous<br/>and return Promises

    style M fill:#ff4757,stroke:#2f3542,stroke-width:3px,color:#fff
    style R fill:#00b894,stroke:#2f3542,stroke-width:3px,color:#fff
    style T fill:#6c5ce7,stroke:#2f3542,stroke-width:3px,color:#fff
```

## Module Resolution Strategy

```mermaid
graph TD
    A[Module Specifier] --> B{Type?}

    B -->|Relative| C[./module.js]
    B -->|Absolute| D[https://example.com/module.js]
    B -->|Bare| E[lodash]

    C --> F[Resolve relative to current file]
    D --> G[Use as-is]
    E --> H{Import Map?}

    H -->|Yes| I[Look up in import map]
    H -->|No| J[Node.js resolution]

    F --> K[Load Module]
    G --> K
    I --> K
    J --> K

    K --> L[Parse and Compile]
    L --> M[Execute Module]
    M --> N[Return Exports]

    style A fill:#ff4757,stroke:#2f3542,stroke-width:4px,color:#fff
    style B fill:#fdcb6e,stroke:#2f3542,stroke-width:3px,color:#2f3542
    style C fill:#e17055,stroke:#2f3542,stroke-width:2px,color:#fff
    style D fill:#fd79a8,stroke:#2f3542,stroke-width:2px,color:#fff
    style E fill:#a29bfe,stroke:#2f3542,stroke-width:2px,color:#fff
    style F fill:#00b894,stroke:#2f3542,stroke-width:2px,color:#fff
    style G fill:#00cec9,stroke:#2f3542,stroke-width:2px,color:#fff
    style H fill:#fdcb6e,stroke:#2f3542,stroke-width:2px,color:#2f3542
    style I fill:#e84393,stroke:#2f3542,stroke-width:2px,color:#fff
    style J fill:#74b9ff,stroke:#2f3542,stroke-width:2px,color:#fff
    style K fill:#00b894,stroke:#2f3542,stroke-width:3px,color:#fff
    style L fill:#6c5ce7,stroke:#2f3542,stroke-width:2px,color:#fff
    style M fill:#55a3ff,stroke:#2f3542,stroke-width:2px,color:#fff
    style N fill:#00cec9,stroke:#2f3542,stroke-width:3px,color:#fff
```

## Conclusion

ES6 modules provide a powerful and standardized way to organize JavaScript code. They offer static analysis, tree-shaking capabilities, and better performance compared to previous module systems. Understanding the various import/export syntaxes, dynamic imports, and module resolution strategies will help you build more maintainable and efficient JavaScript applications.

Key takeaways:

- Use named exports for multiple values, default exports for single primary values
- Leverage dynamic imports for code splitting and conditional loading
- Organize modules logically with clear naming conventions
- Use import maps for better module resolution control
- Handle errors gracefully when working with dynamic imports
- Take advantage of live bindings for reactive programming patterns

By following these patterns and best practices, you can create modular, maintainable, and performant JavaScript applications using ES6 modules.
