Skip to content

JavaScript Style Guide

Background

This document is intended to outline the style rules for JavaScript here at Miva. This style guide applies to anywhere that JavaScript is written.

Key Points

  • Code according to our ESLint JavaScript linting standards.
  • Use pnpm for 3rd-party package management.
  • Leverage the features of ES2016+ when you're working on a site that has Webpack Babel transpiling. Otherwise stick with ES2015.
  • JavaScript Library Usage
    • Prefer Vanilla JS over 3rd-party libraries/frameworks like jQuery, Vue, React, etc.
    • If a framework has to be used, most people tend to have experience with Vue.
  • AJAX
    • Lean on Vanilla JS's Fetch API as much as you can.
    • Avoid having to rely on jQuery as a dependency for AJAX. Instead look to a dedicated AJAX library like Axios.
  • DOM Selection
    • Core framework JS files will use Vanilla JS selectors to eliminate the need for a dependency in the core framework.
    • Use js- prefixed selectors & Vanilla JS (ex. document.getElementsByClassName('js-foobar')) as opposed to jQuery Selectors ($('.js-foobar')) and Hook selectors ($.hook('foobar')/<span data-hook="foobar">)
    • Also instead of just using document.getElementsByClassName, consider using a more-specific document.getElementById('js-your-container').getElementsByClassName('js-your-elements') to further increase the performance.

File Structure

Module Structure

A module is a reusable chunk of code that is abstracted and easily pulled into projects. JavaScript modules are self contained and use the following directory structure.

 module-name/
    lib/
        ModuleName.js
        module-name.scss
    index.js
    _index.scss
    package.json

The index files in the main module folder import the files from /lib allowing you to create as many files in /lib as you need to create your module. For example your package may rely on multiple classes that are all in their own file. The scss index file should only contain @import statements for files in the /lib directory and should be named with a leading underscore (_). The leading underscore lets Sass know that the file is a partial file and should not be generated into a CSS file, which improves performance.

The files in /lib are the main JS classes for your module, as well as any SCSS files needed for your module.

Classes should be written as named exports.

export class Foo { ... }
  • If your module only has one class file, it should be the same name as your module in PascalCase format.
  • All classes in the /lib folder should be named in PascalCase.
  • Your module folder should be kebab-case (.ie hyphenated and lower case).
  • Use unique, and easily understandable module names.

Module Imports

ES modules must use the import statement when importing other module files.

import './myModule';

import * from './myModule.js';

import {MessageContainer, Message} from 'extensions/messages';

Module Exports

Modules are exported as a default export.

export default class AddToCart { ... }

Formatting

Open and Closing Braces

Open and closing braces should be used in all structures, (ex. if, else, for, do, while), even if it only contains a single statement.

Incorrect
if (condition === true) runEvent();
Correct
if (condition === true) {
    runEvent();
}

Non Empty Blocks

Occasionally you will encounter situations where you have an empty block, such as an empty constructor. Empty blocks should be on a single line with its open and closing braces.

Incorrect
export default class ExampleClass {
    constructor () {
        // Add stuff here if you need it
    }

    private function method(param){
        runEvent();
    }
}
Correct
export default class ExampleClass {
    constructor () {}

    private function method(param){
        runEvent();
    }
}

Indentation

Code should be indented with a single tab character for each block like construct.

Incorrect
if (condition === true) {
while(x > 0){
    doSomething();
    x--;
}
}
Correct
if (condition === true) {
    while(x > 0){
        doSomething();
        x--;
    }
}

Single Statement Per Line

Each statement should be on its own line and followed by a line-break.

Incorrect
function method (param) {
    let x = 123; doSomething(x); doSomethingElse(param);
}
Correct
function method (param) {
    let x = 123;
    doSomething(x);
    doSomethingElse(param);
}

All Statements Must Be Terminated By A Semicolon

Every statement must be followed by a semicolon. Some conventions allow the omission of the semicolon, however here at Miva we require its use.

Incorrect
let x = 'a string'
let y = runMethod(x)
console.log(y)
Correct
let x = 'a string';
let y = runMethod(x);
console.log(y);

Language Features

const vs let vs var

All local variables should be declared with const or let. Avoid using var.

const is preferred by default and used for a variable that will not be reassigned or changed.

let is used when the variable will need to be reassigned or changed.

Incorrect
maxQuantity = 10;
var currentQuantity = 0;

document.forms.add.elements.Quantity.addEventListener('blur', (e) => {
    currentQuantity = e.target.value;
    if (currentQuantity > maxQuantity) {
        alert("That's too many!");
    }
});
Correct
const MAX_QUANTITY = 10;
let currentQuantity = 0;

document.forms.add.elements.Quantity.addEventListener('blur', (e) => {
    currentQuantity = Number(e.target.value);
    if (currentQuantity > MAX_QUANTITY) {
        alert("That's too many!");
    }
});

One Variable Per Declaration

Avoid assigning multiple variables in a single declaration.

Incorrect
let x = 1, y = 45, z = 21;
doSomething(x, y, z);
Correct
let x = 1;
let y = 45;
let z = 21;

doSomething(x, y, z);

Avoid Array Constructor

The Array constructor is prone to errors in events where arguments are added or removed. Literals are preferred instead.

Incorrect
const n1 = new Array(x, y, z);
const n2 = new Array(x, y);
Correct
const n1 = [x, y, z];
const n2 = [x, y];

