webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
JavaScript · Lesson 16 of 17

Modules: import / export

Named vs default, dynamic import, tree shaking and barrel files.

Updated

What a module is

A module is a JS file with its own scope: the variables and functions in it are private until you explicitly export them. Another file uses them through an import.

Why: without modules, all the code would live in the same global space — name clashes, hidden dependencies, impossible to maintain. With modules, each file clearly declares what it offers and what it needs.

// math.js
const secret = 42                         // private — not exported
export const sum = (a, b) => a + b        // public

// app.js
import { sum } from './math.js'
sum(1, 2)

Kinds of export

Kind Export syntax Import syntax How many per file
named export const sum = ... import { sum } from './math' any number
default export default function Page() {} import Page from './page' (any name) one
re-export export { sum } from './math' — —
import multiply, { sum, avg as average } from './math.js'  // default + named + renaming
import * as math from './math.js'                           // everything, as an object

Named vs default — when

Named Default
The name on import fixed — the same everywhere you pick it — it can differ in every file
Refactoring, autocomplete, search better worse
Where it's required — page.tsx, layout.tsx, next.config.ts in Next

Recommendation: named exports for all your code; default only where the framework requires it.

Static vs dynamic import

import x from (static) await import() (dynamic)
When it loads at startup, before the code on demand, when you reach that line
Where only at the top of the file anywhere (in functions, conditions)
For almost everything heavy code that's rarely used: editors, charts, maps
button.onclick = async () => {
  const { Chart } = await import('./chart.js')   // downloaded only on click
}

In Next: next/dynamic does the same for components — exactly how the code editor in this app is loaded.

Tree shaking

Tree shaking = the bundler removes from the build the exports nobody imports. It works only with static import/export.

import { debounce } from 'lodash-es'   // ✓ only debounce ends up in the bundle
import _ from 'lodash'                 // ✗ the whole library ends up in the bundle

Barrel files

A barrel is an index.ts that only re-exports other files (export * from './a'). It seems convenient, but:

  • importing a single thing loads everything the barrel re-exports;
  • it slows down the dev server and tests;
  • it easily creates circular imports.

In our project we import directly from the file — see Feature-Sliced Design.

ESM vs CommonJS

ES Modules (ESM) CommonJS (CJS)
Syntax import / export require() / module.exports
Where browsers, Next, modern Node old Node, some libraries
Static (tree shaking) yes no

You write ESM. You'll only run into CJS in old configs.

Summary

  • A module = a file with its own scope; you export what's public.
  • Named exports by default; default only where it's required.
  • Dynamic import for heavy code; tree shaking only with static imports; avoid barrels.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.