2018-03-07 13:32:38 -05:00
|
|
|
# JavaScript style guide
|
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
We use [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript) and it's accompanying
|
|
|
|
linter to manage most of our JavaScript style guidelines.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
In addition to the style guidelines set by Airbnb, we also have a few specific rules
|
|
|
|
listed below.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
|
|
|
> **Tip:**
|
|
|
|
You can run eslint locally by running `yarn eslint`
|
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Avoid forEach
|
|
|
|
|
|
|
|
Avoid forEach when mutating data. Use `map`, `reduce` or `filter` instead of `forEach`
|
|
|
|
when mutating data. This will minimize mutations in functions,
|
|
|
|
which aligns with [Airbnb's style guide](https://github.com/airbnb/javascript#testing--for-real).
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
users.forEach((user, index) => {
|
|
|
|
user.id = index;
|
|
|
|
});
|
|
|
|
|
|
|
|
// good
|
|
|
|
const usersWithId = users.map((user, index) => {
|
|
|
|
return Object.assign({}, user, { id: index });
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
## Limit number of parameters
|
|
|
|
|
|
|
|
If your function or method has more than 3 parameters, use an object as a parameter
|
|
|
|
instead.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
function a(p1, p2, p3) {
|
|
|
|
// ...
|
|
|
|
};
|
|
|
|
|
|
|
|
// good
|
|
|
|
function a(p) {
|
|
|
|
// ...
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
## Avoid side effects in constructors
|
|
|
|
|
|
|
|
Avoid making asynchronous calls, API requests or DOM manipulations in the `constructor`.
|
|
|
|
Move them into separate functions instead. This will make tests easier to write and
|
|
|
|
code easier to maintain.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
class myClass {
|
|
|
|
constructor(config) {
|
|
|
|
this.config = config;
|
|
|
|
axios.get(this.config.endpoint)
|
|
|
|
}
|
|
|
|
}
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
// good
|
|
|
|
class myClass {
|
|
|
|
constructor(config) {
|
|
|
|
this.config = config;
|
|
|
|
}
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
makeRequest() {
|
|
|
|
axios.get(this.config.endpoint)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const instance = new myClass();
|
|
|
|
instance.makeRequest();
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Avoid classes to handle DOM events
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
If the only purpose of the class is to bind a DOM event and handle the callback, prefer
|
|
|
|
using a function.
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
class myClass {
|
|
|
|
constructor(config) {
|
|
|
|
this.config = config;
|
|
|
|
}
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
init() {
|
|
|
|
document.addEventListener('click', () => {});
|
|
|
|
}
|
|
|
|
}
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
// good
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
const myFunction = () => {
|
|
|
|
document.addEventListener('click', () => {
|
|
|
|
// handle callback here
|
|
|
|
});
|
|
|
|
}
|
|
|
|
```
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Pass element container to constructor
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
When your class manipulates the DOM, receive the element container as a parameter.
|
|
|
|
This is more maintainable and performant.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
class a {
|
|
|
|
constructor() {
|
|
|
|
document.querySelector('.b');
|
|
|
|
}
|
|
|
|
}
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
// good
|
|
|
|
class a {
|
|
|
|
constructor(options) {
|
|
|
|
options.container.querySelector('.b');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Use ParseInt
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
Use `ParseInt` when converting a numeric string into a number.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
Number('10')
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
// good
|
|
|
|
parseInt('10', 10);
|
|
|
|
```
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## CSS Selectors - Use `js-` prefix
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
If a CSS class is only being used in JavaScript as a reference to the element, prefix
|
|
|
|
the class name with `js-`.
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```html
|
|
|
|
// bad
|
|
|
|
<button class="add-user"></button>
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
// good
|
|
|
|
<button class="js-add-user"></button>
|
|
|
|
```
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Absolute vs relative paths for modules
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
Use relative paths if the module you are importing is less than two levels up.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
// good
|
|
|
|
import GitLabStyleGuide from '../GitLabStyleGuide';
|
|
|
|
```
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
If the module you are importing is two or more levels up, use an absolute path instead:
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
```javascript
|
|
|
|
// bad
|
|
|
|
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
// good
|
|
|
|
import GitLabStyleGuide from '~/GitLabStyleGuide';
|
|
|
|
```
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
Additionally, **do not add to global namespace**.
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Do not use `DOMContentLoaded` in non-page modules
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
Imported modules should act the same each time they are loaded. `DOMContentLoaded`
|
|
|
|
events are only allowed on modules loaded in the `/pages/*` directory because those
|
|
|
|
are loaded dynamically with webpack.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Avoid XSS
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many
|
|
|
|
vulnerabilities.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
## Disabling ESLint in new files
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
Do not disable ESLint when creating new files. Existing files may have existing rules
|
|
|
|
disabled due to legacy compatibility reasons but they are in the process of being refactored.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
Do not disable specific ESLint rules. Due to technical debt, you may disable the following
|
|
|
|
rules only if you are invoking/instantiating existing code modules.
|
2018-03-28 10:43:20 -04:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
- [no-new](http://eslint.org/docs/rules/no-new)
|
|
|
|
- [class-method-use-this](http://eslint.org/docs/rules/class-methods-use-this)
|
2019-02-22 08:17:10 -05:00
|
|
|
|
2019-03-13 10:20:17 -04:00
|
|
|
> Note: Disable these rules on a per line basis. This makes it easier to refactor
|
|
|
|
in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`.
|