Breaking Your Code into Modules

As you've been coding, you've probably noticed that your JavaScript files can get lengthy and complex. This is where modules come in handy.

Modules allow you to break your code into smaller, more manageable pieces, each with its specific functionality. This makes your code easier to maintain, debug, and reuse. Let's dive into the basics of using modules in JavaScript and how they can help you organize your projects better.

What Are Modules?

Think of modules as little boxes of code, each containing specific parts of your program. For example, you might have one module for handling data fetching, another for UI elements, and another for utility functions. By keeping related code together, modules help keep your project organized and make finding and fixing bugs easier.

In JavaScript, modules can be created by splitting your code into different files and using import and export statements to share functions, variables, or classes between these files.

This section will create another variation of our Pokémon API project and divide some of its functionality into different files.

In short, here's why we should use modules:

  1. Organization: Modules help you organize your code logically, grouping related functions and data.
  2. Reusability: You can reuse modules across different application parts or even in various projects.
  3. Maintainability: Smaller, focused modules are easier to understand, test, and maintain. If you need to update a function, you can do it in one place without worrying about breaking other parts of your code.
  4. Avoiding Conflicts: Modules help prevent variable and function name conflicts by keeping your code separate.

Basic Module Syntax

To create a module, you split your code into different files. Let's go through an example where we split a program into two modules: one for fetching Pokémon data (pokemonFetcher.js) and another for displaying that data (display.js) and then connecting and using them in the main file (script.js).

First, let's create a module that fetches Pokémon data. In a file named pokemonFetcher.js, you might have:

// pokemonFetcher.js

export async function fetchPokemonData(pokemon) {
  try {
    const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`);
    if (!response.ok) {
      throw new Error(`Could not find Pokémon: ${pokemon}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
    return null;
  }
}

Here, fetchPokemonData is a function that fetches data for a given Pokémon from an API. We use the export keyword to make this function available to other files.

Now, let's create another file that'll handle the UI logic, display.js, where we'll import the fetchPokemonData function and use it to display Pokémon data on the screen:

// display.js

export function displayPokemon(data) {
  if (!data) return;
  
  const pokemonContainer = document.getElementById('pokemon');
  const card = document.createElement('div');
  card.className = 'pokemon-card';

  const img = document.createElement('img');
  img.src = data.sprites.front_default;
  img.alt = data.name;

  const name = document.createElement('h2');
  name.textContent = data.name;

  const abilities = document.createElement('p');
  abilities.textContent = 'Abilities: ' + data.abilities.map(a => a.ability.name).join(', ');

  card.appendChild(img);
  card.appendChild(name);
  card.appendChild(abilities);

  pokemonContainer.appendChild(card);
}

In display.js, we define a function displayPokemon that creates and appends HTML elements to display the Pokémon's image, name, and abilities. This function is also exported so it can be used in other files.

Enabling Modules in HTML

To use modules in a web environment, such as an HTML file, you must ensure the browser knows you're working with modules. This involves two key steps:

  1. Setting the Type Attribute: In your HTML file, set the type attribute of your <script> tag to "module".

  2. Using the import Path Correctly: Use the correct path for your module files and include the file extension .js.

Here's how you can set up your HTML file to use these modules:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Pokémon Info</title>
  <link rel="stylesheet" href="styles.css">
  <script type="module" src="script.js"></script>
</head>
<body>
  <h1>Pokémon Info</h1>
  <div id="pokemon"></div>
</body>
</html>

In this setup:

  • The <script type="module" src="script.js"></script> tag tells the browser to treat script.js as a module. This means the browser will support import and export statements within that script.

CSS for Displaying Pokémon

To make the Pokémon data look nice, you can add some basic CSS:

/* styles.css */

body {
  font-family: Arial, sans-serif;
  background-color: #f8f8f8;
  color: #333;
  text-align: center;
}

h1 {
  color: #ffcb05;
  text-shadow: 2px 2px #2a75bb;
}

#pokemon {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 20px;
}

.pokemon-card {
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  margin: 10px;
  padding: 20px;
  width: 200px;
  text-align: center;
}

.pokemon-card img {
  width: 100px;
  height: 100px;
}

.pokemon-card h2 {
  font-size: 1.5em;
  color: #333;
}

.pokemon-card p {
  margin: 5px 0;
}

It's not required, but a bit of CSS always makes things look prettier!

Running Your Project on a Local Server

To ensure your modules load correctly, you must serve your project files over a local server. Due to security restrictions, browsers block file:// URLs from using modules, so a server is necessary.

One of the easiest ways to do this is using the Live Server extension in Visual Studio Code (VS Code). Here's how to set it up:

  1. Install Live Server Extension:

    • Open VS Code.
    • Go to the Extensions view by clicking the Extensions icon in the Activity Bar on the side of the window.
    • Search for "Live Server" and install the extension by Ritwick Dey.
  2. Running the Server:

    • Open your project folder in VS Code.
    • Right-click on your index.html file (or whichever file is your main HTML file) in the Explorer view.
    • Select "Open with Live Server".

