Hello! Welcome to your next lesson on modern JavaScript.
Introduction
In our last few lessons, we've been adding powerful tools like map and filter to your JavaScript toolkit. As you write more code to handle data and build interactivity, you'll notice your JavaScript files can get long and difficult to manage. Just as a design system organizes UI components into a reusable library, a modular code structure organizes functions, variables, and classes into reusable files.
This lesson addresses the final learning outcome in the "Forms and Modern JavaScript" module: Organize code into ES6 modules using import and export statements.
We will cover:
- The core concept of modules and why they are essential for modern web development.
- How to
exportcode (like functions or variables) from one file. - How to
importthat code into another file where you need it. - The two main types of exports: named and default.
- The necessary HTML setup to make modules work in the browser.
By the end of this lesson, you'll be able to break down a large script into smaller, more manageable, and reusable pieces.
1. Why Do We Need Modules?
Before ES6, all JavaScript files loaded on a page shared the same global "space" (the global scope). This meant a variable declared in one file could accidentally overwrite a variable with the same name from another file, leading to bugs that are hard to track down. Large projects became a tangled mess.
Modules solve this problem. Each module (which is just a JavaScript file) has its own private scope. Nothing inside a module is available to the outside world unless you explicitly export it. Other files can then explicitly import what they need.
This approach, often called separation of concerns, has several benefits that will feel familiar from your design work:
- Organization: Code is easier to find and understand when it's grouped by functionality.
- Reusability: You can write a useful function once (e.g., a function to format dates) and import it anywhere you need it, just like reusing a button component.
- Maintainability: If you need to fix a bug or update a function, you only need to do it in one place.
- No Naming Collisions: Since each file is its own world, you don't have to worry about accidentally overwriting variables.
To get a solid grasp of this concept, let's start with a short article.
Implementing Modules using ES6 Syntax
This article from Codecademy, 'Implementing Modules using ES6 Syntax', does an excellent job of explaining what modules are and the problems they solve.
Please read the first section, 'What are Modules?'. Focus on the four benefits listed for isolating code into modules.
2. The Core Syntax: export and import
Now for the "how." The keywords export and import are the foundation of JavaScript modules. There are two primary ways to export code from a file: named exports and a default export.
A single file can have many named exports but only one default export.
Let's watch a video that walks through the syntax for both, along with the crucial HTML setup required to make it all work.
Javascript Modules | Export Import Syntax for ES6 Modules
This video from Dave Gray, 'Javascript Modules', provides a clear, step-by-step demonstration of the import/export syntax and the necessary project setup.
Please watch the following segments: Setup (00:00 - 03:42): Pay close attention to why a local server is needed and the importance of adding type="module" to your script tag in the HTML. Exporting Functions (03:42 - 06:29): This part shows how to use export default for a primary export and export for named exports. Importing Functions (06:29 - 10:00): This demonstrates how to import default and named exports, and how to rename an import using the as keyword to avoid naming conflicts.
Let's summarize the key syntax patterns you just saw.
Named Exports
Use named exports when a module offers several distinct pieces of functionality.
Exporting File (utils.js)
You can export as you declare:
// utils.js
export const PI = 3.14159;
export const capitalize = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
Or you can export them all at the end of the file:
// utils.js
const PI = 3.14159;
const capitalize = (str) => {
/* ... */
};
export { PI, capitalize };
Importing File (main.js)
You must use curly braces {} and the exact names:
// main.js
import { PI, capitalize } from './utils.js';
console.log(capitalize('hello')); // "Hello"
console.log(`The value of PI is ${PI}`);
If you have a name conflict, you can rename on import:
import { capitalize as makeFirstLetterBig } from './utils.js';
console.log(makeFirstLetterBig('world')); // "World"
Default Export
Use a default export for the single, main thing a module provides, like a class or the primary function.
Exporting File (user.js)
// user.js
class User {
constructor(name) {
this.name = name;
}
}
export default User;
Importing File (main.js)
You import without curly braces and can give it any name you like.
// main.js
import Person from './user.js'; // We can name it 'Person' even though it was exported as 'User'
const me = new Person('Alex');
You can also combine them:
// main.js
// Importing the default export `User` as `Person`, and the named export `capitalize`
import Person, { capitalize } from './utils.js';
Here is a diagram that visually summarizes this flow:

