In this tutorial we will use ESLint to enforce coding style conventions in a React App. We will go over some very basic rules for enforcing style and error checking. We will also integrate husky
and lint-staged
: two packages that pair well and help us avoid shipping bad code accidentally.
A linter is a software development tool that analyzes code for bugs and style errors with the goal of ensuring code consistency. We define a collection of rules or conventions we want our code to follow, and the linter will parse through files looking for inconsistencies and raising flags wherever the defined conventions are broken. Linters can also identify common bugs, like undefined or unused variables.
Hopefully, you can see the value of enforcing code consistency for the individual programmer, but a linter especially shines when it comes to teams because it enforces consistent style among groups of developers working on the same codebase.
ESLint is a linter that analyzes JS code. If you are familiar with linters from previous work, you know that many of them are opinionated. They come configured with certain rules out of the box. ESLint does none of that! In ESLint you define your own rules. Thankfully, there are many ready-made rulesets available that you can plugin and extend.
To help get a better understanding, think of using an existing ESLint ruleset just like importing a CSS framework into your project. The CSS framework defines rules for how your UI should look in the same way that a ruleset contains the coding style conventions you want to follow. You can override and tweak parts of the CSS framework to fit your needs with a custom stylesheet, likewise, you can overwrite or extend a ruleset you’ve adopted by writing custom rules.
Let’s get going…
Install eslint
and eslint-plugin-react@latest
. They’re both dev dependencies. The latter is required because we are going to lint a React project.
yarn add -D eslint eslint-plugin-react@latest
Then run
./node_modules/.bin/eslint --init
ESLint will provide a command line interface for you to select options and answer a few questions. This will initialize a new ESLint configuration file automatically.
Here are the options I gave it:
This resulted in the following configuration file (yours may differ):
# .eslintrc.js
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"never"
]
}
}
This is a basic configuration for React projects.
A couple items worth noting:
extends
tells the configuration which ruleset we are adopting. ESLint provides eslint:recommended
to get you started. You can choose to use it, adopt a different ruleset, or remove this field entirely.
Further down we have an object under the rules
property in our config object.
...
"rules": {
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"never"
]
}
...
Based on the answers I provided to ESLint during the --init
sequence, it created these rules for me inside the configuration file. Take the quotes rule, for example: I told ESLint I prefer "
to '
for strings so it added a rule to raise an error
flag during parsing if any JS code contains strings without double quotes.
Rules listed here take precedence over any rules already defined in the ruleset.
To add additional rules we provide the rule name as a key and the error level (either “error”, “warn”, or “off”) as the value inside the rules
object. Optionally, some rules allow additional parameters that modify and flavor how they’re applied.
Take a second to browse through the rules documentation and get a sense of available rules and additional parameters. If you’re curious what rules are included in the eslint:recommended
ruleset, you can find them here too.
To run ESLint command on a folder we do the following:
./node_modules/.bin/eslint --ext .jsx,.js ./src --fix
Here we’ve specified the target of our ESLint command - any files inside of ./src
that have the js
or jsx
extension. We’ve also included the --fix
flag, telling ESLint we’d like it to fix any errors found if it can. Cool!
We can include this command inside our scripts
in package.json
under an alias to facilitate running this command in the future.
Go ahead and run the command now. Assuming your project is inside the ./src
directory, you should see your linter parsing any javascript files and outputting errors and warnings. Take a second to read the results and then go and fix your code so it complies with your new style rules! When you’ve fixed everything, run the linter again to double check all has been resolved!
So far we’ve taken a simple ESLint setup using the eslint:recommended
ruleset and created an alias to run ESLint on all are javascript files inside our ./src
folder.
There is a little problem with this approach! ESLint is completely useless if we forget to use the command before pushing code to our project repository!
Furthermore, we do not want to check all our code for styling and errors, but only the code we’re getting ready to commit.
There is a solution for this using hooks!
Install lint-staged
and husky
as dev dependencies.
yarn add -D husky lint-staged
lint-staged
will only run linter on files that are being staged for git commit. This is super useful because we often don’t want to run our linters on all the files in our project!
Add lint-staged
configuration.
# package.json
...
"lint-staged": {
"./src/**/*.{js,jsx}": [
"eslint --fix",
"git add"
]
},
...
Note we’ve given lint-staged
config inside package.json
a glob
and array. We’re telling lint-staged
, “Among our git staged
files only, find any files matching this glob
and run the following commands on those files.” We’ve given two commands: eslint --fix
and git add
. lint-staged
will run our linter on files in staging that have the extension js
or jsx
inside of ./src
, --fix
any files, and immediately git add
those files back to staging.
For files eslint
is unable to --fix
, any errors will be printed to the screen for the developer to manually fix and git add
them.
Try running lint-staged
./node_modules/.bin/lint-staged
This still doesn’t fix the issue of someone forgetting to run the linter before commiting and pushing code!
For that we pair lint-staged
with husky
.
Husky allows you to implement hooks into our git workflow! Now that we have our ESLint setup and use lint-staged to only run against our staged files, we can add a pre-commit
hook to always run lint-staged
prior to actually commiting code.
# package.json
...
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"./src/**/*.{js,jsx}": [
"eslint --fix",
"git add"
]
},
...
Notice how easy it is to define husky
hooks in our package.json
. We’re telling husky to run the lint-staged
command prior to any commit.
Our lint-staged
command is defined just below that. We define a glob
pattern to match the files in staging we want to target, then we’re telling lint-staged
to run a series of commands. The first will run ESLint with the --fix
flag against those files. If all goes well, the second command will add those files back to staging after applying the fix and the commit will succeed. In cases where eslint
is unable to fix all issues, the commit will be haulted and the developer will need to resolve them manually and attempt to commit again.
Very nice!
In this tutorial we learned about linters. ESLint is a javascript linter that allows you to use custom rulesets. Linters are especially useful for work in groups because it ensures a consistent style throughout the codebase. lint-staged
and husky
are two packages that pair well and complement eslint
, preventing bad code from being pushed and committed using hooks to intercept the git workflow.
Some rulesets and configuration you may want to explore:
Thank you for reading!