Here is a clean way to organise a small front-end project with modules.
my-app/
├── index.html
├── css/
│ └── styles.css
└── js/
├── main.js ← entry point, wires things together
├── api.js ← talks to the server (fetch helpers)
├── storage.js ← localStorage helpers
├── utils/
│ ├── format.js ← formatPrice, formatDate...
│ └── dom.js ← small DOM helpers
└── features/
├── todo.js ← one feature = one file (or folder)
└── cart.js
<script type="module" src="./js/main.js"></script>
main.js: the glue
import { loadTodos } from "./features/todo.js";
import { initCart } from "./features/cart.js";
document.addEventListener("DOMContentLoaded", () => {
loadTodos();
initCart();
});
(With a module script you often do not need DOMContentLoaded, because modules are deferred.)
A feature module
import { loadJSON, saveJSON } from "../storage.js";
import { el } from "../utils/dom.js";
let todos = loadJSON("todos", []);
export function loadTodos() {
render();
}
function render() {
const list = document.querySelector("#todos");
list.replaceChildren(...todos.map(t => el("li", t.text)));
}
export function addTodo(text) {
todos = [...todos, { id: Date.now(), text }];
saveJSON("todos", todos);
render();
}
Guidelines
- One responsibility per module. If a file does three unrelated things, split it.
- Export only what others need. Keep helpers private.
- Dependencies flow one way. Features can use utilities, but utilities should not import features.
- Name files after what they contain, in a consistent style (
kebab-case.jsis common). - Keep the entry file small. It starts things, it does not contain logic.
- Avoid circular imports.
Bundlers and npm
Real projects usually use npm to install libraries and a bundler (Vite, webpack, esbuild) to prepare code for the browser: it combines modules, removes unused code, and minifies. Imports like import dayjs from "dayjs" then work because the tool resolves the package. You can learn this later; modules in the browser work without it.
Legacy patterns you may meet
- CommonJS:
const x = require("./x"); module.exports = .... It is the older Node.js module system. Modern Node.js and bundlers support ES modules too. - IIFEs and global namespaces:
(function () { ... })();andwindow.MyApp = {}. They were the way to avoid globals before modules. - AMD / UMD: older browser module formats, still inside some libraries.
When you read such code, you can understand the idea: each tries to keep variables private and share a few things. For new code, use ES modules.
Growth plan
Start with a few files. Introduce folders when a folder would hold three or more related files. Do not create structure you do not need yet.