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).