Modules

Course 3 · Ch 1
Modules: import/export, and Bundlers Conceptually
Splitting code across multiple files properly, instead of one giant script — and what build tools actually do with that split

Every example across all 17 previous chapters lived in a single block of code. Real projects split logic across many files — a module is simply a single JS file that explicitly declares what it shares with other files (export) and what it borrows from them (import). This is the standard, native mechanism JavaScript now has for organizing larger codebases.

Named Exports

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

export in front of a function or variable declaration marks it as available to other files. A single file can have any number of named exports — there's no limit, and they can be functions, constants, classes (Intermediate Chapter 3), anything.

Importing Named Exports

// main.js import { add, PI } from './math.js'; console.log(add(2, 3)); // 5 console.log(PI); // 3.14159

import { add, PI } from './math.js' uses object-destructuring-like syntax (Intermediate Chapter 1) to pull in specific named exports — only what's listed inside the braces becomes available in this file, by exactly those names.

Default Exports

// logger.js export default function log(message) { console.log(`[LOG] ${message}`); } // main.js import log from './logger.js'; // no braces — and the name can be anything log("App started");

A file can have at most one export default — used for a file's single "main" thing, conventionally one per file (one component, one class, one primary function). Importing a default export uses no braces, and the imported name doesn't need to match what it was called in the original file — import customName from './logger.js' would work identically.

A file can mix named and default exports
export default function log(...) alongside separate export const LOG_LEVELS = [...] in the same file is entirely valid — import log, { LOG_LEVELS } from './logger.js' imports both together in one statement.

The <script type="module"> Requirement

<script type="module" src="main.js"></script>

Browsers only allow import/export inside a script explicitly marked type="module" — a plain <script src="main.js"> (Fundamentals Chapter 1) throws a syntax error the moment it hits an import statement. Module scripts also behave slightly differently: they're deferred automatically (run after the HTML is parsed) and run in strict mode by default.

ES modules require a real server, not file:// directly
Opening an HTML file with type="module" scripts directly from disk (file:///...) typically fails with a CORS error in most browsers — modules need to be served over http:// or https://, even just from a simple local development server, to load correctly.

Why Bundlers Exist

Native import/export works directly in modern browsers — no extra tooling needed for small projects. But real-world apps often have dozens or hundreds of module files, each requiring its own network request, plus a desire to support older browsers, minify code for smaller downloads, and use newer syntax that needs converting. A bundler (Webpack, Vite, esbuild, Rollup) solves this: it reads the entire web of import/export statements across every file and combines them into one (or a few) optimized output files.

// Conceptually, a bundler turns this: // main.js -> imports math.js, logger.js // math.js -> exports add, PI // logger.js -> exports default log // ...into ONE combined, minified file like: const n=(e,t)=>e+t,o=3.14159;function r(e){console.log(`[LOG] ${e}`)}r("App started");

The shortened variable names, the removal of whitespace, and merging everything into one file are all things a bundler does automatically — none of it changes the actual behaviour, only the file size and number of network requests needed to load the app. This course doesn't configure a real bundler, but understanding why one exists is essential before reaching for React, Vue, or any modern framework's tooling.

SyntaxHow many per file?Import syntax
export function/const/classAny numberimport { name } from '...'
export default ...At most oneimport anyName from '...'

Coding Challenges

Challenge 1

Write the contents of a file shapes.js with two named exports: a function circleArea(radius) and a constant TAX_RATE = 0.2. Then write the contents of main.js that imports both and logs circleArea(5) and TAX_RATE.

📄 View solution
Challenge 2

Write the contents of a file validator.js with a default export — a function isValidEmail(email) doing a simple check for the presence of "@" and ".". Then write main.js importing it under the name checkEmail (a different name than it was defined with) and testing it with two example strings.

📄 View solution
Challenge 3

Write the contents of a file stringUtils.js that has BOTH a default export (a function capitalize(str)) and a named export (a function reverse(str)). Write main.js that imports both together in a single import statement and tests each one.

📄 View solution

Chapter 1 Quick Reference

  • export function/const/class name — a named export; any number per file
  • export default ... — the default export; at most one per file
  • import { name } from './file.js' — imports named exports by exact name
  • import anyName from './file.js' — imports the default export; name is up to you
  • import def, { named } from './file.js' — imports both kinds together
  • <script type="module"> — required in the browser for import/export to work at all
  • Module scripts need a real server — file:// alone typically fails with a CORS error
  • Bundlers (Webpack, Vite, esbuild) combine and optimize many module files into fewer, smaller ones
  • Next chapter: generators and iterators