Skip to content

Instantly share code, notes, and snippets.

@davidbgk
Forked from chrisdickinson/js-style-guide.md
Created February 27, 2014 13:48
Show Gist options
  • Save davidbgk/d763eefc93d8f39e3e4b to your computer and use it in GitHub Desktop.
Save davidbgk/d763eefc93d8f39e3e4b to your computer and use it in GitHub Desktop.

JS Style Guide

  1. JS Style Guide
    1. General
      1. Naming
      2. Indenting
      3. Comments
      4. Literals
      5. Block style
      6. Semicolons
      7. Spacing
      8. Object property lookups
      9. Vertical whitespace (newlines)
    2. Functions
      1. Arrangement
      2. Hoisting
      3. ready
    3. Commas
      1. Variable Declarations
      2. Object Literals and Arrays
      3. Function calls
    4. Classes
      1. Creating a class
      2. Working with this
    5. Modules
      1. Require.JS
      2. Importing modules
      3. Exporting values from a Module
      4. Directories
      5. Module size
    6. JS Idioms
      1. Turn arguments into an array
      2. Push arguments onto a list
      3. Concatenate a list
    7. Templates and HTML
      1. General Template Style
      2. Selecting Elements
      3. Adding event listeners

TL;DR: Use jsl. Complete the primer. Be excellent.

General

Naming

  • Constants MUST be ALL_CAPS.
  • Classes MUST be CapCase.
  • Methods, functions, variable names MUST be snake_case_like_python.

Functions SHOULD either be on<eventname> or <verb>; ideally one word.

Module filenames SHOULD be snake_case titled.

Functions SHOULD have names, even when used in expression form:

var x = function some_name() {
}

Indenting

Use spaces, not tabs. Two spaces per indent level.

function example() {
  var a = 0

  return function() {
    return a + 3
  }
}

// a more complex example:
stream()
  .pipe(through(function() {
    return this.queue(3)
  }))

Comments

Comments SHOULD be used sparingly, and should be in the form:

// one space before characters

Comments SHOULD be on a line by themselves. Check spelling and punctuation in comments. Comments may use markdown syntax internally (informally).

Literals

Prefer using single quotes (') to double quotes (") in new code. In existing code, match surrounding style.

If you have to wrap a quote in a string, use the opposite kind of quote.

Prefer to use template files over concatenating large strings together to create templates.

DO NOT concatentate user input into HTML strings.

// THIS IS VERY BAD:
// (this introduces an XSS vulnerability into the site)
var output = '<div id="' + user.id + '">' +
    '<span class="username">' + user.username + '</span>' +
'</div>'

Block style

Blocks are any grouping of statements surrounded by { }.

The leading brace should be on the same line as the statement, with a space between it and the end parentheses of the statement.

Any statement that optionally takes a block should always have a block (do not omit { or }).

The closing } should line up with the original indent level of the line introducing the block.

