Skip to content

JavaScript

Rules marked [enforced] are caught automatically by ESLint (ddev npm run lint:js).

WhatConventionExample
Variable / functioncamelCasegetPostData, postCount
ClassPascalCaseAjaxClient
Constant (module-level)UPPER_SNAKE_CASEMAX_RETRIES
Filekebab-caseajax-client.js

Default to const. Only use let when the variable is reassigned. Never use var.

// Good
const button = document.querySelector('.btn');
let count = 0;
count++;
// Bad
var button = document.querySelector('.btn');
let label = 'Submit'; // never reassigned — use const

End every statement with a semicolon.

// Good
const greeting = 'hello';
// Bad
const greeting = 'hello'

Use ES6 import/export instead of CommonJS. Each module lives in assets/src/js/modules/ and is loaded conditionally from site.js using loadIf.

loadIf checks for a selector before importing the module. If the element isn’t on the page, the JS chunk is never fetched.

assets/src/js/site.js
import { loadIf } from '@/lib/loadIf';
loadIf('.slider-example', () => import('@/modules/slider-example'));

Each module wraps its logic in an init function and calls it immediately. This allows early returns when required DOM elements are missing — top-level return is not valid in ES modules.

assets/src/js/modules/slider-example.js
import 'swiper/css';
import Swiper from 'swiper';
function init() {
const el = document.querySelector('.slider-example');
if (!el) return;
new Swiper(el);
}
init();

Use async/await instead of .then() chains.

// Good
async function fetchPosts() {
const response = await fetch(window.LUMBERMILL_GLOBAL.rest_routes.lumbermill_v1_posts);
return response.json();
}
// Avoid
fetch(url)
.then((res) => res.json())
.then((data) => console.log(data));

Per file, use either jQuery or Vanilla JS — avoid mixing the two in the same file.

Config: eslint.config.js in the theme root. Run before committing:

Terminal window
ddev npm run lint:js