This will start a local server and open your project in a new browser tab. The URL will typically be something like http://127.0.0.1:5500/index.html. Now, your modules should work perfectly!

Named Exports and Imports

The syntax we have used to import/export our functions is known as "named exports." Named exports allow you to export multiple functions, objects, or primitives from a module. These exports must be imported using the exact names or using aliases.

Think of this as destructuring and pulling parts from an object using that technique:

// pokemonFetcher.js

export async function fetchPokemonData(pokemon) {
  // ...
}

export function logFetchError(error) {
  console.error('Fetch Error:', error);
}

To import these functions, you use curly braces:

// script.js

import { fetchPokemonData, logFetchError } from './pokemonFetcher.js';
import { displayPokemon } from './display.js';

async function loadPokemon(pokemonName) {
  try {
    const data = await fetchPokemonData(pokemonName);
    displayPokemon(data);
  } catch (error) {
    logFetchError(error);
  }
}

loadPokemon('pikachu');

You can rename imports using the as keyword:

import { fetchPokemonData as fetchData, logFetchError as logError } from './pokemonFetcher.js';

Default Exports and Imports

Something we haven't used in the examples but are also common is something called "default exports." Default exports are helpful when a module exports a single thing, like a function or an object. A module can only have one default export.

// display.js
export default function displayPokemon(data) {
  // ...
}

Importing a default export is straightforward and doesn't require curly braces:

// script.js
import displayPokemon from './display.js';

You can also mix default and named exports in a module:

// utilities.js
export default function logMessage(message) {
  console.log(message);
}

export function logError(error) {
  console.error(error);
}

And import them together:

import logMessage, { logError } from './utilities.js';

Unlike named exports, we can name our incoming default imports any name at all as a variable and it will set it's value to the name:

// utilities.js
export default function logMessage(message) {
  console.log(message);
}
// script.js
// Use a random variable name:
import myCoolLogger from './utilities.js';

Exporting and Importing Various Values

In JavaScript modules, you're not limited to exporting and importing functions like we have in the previous examples.

You can export and import any type of value, including objects, arrays, variables, constants (hard-coded values in variables), and anything you might need. This flexibility allows you to share any kind of data or functionality between different parts of your application, making your code more modular and reusable.

Here are examples:

// data.js

export const appName = 'PokeApp';
export const version = '1.0.0';

export const pokemonList = ['Pikachu', 'Charmander', 'Bulbasaur'];

export class Trainer {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

In the data.js file above, we export a constant (appName), a variable (version), an array (pokemonList), and a class (Trainer). This demonstrates the diverse range of values you can share between modules.

You can use these values When you import them into another module. This makes accessing shared data or functionality from different parts of your application easy.

// main.js

import { appName, version, pokemonList, Trainer } from './data.js';

console.log(appName); // Output: PokeApp
console.log(version); // Output: 1.0.0

console.log(pokemonList); // Output: ['Pikachu', 'Charmander', 'Bulbasaur']

const ash = new Trainer('Ash Ketchum');
console.log(ash.greet()); // Output: Hello, my name is Ash Ketchum

In this main.js file, we import all the exported values from data.js. We can seamlessly integrate these elements into our application, whether constants, arrays, or classes.

Exporting and importing any value means you can keep your code organized and modular. For instance:

  • Configuration Settings: Export constants for API URLs, default settings, or other configurations that might change across different environments (development, production, etc.).
  • Shared Data: Store shared data, such as a list of Pokémon or user preferences, in arrays or objects that can be accessed from multiple modules.
  • Reusable Components: Export classes or objects that encapsulate complex logic or data structures, making them easy to reuse across your application.

This new section emphasizes the versatility and power of JavaScript modules, highlighting how you can share a wide range of values between different parts of your application. This understanding is key to effectively using modules to create clean and maintainable code.

Best Practices

  1. Keep Modules Focused: Each module should have a clear purpose, like

handling API requests, managing state, or displaying data. 2. Use Meaningful Names: Name your modules and exports clearly so it's easy to understand what each one does. 3. Avoid Large Modules: If a module gets too large, consider splitting it into smaller modules. For instance, if pokemonFetcher.js starts handling too many unrelated tasks, break it into separate files like api.js and utilities.js.

Congrats! You've just set up a basic project using JavaScript modules.

Breaking your code into smaller, more focused pieces can keep things neat and easily managed. This approach will save you time and headaches as you work on more complex projects.

Next up, we’ll explore using libraries and frameworks in JavaScript. These tools can supercharge your development, helping you build apps faster and with less effort. So, stay tuned and keep coding!

BeginnerJavaScript
Avatar for Niall Maher

Written by Niall Maher

Founder of Codú - The web developer community! I've worked in nearly every corner of technology businesses: Lead Developer, Software Architect, Product Manager, CTO, and now happily a Founder.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.