# How to Organize and Structure a Vanilla JavaScript Project with Multiple Modules

> Organize vanilla JavaScript projects with multiple modules by treating features as self-contained folders and using ES modules. Learn to build scalable and maintainable codebases.

- Repository: [Jisan Mia/dom-projects](https://github.com/jisan-mia/dom-projects)
- Tags: how-to-guide
- Published: 2026-03-04

---

**Treat each feature as a self-contained folder bundling its own HTML, CSS, and JavaScript files, then use ES modules and a central entry point to keep the codebase scalable and maintainable.**

When building a pure JavaScript web application with many independent features, learning how to organize and structure a vanilla JavaScript project with multiple modules is essential for long-term maintainability. The **dom-projects** repository by jisan-mia demonstrates an elegant folder-per-feature architecture that keeps each mini-app isolated while sharing common utilities where needed.

## The Folder-Per-Feature Pattern

The `dom-projects` repository organizes code by placing each distinct feature inside its own directory under `projects/`. This approach eliminates global namespace pollution and makes dependencies explicit.

Consider the repository structure:

```text
dom-projects/
├── index.html          # Central launcher

├── index.js            # Optional central script

└── projects/           # Container for all modules

    ├── advanced-todo/
    │   ├── index.html
    │   ├── script.js
    │   └── style.css
    ├── basic-calculator/
    │   ├── index.html
    │   ├── script.js
    │   └── style.css
    ├── camera-app/
    │   ├── index.html
    │   └── camera.js
    ├── flappy-bird/
    │   ├── index.html
    │   ├── flappybird.js
    │   └── flappybird.css
    ├── js-todo/
    │   ├── index.html
    │   └── script.js
    └── … (other mini-apps)

```

Each folder functions as an independent module. The `camera-app` module, for example, contains [`camera.js`](https://github.com/jisan-mia/dom-projects/blob/main/camera.js) which handles MediaDevices API logic without interfering with the `flappy-bird` game loop defined in [`flappybird.js`](https://github.com/jisan-mia/dom-projects/blob/main/flappybird.js).

## Implementing ES Modules in Vanilla JavaScript

Modern browsers support ES modules natively, allowing you to maintain strict scope isolation without bundlers. In `dom-projects`, modules use `type="module"` to enable import/export syntax.

### Module Entry Points

Each module's HTML file declares its script as a module:

```html
<!-- projects/weight-converter/index.html -->
<link rel="stylesheet" href="style.css">
<h1>Weight Converter</h1>
<input id="kg" placeholder="kg">
<span id="lb"></span>

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

```

### Scoped Module Logic

The corresponding JavaScript runs in module scope, preventing global variable leakage:

```javascript
// projects/weight-converter/script.js
const kgInput = document.getElementById('kg');
const lbSpan = document.getElementById('lb');

kgInput.addEventListener('input', () => {
  const kg = parseFloat(kgInput.value) || 0;
  lbSpan.textContent = (kg * 2.20462).toFixed(2) + ' lb';
});

```

### Sharing Utilities Across Modules

When multiple modules need common functionality, place shared code in a utilities folder and import it explicitly. The `dom-projects` repository demonstrates this pattern in [`projects/retro-calculator/js/utils.js`](https://github.com/jisan-mia/dom-projects/blob/main/projects/retro-calculator/js/utils.js).

```javascript
// projects/retro-calculator/js/utils.js
export function formatNumber(num) {
  return num.toLocaleString('en-US');
}

export function saveToStorage(key, value) {
  localStorage.setItem(key, JSON.stringify(value));
}

```

Other modules import these utilities using relative paths:

```javascript
// projects/advanced-todo/script.js
import { saveToStorage } from '../retro-calculator/js/utils.js';

// Module-specific logic using shared utility
saveToStorage('todos', todoList);

```

## Creating a Central Entry Point

The root [`index.html`](https://github.com/jisan-mia/dom-projects/blob/main/index.html) serves as a project launcher, providing navigation to each module without requiring a build step. This pattern keeps the repository accessible for beginners while remaining scalable.

```html
<!-- index.html (repo root) -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM Projects Collection</title>
</head>
<body>
  <h1>Vanilla JavaScript Projects</h1>
  <ul id="projects"></ul>

  <script type="module">
    const list = document.getElementById('projects');
    const modules = [
      'basic-calculator',
      'camera-app',
      'flappy-bird',
      'advanced-todo'
    ];

    modules.forEach(name => {
      const li = document.createElement('li');
      const a = document.createElement('a');
      a.href = `projects/${name}/index.html`;
      a.textContent = name.replace(/-/g, ' ');
      li.appendChild(a);
      list.appendChild(li);
    });
  </script>
</body>
</html>

```

## Documentation and Module Templates

Maintainability requires documentation. The `dom-projects` repository includes a [`README.md`](https://github.com/jisan-mia/dom-projects/blob/main/README.md) template in [`projects/dialog/README.md`](https://github.com/jisan-mia/dom-projects/blob/main/projects/dialog/README.md) that new contributors can copy when adding features.

A well-documented module includes:

- **Purpose**: What the feature does
- **Files**: List of contained assets
- **APIs**: Any external browser APIs used (e.g., MediaDevices, Canvas)
- **Usage**: How to run or test the module

## Summary

- **Folder-per-feature**: Isolate each module in its own directory under `projects/` with dedicated HTML, CSS, and JS files to prevent namespace collisions.
- **ES modules**: Use `type="module"` and explicit `import`/`export` statements to maintain strict scope isolation without bundlers.
- **Shared utilities**: Place common helper functions in a utilities folder (e.g., [`projects/retro-calculator/js/utils.js`](https://github.com/jisan-mia/dom-projects/blob/main/projects/retro-calculator/js/utils.js)) and import them via relative paths.
- **Central launcher**: Use a root [`index.html`](https://github.com/jisan-mia/dom-projects/blob/main/index.html) to dynamically generate navigation links, providing a unified entry point for the entire collection.
- **Documentation**: Include a [`README.md`](https://github.com/jisan-mia/dom-projects/blob/main/README.md) in each module folder explaining purpose, file structure, and external dependencies.

## Frequently Asked Questions

### How do I prevent global namespace pollution in a multi-module vanilla JavaScript project?

Use ES modules by adding `type="module"` to your script tags. This forces each file into its own scope, preventing variable leakage. In the `dom-projects` repository, every module—from [`camera-app/camera.js`](https://github.com/jisan-mia/dom-projects/blob/main/camera-app/camera.js) to [`flappy-bird/flappybird.js`](https://github.com/jisan-mia/dom-projects/blob/main/flappy-bird/flappybird.js)—runs as an isolated module, ensuring that variables like `score` or `stream` remain local to their respective features.

### What is the best way to share utility functions between modules without a bundler?

Place shared utilities in a dedicated JavaScript file and export them as named exports. For example, [`projects/retro-calculator/js/utils.js`](https://github.com/jisan-mia/dom-projects/blob/main/projects/retro-calculator/js/utils.js) exports helper functions like `formatNumber` and `saveToStorage`. Other modules import these using relative paths: `import { saveToStorage } from '../retro-calculator/js/utils.js'`. This pattern keeps dependencies explicit and avoids the complexity of build tools.

### Should I use a single HTML entry point or separate HTML files for each module?

Use separate HTML files for each module to maintain true isolation, then provide a central launcher page at the repository root. In `dom-projects`, each feature like `advanced-todo` or `basic-calculator` has its own [`index.html`](https://github.com/jisan-mia/dom-projects/blob/main/index.html), allowing developers to work on individual modules without loading unnecessary code. The root [`index.html`](https://github.com/jisan-mia/dom-projects/blob/main/index.html) dynamically generates a navigation list, serving as a project showcase and entry point.

### How do I add a new module to an existing vanilla JavaScript project?

Create a new folder under `projects/` containing [`index.html`](https://github.com/jisan-mia/dom-projects/blob/main/index.html), [`script.js`](https://github.com/jisan-mia/dom-projects/blob/main/script.js), and [`style.css`](https://github.com/jisan-mia/dom-projects/blob/main/style.css). Follow the naming convention of existing modules (kebab-case folder names) and include a [`README.md`](https://github.com/jisan-mia/dom-projects/blob/main/README.md) explaining the feature's purpose and any external APIs used. As shown in [`projects/dialog/README.md`](https://github.com/jisan-mia/dom-projects/blob/main/projects/dialog/README.md), documenting the module's file structure and dependencies ensures other contributors can understand and test your addition without examining the source code.