Overview
This structure ensures readable, modular, and maintainable JavaScript with clear organization.
File Structure
Every JavaScript file should follow this organizational pattern:
/**
* Script Purpose: [Brief description]
* Author: [Your Name]
*/
console.log("Script Name loaded");
//
//------- Utility Functions -------//
//
function utilityFunction() {
// ... implementation
}
//
//------- Main Functions -------//
//
function initComponent() {
// ... implementation
}
//
//------- Event Listeners -------//
//
function setupEventListeners() {
// ... implementation
}
//
//------- Initialize -------//
//
document.addEventListener("DOMContentLoaded", () => {
initComponent();
setupEventListeners();
});
Section Organization
- File header. Short comment block with purpose and author.
- Console log. One
console.log()at the top to confirm the script loaded. - Utility functions. Reusable helper functions grouped together.
- Main functions. Primary functionality grouped together.
- Event listeners. Event handling functions grouped together.
- Initialize. All setup calls within a single
DOMContentLoadedlistener at the end.
Section Dividers
Use a three-line divider to separate major groupings. The blank // lines above and below give it visual weight that sets it apart from ordinary comments:
//
//------- Section Name -------//
//
Most functions need no comment above them if the name is clear. Add one only when the purpose is not obvious from the name:
// Recalculates positions after the Barba page transition finishes
function refreshLayout() {
// ... implementation
}
Variables
Use const by default. Use let only when you need to reassign the value. Never use var.
const maxItems = 10; // never reassigned
let scrollPos = 0; // updated as the user scrolls
var has confusing scoping behaviour and should not appear in any file.
Naming Conventions
| Type | Example | Rule |
|---|---|---|
| Functions | initSlider(), handleClick() |
Use lower camelCase, clear and descriptive |
| Variables | mainElement, scrollPos |
Use lower camelCase, clear and descriptive |
| File Names | slider.js, utils.js |
Use lowercase kebab-case |
| Constants | MAX_ITEMS, API_URL |
Use UPPER_SNAKE_CASE |
Name things for clarity, not brevity. Use as many words as needed to make the meaning obvious, then stop. A longer name that reads clearly beats a short one that does not: submitButton is better than btn, and event is better than e. Keep it short only when the shorter version is just as clear.
Initialization Pattern
- Define utility functions first.
- Define main functions grouped by section.
- Define event listeners separately.
- Call everything within a single
DOMContentLoadedlistener at the end.
Re-initialization on SPA Navigation
DOMContentLoaded fires once per full page load. On a site with client-side navigation (Barba or any SPA router), the fetched page's scripts never execute and DOMContentLoaded never re-fires — a module initialized only from DOMContentLoaded silently breaks after the first navigation.
On such a site, each module exposes its init and registers it twice: once for the initial load, once for the router's after-navigation event (the studio site fires studio:after-nav from its Barba hooks).
function initGallery() {
const gallery = document.querySelector("[data-gallery]");
if (!gallery || gallery.dataset.initialized) return;
gallery.dataset.initialized = "true";
// ... implementation
}
//
//------- Initialize -------//
//
document.addEventListener("DOMContentLoaded", initGallery);
document.addEventListener("studio:after-nav", initGallery);
Rules:
- Init functions must be idempotent. Both events can hit the same DOM, so guard against double-init (the
data-initializedpattern above). - Tear down what you set up. Listeners, observers, and animation timelines bound to the old page must be removed in the router's before-leave hook, or they leak across navigations.
- The single-initialize-section rule still applies. The after-nav listener sits beside the
DOMContentLoadedlistener at the end of the file, not scattered through it.
Commenting
Comment the why, not the what. Well-named code already explains what it does, so a comment earns its place only when it explains something the code cannot: a decision, a workaround, or behaviour that is not obvious.
Skip comments that just restate the code:
// Get the button
const button = document.querySelector(".btn");
Keep comments that explain something non-obvious:
// 50ms delay needed because the Barba transition is still painting
setTimeout(initSlider, 50);
The goal is fewer, more useful comments. Too many comments make code harder to read, not easier, because the logic gets buried in prose.
Console Logging
Include one console.log() at the top of each script to confirm it has loaded. Do not scatter console logs throughout the code.
Early Returns
Exit a function early instead of wrapping its body in a large if. This keeps the main logic flat and easier to follow:
// Avoid
function init(element) {
if (element) {
// ... lots of code
}
}
// Prefer
function init(element) {
if (!element) return;
// ... lots of code
}
Deeply nested conditionals are the most common thing that makes code hard to read. Handle the exit cases first, then write the main logic at the top level.
Magic Numbers
Avoid bare numbers whose meaning is not obvious. Give them a named constant so the value explains itself and can be changed in one place:
// Avoid
setTimeout(initSlider, 50);
// Prefer
const transitionDelay = 50;
setTimeout(initSlider, transitionDelay);
File Length
Keep each file focused on one component or concern. When a file grows past roughly 200 to 300 lines, or starts handling two unrelated things, split it into separate files. This keeps the single DOMContentLoaded pattern clean and makes each file easier to read.
Rules to Follow
Organization
- Group related functions together
- Use section dividers for major groupings
- Place initialization at the end
- Keep utility functions separate from main functions
Commenting
- Include a short file header with purpose and author
- Comment the why, not the what
- Comment a function only when its name does not make the purpose clear
- Use section dividers for major groupings
Code Quality
- Use descriptive function and variable names, clear over short
- Use lower camelCase for functions and variables
- Use
constby default,letwhen reassigning, nevervar - Use early returns to avoid deep nesting
- Replace magic numbers with named constants
- Follow naming conventions consistently
- Place initialization in a
DOMContentLoadedlistener (plus the router's after-nav event on SPA sites)
What to Avoid
- Scattering related functions across the file
- Mixing utility functions with main functions
- Multiple initialization points
- Excessive console logging
- Comments that restate what the code already says
- Files that handle more than one concern