TL;DR: Use jsl. Complete the primer. Be excellent.
- 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() {
}
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 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).
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>'
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) {
}
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()
}
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
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).
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
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 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.
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)
})
}
We use comma-first. Here's what you should know:
Variable declarations MUST be ordered by:
- has assignment to no assignment
- 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
]
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.
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 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() {
}
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.
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() {
}
})
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')
Prefer to export single functions with a clearly defined API.
// in require.js-style modules:
define(function(require) {
return action
function action(inputs) {
}
})
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.
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:
-
Create a new directory with your module's name. Move your existing module into that directory as
index.js
. Change references to your module toyourmodule/index
. -
Separate self-contained bits of
index.js
into modules withinyourmodule/
. -
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.
[].slice.call(arguments)
list[list.length] = item
OR list.push(item)
list = list.concat(other_list)
-
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
, ordata-*
attributes to the appropriate elements. Prefername
for known target elements,rel
for buttons or links that otherwise perform an action, anddata-*
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.
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).
Prefer event delegation over adding listeners to individual elements. This means you can have a single, top-level element that attaches most behaviors.