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
- 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-specificdocument.getElementById('js-your-container').getElementsByClassName('js-your-elements')
to further increase the performance.
Recommended Reading#
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;
my_element
exists before trying to change an attribute.
Correct
const myElem = document.getElementById( 'my_element' );
if (myElem) {
myElem.checked = true;
}
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)
.