How do ES modules (import/export) work in JavaScript?
ES modules let you split code into files using export to expose values and import to consume them. Named exports and default exports work differently.
JavaScript
Overview
ES Modules (ESM) are the official JavaScript module system (ES2015). Each file is its own module with its own scope. You explicitly `export` what you want to share and `import` what you need from other modules. This replaces older patterns like CommonJS (`require`/`module.exports`) in Node.js.
Key Points
- •Named export: `export const x = 1`. Import: `import { x } from "./file.js"`.
- •Default export: `export default fn`. Import: `import fn from "./file.js"` (name is your choice).
- •A module can have multiple named exports but only one default export.
- •Re-export: `export { x } from "./other.js"` (barrel pattern for index files).
- •In browsers, use `<script type="module">`. Node.js supports ESM in `.mjs` files or with `"type": "module"` in `package.json`.
- •Dynamic import: `const module = await import("./lazy.js")` - loads at runtime.
Examples
Named exports and imports
javascript// utils.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// main.js
import { PI, add } from './utils.js';
import { multiply as mul } from './utils.js'; // rename on import
console.log(add(2, 3)); // 5Default export
javascript// Button.jsx
export default function Button({ children, onClick }) {
return <button onClick={onClick}>{children}</button>;
}
// App.jsx - name the import whatever you like
import Button from './Button.jsx';
import MyButton from './Button.jsx'; // same thing, different nameBarrel file (re-exporting from an index)
javascript// components/index.js
export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
export { Modal } from './Modal.js';
// Elsewhere
import { Button, Input, Modal } from './components';Barrel files provide a clean public API for a folder of modules.
Dynamic import (code splitting)
javascript// Only load the chart library when needed
async function showChart(data) {
const { Chart } = await import('./chart.js');
new Chart(data).render();
}
// React lazy loading
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));Pro Tip
Prefer named exports over default exports in large codebases. Named exports are explicit (you see exactly what is imported), work better with tree-shaking, and make renaming across the codebase easier. Default exports make refactoring harder because the import name is unconstrained.