How to Prevent Accidental Code Errors with ESLint, Prettier, and Husky
Adam Przewoźny
Posted on February 13, 2022
Originally written by Jakub Krymarys
Any software engineer, regardless of their level of advancement and years of experience, may have a worse day and accidentally introduce changes that will result in bugs or simply won’t fit into good code development practices.
Fortunately, there are several ways for you to protect your JavaScript project against such cases.
I assume the first thing that comes to your mind is using various types of tests. Of course, they are the most effective method, but we’ll be dealing with something else in this article.
Instead of testing the functionality of the application to keep your software project safe from accidental developer mistakes, we’ll focus on the code itself. To do this, we’ll use:
ESLint for analyzing JavaScript code to find potential bugs and bad practices,
Prettier to format the code in accordance with the adopted standard,
Husky to allow us for integration with Git hooks that will in turn allow us to automate the two previous tools.
All of these tools work well with any Node.js project. Since I’d like to give you specific examples of configs, I’ll be discussing these using a sample “pure” React.js project created with the Create React App (CRA).
Code analysis with ESLint
Let’s start with ESLint. This is a so-called linter, which is a tool that statically analyzes JavaScript code to find any potential problems. It can react to each of them in two different ways—by marking it as either a warning (and displaying an appropriate message in the console), or as an error (in this case, not only will we see the message, but the compilation of the code will also fail).
If you’ve worked with React, you’ve probably seen more than one warning or error in the browser console. Some of them are the effect of ESLint. It’s integrated with the application that we create using the CRA. However, it has a very minimalist configuration there.
{
(...)
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
(...)
}
Default ESLint config in the package.json
file for a React.js application created with the CRA
However, if for some reason you do not have ESLint in your project, you can easily add it using the command npm install eslint --save-dev
.
To make the linter a real “lifesaver” of our project, we need to extend this basic configuration a bit. By default, it only has a set of React-specific core rules and doesn’t check the JS syntax itself.
I suggest starting with the configuration recommended by the ESLint team: "eslint:recommended"
.
The exact contents of this set can be seen here.
How do I extend the ESLint configuration?
The linter configuration can be extended in two ways:
- by modifying the appropriate
eslintConfig
field inpackage.json
; - by creating
.eslintrc
, a special configuration file in the main project folder.
Both work equally well, but as a fan of breaking everything down into as many little chunks as possible, I recommend separating the config into a new file. Divide and conquer!
Remember, however, that when you create the configuration in a separate file, you should remove the eslintConfig
from package.json
.
The .eslintrc
configuration file consists of several sections:
{
"extends": [(...)], // which configurations we want to extend
"rules": { (...) }, // changing the rules or changing the meaning of the existing ones
"overrides": [ // overriding rules for specific files/file groups
{
"files": [(...)], // which we define here, for example all TS files
"rules": { (...) } // rules are overridden here
}
]
}
Our basic configuration should look something like this:
{
"extends": [
"eslint:recommended",
"react-app",
"react-app/jest"
]
}
Note: it’s very important that "react-app"
and "react-app/jest"
remain in "extends"
of our project (because they “check” React mechanisms)!
This is a good starting point for neat and conscious use of your linter. However, you can change this configuration (using the official documentation) or simply make your own rule modifications (which are also well documented in the ESLint documentation).
When should I add my rules to ESLint?
Certainly not immediately. I’d suggest starting with the recommended set of rules and introducing any changes only when one is missing or one of them contradicts the requirements of your project.
Of course, don’t forget to discuss it thoroughly within the team so that all of its members are unanimous and understand why this rule has been changed.
To add your own rule or change how the existing rule works, we first need to find it in the rule set.
Then we can add it to the config rules section (if we want it to apply to the entire project) or to the overrides section (if it’s supposed to work only with a certain group of files) with one of the three expected values given below, which will determine how the linter will respond to the code fragments falling under it:
- 0 or “off”—the rule will be disabled,
- 1 or “warn”—the linter will respond with a warning,
- 2 or “error”—the linter will respond by throwing an error and aborting compilation.
For example: "no-console": "error"
will block application compilation (it will throw an error) as soon as the linter detects console.log
.
{
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest",
"eslint:recommended"
],
"rules": {
"no-console": "off"
}
}
A sample configuration extended by the "no-console"
rule
How do I run the linter?
In our project, the linter can be run in several ways.
As soon as you restart the application, the new configuration should be taken into account and the linter will check the code according to it every time you compile it.
Of course, we can also analyze the entire project ourselves. There are several ways to do this.
The easiest one is to add the appropriate script to the package.json
file, then run it with the nam run lint
command.
{
(...)
"scripts": {
(...)
"lint": "eslint --fix './src/**/*.{js,jsx}'"
}
(...)
}
You may also use the npx
tool:
npx eslint --fix './src/**/*.{js,jsx}'
As you may have noticed, I added the –fix
flag to the ESLint command. Thanks to it, the linter will automatically repair some of the errors it encounters, which will further improve the entire process.
Code formatting with Prettier
Another thing to ensure in your project is that your code is automatically formatted according to a centralized configuration. Usually, each developer on the team has slightly different preferences, which is totally fine, though it can lead to minor or major problems.
By the way, Prettier was created as a way to stop all discussions about which formatting is better. Its formatting style is a result of long debates, as it’s meant to be a compromise between the requirements of all developers.
One of these problems will surely be confusion in pull/merge requests. Suddenly, it may turn out that we have “modified” many more lines of code than was originally intended to result from the changes related to the new functionality or fixes. It’s only our formatter that ordered the code “in its own way.”
Of course, this doesn’t change the functionality of the application, but it does introduce unnecessary confusion. It won’t be immediately clear to the person conducting the code review which parts of the code they need to check.
To introduce standardized code formatting at the project level, we will use Prettier.
So let’s move on to its installation itself. Unlike ESlint, this tool is not built into the CRA, but as is the case with all NPM packages, the installation is very simple and limited to the following command:
npm install --save-dev prettier
Then we’ll configure our formatter. To do this, we will use two files: .prettierrc.json
that contains the configuration and .prettierignore
where we can list files and folders that Prettier should skip (this file works in the same way as .gitignore
).
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 120
}
Sample .prettierrc.json
configuration
node_modules
build
Sample .prettierignore
configuration for React
If you’re adding Prettier to an existing project, note that the first time you use it, it will likely format most of the files in the project. Therefore, it’s a good idea to do it right away, in a dedicated commit.
Just remember to notify the whole team about the need to download the latest version of the code. Otherwise, you will face clashes between the code with the new configuration and the out-of-date versions of the project.
Like with the linter, we can start Prettier in two ways:
- Via a script in
package.json
that we run withnpm run prettier
{
(...)
"scripts": {
"prettier" : "prettier --write ."
}
(...)
}
- Using the
npx
tool
npx prettier --write .
We also need to adjust the ESLint configuration by adding the information that we’ll also be using Prettier in the project. Otherwise, the two systems may clash.
To do this, you must first install the Prettier-specific ESLint config with the command:
npm install --save-dev eslint-config-prettier
Then you add it to the “extends” section in the .eslintrc
file. It’s really important to add it as the last item, since it has to override a few entries from the previous set of rules.
{
(...)
"eslintConfig": {
"extends": [
"eslint:recommended",
"react-app",
"react-app/jest",
"prettier"
],
(...)
}
}
Tool automation with Husky
Finally, let’s automate running both of these tools to improve our workflow. We’ll use Husky for that. It’s a tool that enables integration with Git hooks… so little, and yet so much!
Git hooks are a way to run any scripts in response to various actions related to the Git version control system.
To make it as simple as possible, we’ll use the lint-staged project, which will streamline this process and introduce one more important optimization.
What is lint-staged? Why use the tool?
While reading the paragraphs on ESlint and Prettier, you may have started to wonder whether such a solution would slow down your project. After all, continuous formatting and analyzing several hundred—or even several thousand!—lines of code in several files can definitely take a long time, which can be irritating with each commit.
Moreover, it’s easy to see that most of these files won’t even be modified, so it will be a waste of time to constantly analyze them.
Fortunately, there is a way for that: the lint-staged tool. It allows for a fabulously simple integration with the Git hook pre-commit, which is quite enough to start with.
We install it in a slightly different way than the rest. This time, we’ll use the following command:
npx mrm@2 lint-staged
To read more on how exactly this tool works, I encourage you to browse the GitHub page of the project.
This command—or actually the script we run with it—does a few things that are important to us:
- install Husky,
- install lint-staged,
- configure lint-staged based on whether we already have ESlint and Prettier installed.
After installing lint-staged, we need to add the configuration of this tool to package.json
. It consists of JSON, which takes the name of a file (or a regex that defines a group of files) as a key. What it takes as a value is a string with a command to be executed or an array of strings if there are several such commands.
If you created your application via the CRA, it’s most likely that lint-staged only configured Prettier for you. Therefore, we’ll add the linter to the lint-staged configuration, as in the example below.
{
(...)
"lint-staged": {
"*.{js,jsx}": "eslint --fix src/",
"*.{js,jsx,json,css,md}": "prettier --write"
}
(...)
}
Pay attention to what files these two tools should handle. ESLint only works with JavaScript, while Prettier works with many other formats.
Benefits of using Husky, Prettier, and ESLint to increase the code security of your project
A dozen or so minutes devoted to the configuration we’ve presented above will save you a lot of stress and countless hours that you’d spend debugging a problem that could be caught by the linter at the stage of writing the code.
Add to that all the time you’d spend analyzing Git changes, resulting only from the differences in the formatter configuration of the IDE among individual developers on the team—changes that don’t affect the functionality of the application, and are merely code formatting.
In addition, your code will simply be nicer and in line with good practices, which will definitely make it easier to work with.
Further reading on protecting your code with ESLint, Husky, and Prettier
A deeper understanding of how ESLint works and why it marks certain constructs as warnings or bugs will lead to a better understanding of JavaScript and introduce you to some good rules to follow when writing projects in this crazy language.
As you may have guessed, what I’ve discussed in this article is just the tip of the iceberg, especially in the context of ESLint itself and the possibilities this tool offers. Here are some interesting links that will allow you to broaden your knowledge on this topic:
- Using ESLint with TypeScript
- All the rules supported by ESLint -Suggestion to add the integration described in this article to the CRA
- Basic ESLint configuration in the Create React App
- Linting messages in commits
- The origins of Prettier
- ESLint --fix flag
Plus the pages of the tools used here:
Final thoughts on Prettier, Husky, and ESLint
Thanks for reading our article on protecting your project against accidental mistakes by using ESLint, Prettier, and Husky. It should save you a lot of trouble in the future.
We have several other technical guides written by experts on a variety of subjects that’ll help you overcome multiple development challenges. Here are some examples:
Posted on February 13, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.