Avoid Object Constructor

The Object constructor should be avoided for consistency.

Incorrect
const person = new Object();
person.fname = 'Richard';
person.lname = 'Feynman';
Correct
const person = {
    fname: 'Richard',
    lname: 'Feynman'
};
Correct
const person = {};

person.fname = 'Richard';
person.lname = 'Feynman';

Arrow Functions

Arrow functions are allowed and preferred in many cases. Arrow functions simplify the scoping of this for nested functions. Because of this, they should be used over other scoping approaches such as f.bind(this) or const self = this.

Arrow function parameters should always be surrounded in parenthesis to prevent issues when additional parameters need to be added later.

The body of the arrow function should be a block statement surrounded by curly braces.

Incorrect
const self = this;
document.getElementById('foo').addEventListener('click', function (event) {
    // here, "this" is the HTMLElement where the click event occurred
    self.setFoo(42);
});
Correct
document.getElementById('foo').addEventListener('click', (event) => {
    this.setFoo(42);
});

String Literals

Prefer to use single quotes over double quotes for strings. If a string contains a single quote that would break this, consider using a template string.

Template literals should be used in place of complex string concatenation. Template literals are delimited with the ` character.

Incorrect
function allTheMath (x, y) {
    return 'Sum: ' + a + ' + ' + b + ' = ' + (a + b) + "\n" +
        'Difference: ' + a + ' - ' + b + ' = ' + (a - b) + "\n" +
        'Product: ' + a + ' * ' + b + ' = ' + (a * b) + "\n" +
        'Quotient: ' + a + ' / ' + b + ' = ' + (a / b);
}
Correct
function allTheMath (x, y) {
    return `
        Sum: ${a} + ${b} = ${a + b}
        Difference: ${a} - ${b} = ${a - b}
        Product: ${a} * ${b} = ${a * b}
        Quotient: ${a} / ${b} = ${a / b}
    `;
}

Please note that in a template literal, the whitespace may matter in which case it is acceptable to break the standard indentation structure inside the template literal if needed.

For Loops

There are many acceptable forms of the for loop in JavaScript ES6.

Correct
// Standard
for (let i = 0, len = arr.length; i < len; i++) {
    doSomething(arr[i]);
}
Correct
// For Of
const arr = ['a', 'b', 'c', 'd'];
for (const element of arr) {
    doSomething(element);
}
Correct
// For In
const object = {a: 1, b: 2, c: 3, d: 4};
for (const key in object) {
    doSomething(key, object[key]);
}

For ... In loops can only be used to iterate over dictionary style objects that have a key value pair. Because of this, For Of loops are preferred over For In loops.

Exceptions

Exceptions should be considered anywhere an exceptional case could occur. This includes instances where you are using Promises for Asynchronous calls. An method or call that has the potential to encounter an error case should have its exception handled gracefully.

Consider using Try/Catch blocks around methods that could encounter an exception.

Incorrect
doSomethingThatMightFail();
Correct
try {
    doSomethingThatMightFail();
} catch (e) {
    if (e instanceof EvalError) {
        handleException(e);
    } else {
        throw e;
    }
}

Equality Checks

Default to using === and !== in comparison statements to ensure type is checked along with value.

Do not use == or != unless you have a specific use case where catching null and undefined together.

Incorrect
const a = '1';
const b = true;
if (a == b) {
    // This will run because '1' will be type coerced to: true
}
Correct
const a = '1';
const b = true;
if (a === b) {
    // This will not run
} else {
    // This will run because `a` is a string and `b` is a boolean
}

Invoking Class Constructors

Always invoke new class constructors with (). Omitting the parenthesis can lead to subtle mistakes.

Incorrect
new AddToCart;
Correct
new AddToCart();

Validate Your Variables

Be sure to check that the elements exist on the page and that the variables you are using are defined as you expect before using them.

Incorrect

document.getElementById( 'my_element' ).checked = true;
Reasoning: Not checking that my_element exists before trying to change an attribute.

Correct

const myElem = document.getElementById( 'my_element' );
if (myElem) {
    myElem.checked = true;
}
Reasoning: Checking if the element exists first.

Naming Conventions

General Guidelines

All names for functions, variables, classes, etc. should use only letters, digits, and select delimiter characters (underscores, hyphens, etc).

Try to be as descriptive as possible with your name, while still attempting to keep variable names as short as possible. Avoid using abbreviations that may be confusing or ambiguous.

Incorrect
let x = someFunction();
let c_rcrd = customerObject;
let spd = secondsPerDay();
Correct
let numOfOrders = someFunction();
let customerRecord = customerObject;
let secPerDay = secondsPerDay();

Specific Conventions

Package Names

Package names should use kebab-case to compound words within the package name.

Class Names

Class names should use PascalCase to compound words within the class name.

Method/Function Names

Method and function names should use camelCase to compound words within the function or method.

Constant Names

Constant names should use CONSTANT_CASE where all letters are uppercase and words are delimited with an underscore.

CONSTANT_CASE should not be used for all const variables. CONSTANT_CASE should only be used when the contents of the variable will NEVER change and they indicate key configurations or magic-numbers of the functionality (ex. file-paths, an API key, number seconds in a day, etc.).

Parameter Names

Parameter names should use camelCase. Avoid using single character parameters such as function doSomething(x).