What Are ES Modules? Import and Export Explained for AI-Assisted Developers

Every AI-generated project splits code across files using import and export. Here's what that means and what to do when it breaks.

TL;DR

ES Modules (ESM) are the official JavaScript standard for sharing code across files using import and export. Every modern framework — React, Vue, Next.js, Svelte — uses them by default. Understanding the difference between named and default exports, and knowing how to fix "Cannot use import statement outside a module," will save you hours of confusion. Tested with Node.js 22, Vite 6, React 19 — March 2026.

Why AI Coders Need to Know This

The moment your project grows beyond one file, you need modules. AI will create 10, 20, 50 files and connect them with import/export. When a module error appears, it stops everything:

  • "Cannot use import statement outside a module"
  • "SyntaxError: Unexpected token 'export'"
  • "Module not found: Error: Can't resolve './utils'"

These errors are almost never about the logic in your code. They're about how the module system is configured. Knowing the basics of ES Modules turns a 2-hour debugging session into a 2-minute fix.

Real Scenario

Your prompt to the AI

"Create a utility file with helper functions for formatting dates and currency, then import and use them in my main app component."

This produces multiple files connected via imports — the most common module pattern in any real project.

What AI Generated

// ---- utils/formatters.js ----

// Named export: a specific function with a name
export function formatDate(dateString) {
  const date = new Date(dateString);
  return date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long', 
    day: 'numeric'
  });
}

// Named export: another function from the same file
export function formatCurrency(amount, currency = 'USD') {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency
  }).format(amount);
}

// Default export: the "main thing" this file provides
export default function formatPercent(value) {
  return `${(value * 100).toFixed(1)}%`;
}


// ---- App.js ----

// Import named exports — must use their exact names in { }
import { formatDate, formatCurrency } from './utils/formatters';

// Import default export — name it whatever you want
import formatPercent from './utils/formatters';

// Or import everything in one line:
import formatPercent, { formatDate, formatCurrency } from './utils/formatters';

function App() {
  const price = formatCurrency(1299.99);       // "$1,299.99"
  const date = formatDate('2026-03-17');        // "March 17, 2026"
  const growth = formatPercent(0.127);          // "12.7%"

  return (
    <div>
      <p>Price: {price}</p>
      <p>Date: {date}</p>
      <p>Growth: {growth}</p>
    </div>
  );
}

export default App;

Understanding Each Part

Named exports: export function / export const

A named export makes a specific binding available for other files to import. You can have as many named exports per file as you want. When importing, the names must match exactly and go inside curly braces: import { formatDate } from './utils/formatters'.

Default export: export default

A file can have one default export — its primary value. When importing a default, you don't use curly braces and you can choose any name: import whatever from './myFile'. React components are almost always default exports.

The import path

./utils/formatters is a relative path — starting from the current file's location. The ./ means "same folder." ../ goes up one folder. Paths without ./ or ../ (like import React from 'react') refer to packages in node_modules.

Named vs. default: when to use which

  • Default export: One main thing per file — a React component, a class, a configuration object.
  • Named exports: Multiple utilities, helpers, or constants from the same file.
  • Barrel files: AI often creates an index.js file that re-exports from multiple files so you can import from a single path: import { formatDate, formatCurrency } from './utils'.

CommonJS vs. ESM

There are two module systems in JavaScript. ES Modules (the modern standard) use import/export. CommonJS (Node.js's original system) uses require()/module.exports. They don't mix in the same file. Most AI-generated code uses ESM, but some older packages and Node.js scripts still use CommonJS.

The type: "module" in package.json

Node.js defaults to CommonJS. To use import/export in Node.js without a bundler, you need to add "type": "module" to your package.json, or use the .mjs file extension. React projects created with Vite or Create React App configure this automatically.

What AI Gets Wrong

1. Mixing default and named import syntax

A classic mistake: import { App } from './App' when App is a default export. Default exports don't use curly braces. AI sometimes generates the wrong import style, especially when converting between patterns.

2. Wrong relative paths

AI may generate import utils from 'utils/formatters' (no ./) when the file is local, not in node_modules. Or it adds an extra ../ when the path goes the wrong direction. The error "Module not found" almost always means a path problem, not a code problem.

3. Adding file extensions inconsistently

In Node.js ESM, you must include the file extension in import paths: import { x } from './utils.js'. In bundled environments (Vite, webpack), extensions are optional. AI knows both conventions and sometimes picks the wrong one for your environment.

4. Re-exporting without checking the source

AI often creates barrel files that re-export from multiple sources. If any of those source files has an error, the entire barrel import fails — with a confusing error pointing to the barrel file, not the actual broken file.

How to Debug with AI

"Cannot use import statement outside a module"

This is the most common module error in Node.js. Debugging prompt: "I'm getting 'Cannot use import statement outside a module' in a Node.js script. My package.json does not have type: module. Should I add it, or convert my imports to require()?"

"Module not found: Can't resolve './filename'"

Path problem. Debugging prompt: "I'm getting Module not found for './utils/formatters'. My file is at src/components/App.js and formatters.js is at src/utils/formatters.js. What is the correct relative import path from App.js?"

Tool-specific tips

  • Cursor: Ask it to show you the directory structure and confirm the import paths are correct relative to each file's location.
  • Claude Code: Paste the error + both file paths. Ask: "Is this a named export or default export, and am I importing it correctly?"
  • Windsurf: Good at converting entire projects from CommonJS to ESM or vice versa when you change the module strategy.

What to Learn Next

Key Insight

Named exports need curly braces. Default exports don't. When you see a module error, the first thing to check is: does the import style (with or without {}) match the export style (export const vs export default)?

FAQ

ES Modules (ESM) are the official JavaScript standard for splitting code across multiple files using import and export statements. They were introduced in ES2015 (ES6) and are now the default module system in modern frameworks like React, Vue, Next.js, and Vite.

A named export uses export const or export function and must be imported with its exact name in curly braces: import { myFunction } from './file'. A default export uses export default and can be imported with any name: import anything from './file'. A file can have many named exports but only one default export.

This error means Node.js is treating the file as CommonJS but found an ESM import statement. Fix: add "type": "module" to your package.json, rename the file to .mjs, or configure your bundler. This does not affect React/Vite projects which configure ESM automatically.

ES Modules use import/export syntax and are the browser-native standard. CommonJS uses require() and module.exports — Node.js's original module system. Modern projects use ESM. CommonJS is still common in older Node.js code and some npm packages.

No. You cannot mix import statements and require() calls in the same file. They are different module systems. Most modern projects use import/export (ESM). If you need a CommonJS package in an ESM project, use a dynamic import: const pkg = await import('package-name').