If there is an interstitial statement (as in else or catch), the interstitial should be one space after the closing } and introduce its own { on the same line.

Where possible, prefer return, continue, or break to introducing an else clause.

function good() {

}

if(3) {
}

if(3) {

} else {

}

if(3) {

} else if(4) {

} else {

}


try {

} catch(err) {

}

try {

} catch(err) {

} finally {

}

for(var i = 0, len = 1; i < len; ++i) {

}

Semicolons

Do not use semicolons outside of a for loop.

Do not use statements that require a leading semicolon.

// bad:
;[a, b, c].forEach(function() {

})

// better:
var items = [a, b, c]

items.forEach(function() {

})

// also bad:
;(x === 1 && do_something())

// better:
if(x === 1) {
  do_something()
}

Spacing

Surround all binary operations with a space on either side.

Commas, in all cases where they appear between tokens, should be followed by a single space.

Unary operations should not have a space between the operator and the operand, except where necessary.

// bad:
var x = 1+2
  , y = 3/2

x = 'hello'+world

// great:
var x = 1 + 2
  , y = 3 / 2

x = 'hello' + world

Object property lookups

Use computed property lookups only when necessary. I.e., the property is not known at runtime, the property contains characters invalid for an identifier, or the property causes issues with certain JS runtimes (super, delete) due to being a reserved word.

// bad:
obj['hello']

// good:
obj['delete']
obj.hello

To test presence, use obj.prop === undefined. Do NOT use typeof obj.prop !== "undefined" (we don't need to be that defensive or verbose).

Vertical whitespace (newlines)

Every non-expression statement should be separated by one blank newline.

Expression statements may be grouped.

// bad:
var x = 3
x += 2
for(var i = 0, len = 10; i < len; ++i) {
  x /= i
  x *= 2
  y.count += x
  if(x % 2 === 0) {
    break
  }
}
return x

// good:
var x = 3

x += 2

for(var i = 0, len = 10; i < len; ++i) {
  x /= i
  x *= 2
  y.count += x

  if(x % 2 === 0) {
    break
  }
}

return x

Functions

Arrangement

An attempt should be made to put sequences of functions alongside each other, even if they have dependent data:

// we use `file_data` in the outer scope
// so that we don't have to nest `onparsed` into
// `onread`.
function read_then_parse_then_print(file, ready) {
  var file_data

  return read(file, onread)

  function onread(err, data) {
    if(err) {
      return ready(err)
    }

    file_data = data

    parse(data, onparsed)
  }

  function onparsed(err, ast) {
    if(err) {
      return ready(err)
    }

    if(ast.valid()) {
      return print(file_data, ready)
    }

    return ready(new Error('invalid ast'))
  }
}

Hoisting

Hoisting is encouraged for synchronously called functions, provided the hoisted function's name is descriptive enough that the function need not be referenced while parsing the function it is referenced in.

Asynchronous functions (steps) SHOULD be hoisted, and SHOULD appear after the return statement of the function they're being used in. Async functions should have names roughly corresponding to their instigating call's name -- read -> onread, render -> onrender, etc.

Steps SHOULD be defined in the order in which they will be ideally executed.

ready

Functions that perform a single asynchronous task MUST take a callback called ready.

ready MUST be called when the asynchronous operation is complete, whether in failure or in success.

The first argument to ready MUST be null (if no error) or an instance of Error.

The second argument to ready MAY be absent (if no results needed), null (if an error occurred), or the result.

Code receiving an error MUST NOT throw the error if it has a ready function available to it.

ready MUST NOT be called more than once.

proto.render = function(context, ready) {
  var self = this

  context = Object.create(context)
  context.extra = self.something

  self.template.render(context, function(err, html) {
    if(err) {
      return ready(err)
    }

    ready(null, html)
  })
}

Commas

We use comma-first. Here's what you should know:

Variable Declarations

Variable declarations MUST be ordered by:

  1. has assignment to no assignment
  2. line length

The first declaration MUST be preceded with var. Subsequent declarations MUST be preceded with a ,, aligned to the r in var above.

Var statements SHOULD be at the top of their respective functions, with the exception of for-loop declared var statements (i.e., for(var i = 0, for(var key in).

In general, one SHOULD strive to keep complex object creation out of var blocks containing multiple declarations.

Multi-line assignments will be judged on their longest constituent line.

var thing = require('../path/to/file')
  , thing2 = require('../path')
  , some_other_var_name
  , some_non_declared

// single line object vars and assignments:
var obj = {
    x: 5
  , y: 2
}

obj = {
    x: 5
  , y: 2
}

var arr = [
    5
  , 2 
]

arr = [
    5
  , 4
]

// objects and arrays and functions
// NOTE: this is not recommended! 
var thing = require('thing2')
  , thing2 = require('bop')
  , obj2 = function() {
      return obj
    }
  , obj = {
        x: 5
      , y: {
            a: 1
          , b: 3
        }
    }
  , arr = [
        a
      , b
    ]

Object Literals and Arrays

As above, the first item must be indented twice relative to the open brace or bracket.

Subquent items will be indented once, then preceded with a comma and a space.

One SHOULD avoid nesting object literals, arrays, and functions inside of object literals and arrays. Aim to make your code flat.

Function calls

There are three kinds of function calls:

Single line: a(1, 2), which work as expected.

Multiline, where the first argument is indented twice, and subsequent arguments are indented a single time:

a(
    1
  , 2
  , 3
  , {
        x: 1
      , y: 2
    }
  , function() {
      return 3
    }
)

Contiguous, where one or more arguments are multiple lines, but each argument starts on the same line as the previous argument's "end" line:

setTimeout(function() {

}, 100)

func(0, {
    a: b
}, 1, 2, {
    c: d
})

Classes

Creating a class

Classes MUST be function statements.

Classes MUST be succeeded by a var statement assigning cons and proto to the prototype and constructor of the class, respectively.

If subclassing, the constructor MUST call the parent call as the first step of the constructor function.

If subclassing, the prototype of the constructor must be set to Object.create(ParentClass.prototype). The constructor field of the prototype should be reset to the constructor.

All attributes a class instance will ever have MUST be initialized in the constructor.

Constructors MUST NOT be asynchronous. Remember, RAII.

function MyClass() {
  Parent.call(this)

  this.attr = 0
}

var cons = MyClass
  , proto = cons.prototype = Object.create(Parent.prototype)

proto.constructor = cons

proto.some_method = function class_method() {

}

Working with this

If ever a function needs to refer to this within a different scope, introduce a variable self as the first operation in the enclosing scope, assign it to this, and change all existing references from this to self. DO NOT mix self and this within a function.

proto.fetch_from_url = function(url, ready) {
  var self = this

  request.get(url, function(err, data) {
    data.value = self.some_value

    return ready(null, data)
  })
}

Avoid writing standalone functions that manipulate this. Try to keep all context necessary to understand the workings of a function within a page of that function.

Modules

Require.JS

We currently use require.js, though we're moving to CommonJS/Browserify. Require.js modules should use the following style:

define(function(require) {
  var imported = require('./some/other/file')

  return exported

  function exported() {

  }
})

Importing modules

Never compute module names. Module imports MUST be statically analyzable. Module imports SHOULD use relative paths when referring to UA code:

var z = require('./some_other_module')
  , x = require('./yyy')

Exporting values from a Module

Prefer to export single functions with a clearly defined API.

// in require.js-style modules:
define(function(require) {
  return action

  function action(inputs) {

  }
})

Directories

Top level directories currently serve as a sort of poor man's tagging feature to group modules with related concerns. New top level directories SHOULD not be added.

The directories are as follows:

  • utils/: Low level utilities with very simple inputs and outputs. String -> Object sorts of things should live here.
  • modules/: Higher level, more complex transformations of inputs and outputs. Stream and Eventemitter subclasses should live here. Modules in this directory SHOULD not use jQuery or the DOM.
  • widgets/: Usually backed by a module -- these modules should represent components that may be passed a root element and perform side effects on it. Anything that interacts with the DOM should live here.
  • page/: Entry points for single page JS applications.
  • behaviors/: Modules that will execute on every page of the site.
  • thirdparty/: All thirdparty JS code should live here. Thirdparty modules should be added with a comment at the top noting what commit hash they were fetched from, and a URL to find the original code.
  • templates/: Frontend JavaScript templates.

Module size

Non page-specific modules should ideally top out at 200-300 LOC. Once a module starts to exceed that number of lines a few things SHOULD happen:

  1. Create a new directory with your module's name. Move your existing module into that directory as index.js. Change references to your module to yourmodule/index.

  2. Separate self-contained bits of index.js into modules within yourmodule/.

  3. If several modules are repeating a certain pattern, consider refactoring those modules (and your module) to use a common top-level module.

Every module SHOULD have tests and documentation. Tests live in airship/jstests/ and are written using drive.js, though we're planning on moving towards tape.

JS Idioms

Turn arguments into an array

[].slice.call(arguments)

Push arguments onto a list

list[list.length] = item OR list.push(item)

Concatenate a list

list = list.concat(other_list)

Templates and HTML

General Template Style

  • Do NOT use HTML comments. Prefer {# django style oneline comments #} for small comments, and {% comment %}{% endcomment %} for larger comments.

  • Filter and tag nodes SHOULD have surround their contents with a single space:

{{ this.is.good }}
{% this is also good %}{% endthis %}
  • Do NOT use inline script tags, even when it seems expedient.

  • When one needs hooks into HTML, add name, rel, or data-* attributes to the appropriate elements. Prefer name for known target elements, rel for buttons or links that otherwise perform an action, and data-* attributes for other kinds of elements. One SHOULD list the used selectors in a {% comment %} block at the top of the file, along with what they're used for, for the benefit of future developers and designers.

  • 4 space tabs for each element. Template tags introduce a new tab level internally. Match surrounding style as closely as possible.

Selecting Elements

Constant use of the $(".selector") function is an anti-pattern. Always store a reference to the elements you are manipulating as a variable. Variables holding element values SHOULD be suffixed with _el (or simply named el, if unique).

Adding event listeners

Prefer event delegation over adding listeners to individual elements. This means you can have a single, top-level element that attaches most behaviors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment