My JavaScript Style Guide

Posted by Cyril Walle

Last edit

JavaScript Red Style Guide

red

red is life passion fire

Priorities

  1. Readability
  2. Crystal clear way to add and subtract functionality
  3. Minimal vocabulary
  4. Modern

Outline

  1. Features to use
  2. Features to avoid
  3. Features to never use
  4. Details

1. Features to use

general

Use trailing commas.

Prefer const over let, no var.

Explicit type conversion with the functions String, Boolean, Number, Array.from, Object.fromEntries.

return, throw as early as possible to avoid huge indentation levels.

Make every line of code independent if possible, do not use chain variable assignments.

function

Use an expression because:

  • it works as assignment
  • as parameter for another function
  • as IIFE
  • compatible with arrow and original function syntax
  • ability to mark as constant
  • easy to alias
  • easy to decorate
  • no hoisting

Ideally less than 4 parameters. If more consider using an objects and destructuring. Always use explicit return for arrow functions.

const x = function (a, b) {
    return a - b;
 };

 const y = (a, b) => {
     return a * b;
 };

object life-cycle

Declare all fields as soon as possible even if they don't have a value yet, put the creator/constructor function first. A seal on the object returned by the constructor return Object.seal(hero)) should not throw any error.

const createHero = ({ name }) => {
    const hero = {
        name,
        hitPoints: 100,
        location: [0, 0],
        favouriteAttack: undefined,
    };
    
    return hero;
};

const moveHero = (hero, [x, y]) => {
    const [pastX, pastY] = hero.location;
    hero.location = [pastX + x, pastY + y];
};

const teleportHero = (hero, location) => {
    hero.location = location;
};

Export and import as a namespace

Alternatively omit "Hero" in function names to avoid renaming. js export { createHero as create, moveHero as move, teleportHero as teleport, };

import * as Hero from "./hero.js";


const hero = Hero.create({ name: `Superjhemp` });
Hero.move(hero, [5, 20]);
Hero.teleport(hero, [100, 100]);

array, object, import, export

Use multiple lines for 4 or more items and consider it at 3 items.

array

.length = 0 to reset it.

[] to create a new array.

.push() to append.

const array = [
    4,
    5,
    6,
];

Use map, forEach and other array methods to loop over an array. Array.isArray for type checking.

object

object[key] = value; to add a key value pair.

delete object[key] to remove a key value pair.

{} to create a new object.

Use the shorthand when possible , computed properties

const b = 7;
const c = `key`;
const object = [
    a: 5,
    [c]: 9,
    b,
];

Use Object.keys, Object.values, Object.entries to convert it to an array, then use array methods to loop over an object.

typeof x === `object` && x !== null for type checking.

Object.hasOwn to check the existence of a property-value pair.

strings

Use backtick ` as they always work. ' and " can be included directly. Concatenation is done via ${x}. Adding line breaks just works. \n can also be used. There is no need to switch the delimiter.

const s = `string`;
const s2 = `Tom says: "That's my ${s}"`;

typeof x === `string` for type checking .

numbers

Prefer Number.isFinite over isNaN. Number.isFinite can also be used for type checking. It returns false if the number is NaN, Infinity, -Infinity or another type. typeof x === `number` will return true if x is NaN, Infinity, -Infinity as well.

symbols

const mySymbol = Symbol();
const yourSymbol = Symbol(`A meaningful description`);
yourSymbol.description;

Do not use Symbol.for because they are disguised globals. typeof mySymbol === `symbol` for type checking.

date

Use numbers to store dates. Use the Date built-in to display them.

Store the current time.

const time = Date.now();

To display the date.

const date = new Date();
date.setTime(time);

date.toLocaleString();
date.toLocaleTimeString();
date.toLocaleDateString();
// and other toString variants

sets

Yes

new Set();

When to use sets over arrays

  • No duplicates are wanted
  • The order is irrelevant

maps

Yes

new Map();

When to use maps over objects

In the following situations:

  • Keys are something else than string or symbol
  • Continuous adding and removal of key-value pairs
  • Keys are unknown or can be anything

absence of value

Use undefined, avoid null. undefined is the default that is already used by the language, for example: destructuring when missing, default return value, unassigned variable etc.

Optional chaining

Yes.

The chain stops when expecting a value from undefined. Know the difference between those:

(undefined)?.startsWith(`a`); // undefined
{}?.startsWith(`b${1+2}`); // TypeError and what is inside the parentheses is computed
{}?.startsWith?.(`c`); // undefined

{}.a?.b?.c?.["d"] // undefined

In the second example we expect a value from undefined and the chain immediately stops.

In the second example we expect a value from an object (startsWith), which resolves to undefined, and then we call it (cannot call undefined).

In the third example we also use an optional call (?. + parentheses) to only call it if it is callable.

Booleans

true or false, use Boolean function to force cast to a Boolean. Do not use !! to cast to a Boolean. Leverage truthy values in if and while.

promises

Prefer promises over callbacks for oneasync, await , Promise.all.

if else

Always use multi-line brackets.

if (condition) {

}
if (condition) {
    
} else {

}
if (condition) {
    
} else if (otherCondition) {

} else {

}

import export

Put them at the top of the file as they will be executed first. Put exports before imports as they are executed before. Use named exports, as they allow for extension and are always live bindings. Do not inline named exports in the middle of the file to make it obvious what is exported by reading the first line.

export { y };

import FastAverageColor from "fast-average-color/dist/index.esm.js";
import { x } from "./x.js";
import * as z from "./z.js";


const y = 5;

import/export order

  1. exports with optional line break
  2. imports of standard library
  3. imports of runtime library (NodeJS, Deno, etc)
  4. imports of external dependencies
  5. imports of local files
  6. optional line break
  7. declaration of constants that are import related (some libraries only export default)
  8. optional two line breaks then begin of code

Limit variable reach

Historically it has been done with an immediately invoked function expression, now that let and const are available and block scoped use a simple block. With an iife:

// do not expose i
let nextSquare;
(function () {
    let i = 0;
    nextSquare = function () {
        i += 1;
        return i ** 2;
    };
}());

With a block:

let nextSquare;
{
    let i = 0;
    nextSquare = function () {
        i += 1;
        return i ** 2;
    };
}

global built-ins

Use directly.

setTimeout(() => {

}, 1000);

avoid

window.setTimeout(() => {

}, 1000);

2. Features to avoid

this and class

Read Why disallow the class keyword

Avoid this. And associated bind, call, apply, class, prototype, super, new, extends, Object.create, Object.setPrototypeOf, Object.getPrototypeOf, __proto__, instanceof, typeof, .prototype.isPrototypeOf.

Any function can return an object, any function can take an object as first argument and operate on it. Every function can compose or combine results of other functions.

Prefer regular objects and functions over class. These can be exported from the same file and one can still use Object-oriented patterns without the class keyword.

bind can still be useful for currying with undefined as first value.

apply

Prefer array spread syntax.

const numbers = [4, 5];
const max = Math.max.apply(undefined, numbers);
const numbers = [4, 5];
const max = Math.max(...numbers);

regular expressions

Avoid when possible. Regular expression is a powerful language that overlaps with JS constructs. Often a single line can obscure recursive and complex instructions. The tooling to debug and assess performance of regexs are lower than JS.

Avoid the constructor syntax. Use named groups.

let r = /[a-z]+/;

Alternatives

Use regular objects

Regex are sometimes used to extract information out of a big string. Consider using an object instead that can be extracted with JSON for example.

Raw String
  • String.prototype.includes
  • String.prototype.indexOf
  • String.prototype.replace
  • String.prototype.endsWith
  • String.prototype.startsWith

ternary operator

Prefer if else, as they can be extended and are cleaner.

switch case break

Prefer if else.

conditional assignment

Avoid

const x = y || z;
const r = z && u || w;

Prefer if else.

chained method calls

Keep lines independent.

comma operator

The comma operator is well known to write one-liners and hurts readability. Put each instruction on individual lines.

for of, for in, for () loops

Prefer array built-ins to iterate.

Export objects with multiple variables

Prefer exporting variables individually via named exports. Three shake friendlier and less runtime overhead.

Avoid

export { constants };

const constants = {
    X: 1,
    Y: 2,
};

Prefer

export { X, Y };

const X = 1;
const Y = 2;

default exports

No clear way to add or subtract functionality. Prefer named exports.

Meta-programming and Proxy

Avoid whenever possible. Avoid the use of Proxy, Object.defineProperty, Function.name and Function.length.

getters and setters

For setters that have additional functionality prefer explicit functions. Otherwise use public members.

Implicit type conversion

Prefer explicit type conversion

const x = `1`;
// const y = +x;
const y = Number(x);
const y = 1;
//const x = "" + y;
const x = String(x);

arguments

Do not use the special arguments, use rest arguments instead.

Soft equal

Avoid == and !=, prefer === and !==.

Automatic semicolon insertion

Avoid relying on asi to be consistent and have less edge cases to remember.

try catch over everything

Use try catch on individual statements that are expected to fail for code clarity it makes it obvious which one is expected to fail. Generally avoid try catch entirely if possible. Validate input as early as possible to avoid potential error management in the middle of the function body.

try catch finally

Avoid finally, in most cases, putting the statements after try catch has the same effect.

generators, iterators

Avoid generators and associated yield keyword. Prefer functions that return a function with a closure.

side effect inside if

Avoid any side effect inside the condition of an if. Same for while and for.

disable the linter on individual files, lines

Be consistent.

multi level destructuring

Avoid,

const { body: { className } } = document;

prefer 1 line per level.

const { body } = document;
const { className } = body;

unary operators ++ --

Avoid those. Prefer += and -=. There is no need to remember the difference between --a and a--. The increment amount can be something else than 1. It can be any variable. It is consistent with other operators such as /= , *= , **= , %= etc

let a = 1;
a += 1;
a /= 2;

3. Features to never use

global variables

Prefer explicit exported variables. With the exception of polyfills.

Modifications of built-ins

Prefer exporting new variables with different names.

with

Use object destructuring instead.

non-strict mode

Strict mode throws error when doing things like assigning to undefined and will prevent mistakes. Strict mode is always enabled when using import/export.

new Boolean, new String, new Array, new Object

These have no real purpose other than introduce subtle bugs. Use Boolean, String and array literal [] instead. new Object() is harmless but use the object literal shorthand {} for consistency.

void operator

Don't use the void operator, use a dedicated minifier instead.


Details

naming

Use semantic long descriptive names for variables. Do not use names that are already used by builtspecial keywords.

Camel case for regular variables and file names. No spaces.

Optionally Pascal case for creator/constructors.

Optionally MACRO_CASE (all caps with underscores) for top level constants that do not change across versions and runs.

Package/module names and script names with Kebab case (lowercase with dashes). No dots.

Default imports use the same letters as the package name.

Examples

majo-ubjson as package name

majoUbjson.js entry file (not index.js)

bundle, minify-html as script names

// ALWAYS the same
const { PI } = Math;
const HALF_PI = PI / 2;

// regular variables that can be changed
const radius = 1;
const volumeSphere = (4 / 3) * PI * radius ** 3;

// constructor
const BookStore = class {

};

// creator Pacal (CreateHero) or camel case (createHero)
const createHero = function () {
    const hero = {};
    return hero;
};

// No !
// confusing 
const await = 2;
// overshadows built-in
const Date = { year: 2019 };

indentation

Indent with 4 spaces, or 1 tab.

spacing

Start with prettier.

Extensions

If any non standard features are used, use should be documented. If any features are not in the standard track pipeline, the file extension should reflect that.

Prefer to avoid use of features that have not reached stage 4.

Empty if else bodies

Yes when it helps document the code, for example by acknowledging a given situation.

magic numbers

Use constants at the top of the scope.

package.json (when used)

Order by what you want to see the most.

  1. Name, version, description, license, author, homepage (What is it ?)
  2. type, main, module, browser, exports, bin (What is the entry file ?)
  3. scripts (What commands are available ?)
  4. dependencies, optionalDependencies, peerDependencies devDependencies (What does it use ?)
  5. configs, engines, os, cpu (How is it configured ?)
  6. files, repository, keywords, private, publishConfig (Meta, publishing, distribution)

Avoid peerDependencies because they create more problems than they solve, instead specify them in the documentation.

Minimal example

{
  "name": "utilsac-example",
  "version": "15.0.0",
  "description": "Utility functions",
  "license": "CC0-1.0",
  "type": "module",
  "main": "utility.js",
  "repository": {
    "type": "git",
    "url": "git://github.com/GrosSacASac/utilsac-example.git"
  }
}

About

Lint set-up

Install eslint and eslint-red rules

npm i -D eslint eslint-config-red

Inside package.json

"eslintConfig": {
    "extends": ["red"],
    "parserOptions": {
      "ecmaVersion": "latest",
      "sourceType": "module",
      "ecmaFeatures": {}
    },
    "env": {
      "es2022": true,
      "browser": true,
      "node": true,
      "serviceworker": true,
      "worker": true
    },
    "rules": {}
}

Below es2021, remove unused environment.

If code is meant for other environments other than browser, add them to "env"

Inside package.json > scripts

"lint-fix": "eslint --ignore-path .gitignore --fix source",
    "lint": "eslint --ignore-path .gitignore source",

where source is the folder containing all the files to lint.

Inspiration

Contributors

  • GrosSacASacs
  • fschoenfeldt

License

Public Domain

See also

CSS Style Guide