3. Practice Exercise
Let's apply what you've learned. Below is a single HTML file with a script that handles everything. Your task is to refactor this code into a modular structure.
Your Goal:
- Create three files:
index.html,main.js, andutils.js. - Move the HTML structure into
index.html. - Move the two functions,
createUserCardandappendCard, intoutils.js. Export both of them using named exports. - Move the rest of the JavaScript logic (the
usersarray and the code that loops through it) intomain.js. - In
main.js, import the functions fromutils.jsand use them to render the user cards. - Make sure to link
main.jscorrectly in yourindex.htmlfile so that it works as a module.
Starting Code (all in one file):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>User List</title>
<style>
body { font-family: sans-serif; background-color: #f0f2f5; }
.container { max-width: 800px; margin: 2rem auto; }
.card { background: white; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.card h3 { margin: 0 0 0.5rem; }
.card p { margin: 0; color: #65676b; }
</style>
</head>
<body>
<div class="container" id="user-container">
<h1>Our Team</h1>
</div>
<script>
const users = [
{ id: 1, name: 'Alex Chen', role: 'Lead Designer' },
{ id: 2, name: 'Brenda Smith', role: 'UX Researcher' },
{ id: 3, name: 'Carlos Gomez', role: 'UI Designer' }
];
// This function creates the HTML string for a single user card
const createUserCard = (user) => {
return `
<div class="card" id="user-${user.id}">
<h3>${user.name}</h3>
<p>${user.role}</p>
</div>
`;
};
// This function appends the card HTML to the container
const appendCard = (htmlString) => {
const container = document.getElementById('user-container');
container.innerHTML += htmlString;
};
// Main logic: Loop through users and render them
users.forEach(user => {
const cardHTML = createUserCard(user);
appendCard(cardHTML);
});
</script>
</body>
</html>
Click here for the solution
File: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>User List</title>
<style>
body { font-family: sans-serif; background-color: #f0f2f5; }
.container { max-width: 800px; margin: 2rem auto; }
.card { background: white; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.card h3 { margin: 0 0 0.5rem; }
.card p { margin: 0; color: #65676b; }
</style>
</head>
<body>
<div class="container" id="user-container">
<h1>Our Team</h1>
</div>
<!-- Note the type="module" attribute -->
<script type="module" src="./main.js"></script>
</body>
</html>
File: utils.js
// This function creates the HTML string for a single user card
export const createUserCard = (user) => {
return `
<div class="card" id="user-${user.id}">
<h3>${user.name}</h3>
<p>${user.role}</p>
</div>
`;
};
// This function appends the card HTML to the container
export const appendCard = (htmlString) => {
const container = document.getElementById('user-container');
container.innerHTML += htmlString;
};
File: main.js
// Import the necessary functions from utils.js
import { createUserCard, appendCard } from './utils.js';
const users = [
{ id: 1, name: 'Alex Chen', role: 'Lead Designer' },
{ id: 2, name: 'Brenda Smith', role: 'UX Researcher' },
{ id: 3, name: 'Carlos Gomez', role: 'UI Designer' }
];
// Main logic: Loop through users and render them
users.forEach(user => {
const cardHTML = createUserCard(user);
appendCard(cardHTML);
});
Remember to run this using a local server (like the Live Server extension in VS Code) for the modules to load correctly.
Conclusion
Excellent work! You've just taken a huge step toward writing professional, modern JavaScript. By organizing your code into modules, you make it cleaner, more reusable, and easier to maintain as your projects grow in complexity.
Key Takeaways:
- Modules for Organization: Each file is a module with its own private scope, preventing conflicts in the global scope.
exportto Share: Useexport(for named exports) andexport default(for a single, primary export) to make code available to other files.importto Use: Useimport { named } from './file.js'andimport Default from './file.js'to consume exported code.- HTML Setup is Crucial: You must add
type="module"to your<script>tag and use a local server to test your code.
In our next lesson, we will begin a new module on Asynchronous JavaScript. We'll explore how JavaScript handles tasks that take time, like fetching data from a server, without freezing the user interface. This is fundamental to building dynamic, data-driven applications.
Can't find a good explanation? Sign up and we'll make it for you
Sign up