Comment on page
ESLint and Prettier
ESLint and Prettier for Your React Apps
Building high-quality apps is hard for many reasons:
- The more your codebase grow, the more it becomes hard to maintain.
- Developers have different way to code, which can confuse you and slow you down.
- JavaScript is a language that seems easy to learn at first, but it’s not that easy. You can fall into common pitfalls. The same goes with React (or any other front-end library/framework).
- Etc.
That’s why nowadays, there are many tools that allow us to write quality code and make sure our apps don’t break. You may think of testing tools like Jest, React Testing Library or Cypress at first, but before even writing tests, you can add a first layer of safety in your app with a linter.
A linter analyses statically your code. It means it checks out the code without executing it. But why use a linter you might think? Because you can build an app while writing awful code. Thus, it’s essential to have a tool that spots for you common errors and bad practices.
The best part of a linter is that it can find potential errors in your code that don’t look like them. As an example, here is a for-loop that, at first glance, will log numbers from 0 to 4:
for (var i = 0; i < 5; i++) { setTimeout(() => console.log(i), 1000)}
However, it logs five times
5
. Curious, right? The reason behind this is that the var
keyword is function-scoped. As there are no functions here, i
will be a global variable. Thus, by the time the first setTimeout
’s callback finishes runnig, the loop has always been executed and so, the last value of i
is 5.You can fix this behavior by using the
let
keyword, which is block-scoped:for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), 1000)}
This time, each iteration has its own
i
variable and is not declared globally.It’s almost impossible to guess what would happen here if you haven’t run into it at least once in your developer’s life. But a linter can! It would see you’re using the
var
keyword in a for-loop and that inside this loop, you’re using a function that references variables outside of its scope which seems like a bad practice. Thus, it would throw an error at you and you would know what to do.Now that you saw why linting your code is important, let’s discover ESLint. It’s an open-source project originally created by Nicholas C. Zakas which provides a pluggable linting utility for JavaScript.
Basically, ESLint parses your code, analyses it and run linting rules that may trigger warning or errors to let you know if your code is right or wrong. For example, one popular rule is no-undef: it makes sure your code doesn’t have any undeclared variables. With
no-undef
, the following code would be incorrect:// ❌ Oops, what is add?
const result = add(1, 2)
Indeed. ESLint wouldn’t know what
add
refers to and would throw an error at you. You need to explicitly declare add
to correct it:// ✅ Correctfunction add() {}
const result = add(1, 2)
You can install ESLint in two different ways:
- 1.Globally:
npm install eslint -g
- 2.On a project:
npm install eslint --save-dev
I recommend that you install ESLint directly in a project. Indeed, if you install it globally, you could make ESLint clashing with your other projects or with other developers (and then you could run into one these “It works on my machine!” problems).
Note: You can also use the ESLint extension for VSCode. It’ll deeply improve your developer experience by highlighting errors and warnings directly in your code.
What’s great with ESLint is that it’s highly configurable. All you have to do is to create a
.eslintrc
file at the root of your project and then you can run ESLint on any files you want.Note: it is also possible to define an ESLint configuration inside a
package.json
file. For that, just put your configuration object in a eslintConfig
key.This configuration file takes the form of a JSON object. You can specify many options to do so:
parserOptions
The parserOptions: tells ESLint how you want it to parse your code. Most of the time, you specify which ECMAScript version (ES6, ES2018, etc.) you are using with the
ecmaVersion
key. This is also where you tell ESLint if you use ECMAScript modules (import
and export
) by setting sourceType
to module
.environment
environment
defines predefined global variables you’re using. For example, Jest allows you to use global variables in your test files such as describe
, it
or test
. However, ESLint won’t know what these variables refer to if you haven’t explicitely told it. For that, set jest
to true
.globals
You might be sometimes declaring global variables by yourself. Then, you need to add them in this object.
plugins
Basically, plugins are a set of ESLint rules related to a same subject. As an example, eslint-plugin-react contains many rules related to React.
Caution: you have to install the plugin as a dev dependency if you want your rules to work properly.
rules
These are the linting rules we were talking about before. Each rule has a value that is either
off
if you want to disable the rule, warn
if it should show a warning or error
if it should throw an error.extends
Allows you to extend your configuration file from others configurations. A popular one is
react-app
provided by Create React App. That’s also in extends
that you can use popular style guides such as the one of Airbnb, Google or Standard.Inline configuration
You can modify ESLint’s configuration inline with special comments. As an example, you could do the following to the incorrect code related to
no-undef
:// eslint-disable-next-line
const result = add(1, 2)
With that, ESLint will stop complaining.
Use it with caution though! It’s not a good idea to disable ESLint every time it spots errors, it often means something’s wrong with your code.
ESLint has a CLI to lint files. You can find all the options on ESLint’s docs. The one you will use the most is the
--fix
option which fixes the files when ESLint can. For example the following command lints every file present in the codebase:eslint .
Then you can include these scripts in your
package.json
file:{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint --fix ."
}
}
You can use these scripts using the
npm run
command. One will just lint the codebase while the other one will lint and try to fix whatever it can fix.Here is an example of a
.eslintrc
file:.eslintrc{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"no-console": "error"
}
}
In this configuration, we:
- Extend the
eslint:recommended
configuration - Tell ESLint we’ll use CommonJS global variables (
require
instead ofimport
) - Specify we’ll write our JavaScript with the 2018 version of ECMAScript syntax
- Disallow the use of
console.log
.
Here is another ESLint configuration:.eslintrc
{
"env": {
"browser": true,
"jest": true,
"es6": true },
"plugins": ["import"],
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module" },
"rules": {
"no-console": "warn",
"no-eval": "error",
"import/first": "error"
}
}
This ESLint config meets the following requirements:
- Includes the recommended ESLint rules
- Includes
jest
’s,es6
’s andbrowser
’s global variables - Disallows the use of
eval
- Warns the user if it uses
console.log
- Indicates the parser you’re using ES2018’s syntax
If you’re curious, you can also check out what the configuration of create-react-app looks like under the hood, it’s really interesting!
You now know how to enforce some guidelines in your project and avoid common pitfalls with ESLint, Great! Now what? Formatting your code.
Imagine you work in a team of three developers on the same project:
- One developer always uses single quotes instead of double quotes.
- Another one uses indentation of four spaces and make its line very short.
- However, you prefer indentation of two spaces and you usually use double quotes.
Then, what happens if you need to collaborate on a same project? Well:
- Your code is a mess.
- You waste your time rewriting others code with your own code style
How can you fix it? Use a common code style. How to do it? With Prettier: an opinionated code formatter. It obviously supports JavaScript but also many other languages like JSX, CSS, JSON or Vue.
You can install Prettier in two different ways:
- 1.Globally:
npm install prettier -g
- 2.On a project:
npm install prettier --save-dev
Just like ESLint, I recommend that you install Prettier in a project instead of globally because it could be clashing with other projects.
Note: You can also use the Prettier extension for VSCode. It’ll deeply improve your developer experience by formatting your code from VSCode.
Prettier as said above is an opiniated code formatter. They will format your code by default in a way that you may not like. But they allow you to modify a few things. You can configure it via a
.prettierrc
file or via a prettier
key in your package.json
file. As an example, here are some rules you can configure:singleQuote
:true
if you want to use single quotes,false
otherwise.printWidth
: a number that specifies the line length that the printer will wrap on.semi
:true
if you want to print semicolons at the end of every statement,false
otherwise.
Prettier, just like ESLint, has a CLI to format files. You can find many options on Prettier’s docs. The one you will use the most is the
--write
option which is similar to the --fix
option of ESLint. For example the following command formats every .js
or .jsx
file located in a src
folder:prettier --write src/*.(js|jsx)
Then you can use this CLI option to include a format script in your
package.json
file:{
"scripts": {
"format": "prettier --write \"**/*.+(js|jsx|json|css|md)\""
}
}
Now, you just have to run
npm run format
to format your entire codebase.Thus, you can integrate Prettier in your ESLint configuration by installing the following:
npm install --save-dev eslint-config-prettier eslint-plugin-prettier
eslint-config-prettier
disables rules that conflict with Prettier while eslint-plugin-prettier
adds the rule that format content using Prettier. You can enable this configuration by using the recommended one:{
"extends": ["plugin:prettier/recommended"]
}
Here is an example of a Prettier configuration:.prettierrc
{
"printWidth": 85,
"arrowParens": "always",
"semi": false,
"tabWidth": 2
}
Basically, it means that Prettier:
- Will wrap on line that exceed 85 characters.
- Will always use parenthesis around arrow functions, even if there is one argument.
- Won’t print semicolons at the end of every statement.
- Will use two spaces per indentation-level.
Let’s take an example of a simple yet messy React component and visualize the effects of Prettier by trying out its online playground:
That’s instantly more readable and trust me, you would have wasted your time formatting this by hand. So using a formatter is essential.
You can lint and run a codebase, great. But does it mean that now you should lint and format your code whenever you want? No!
If you use Git in your project - and who doesn’t - you can make use of Git hooks to run ESLint and Prettier before each commit or each push. Then, your code is constantly linted and formatted before deploying to production 😉
We’re not talking about the dog here. We’re talking about the tool that allows you to setup Git hooks very easily. Let’s say your
package.json
has the following scripts and you want to run lint
and format
at each commit:{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint .",
"format": "prettier --write \"**/*.+(js|jsx|json|yml|yaml|css|md|vue)\""
}
}
Then, you can add Git hooks in two easy steps:
- 1.Install husky as a dev dependency:
npm install --save-dev husky
- 1.Insert the following in your
package.json
:
{
"husky": {
"hooks": {
"pre-commit": "npm run lint && npm run format"
}
}
}
That’s all. At each commit, the command associated to
pre-commit
will be run and so your codebase will be linted and formatted.pre-commit
is by far the most used Git hook but you can also use other hooks like pre-push
or post-commit
.Bear with me, this tool will be the last to setup. The more your codebase will grow, the more linting and formatting will be longer. Imagine you have more than 1000 components, you don’t want to wait for ESLint to lint all your codebase. In the end, the only files that need to be linted are the one that just have been modified. For that, you can use lint-staged. Indeed, this tool will make sure you’ll lint files that will be committed, that is to say, the ones that are staged.
To install it, you have to run the following command:
npm install --save-dev lint-staged
Then, for the configuration, you have to either add a
lint-staged
key to your package.json
or a .lintstagedrc
file. According to lint-staged docs, the configuration should be an object where each value is one or more commands to run and its key is a glob pattern to use for these commands. Here is an example:{
"lint-staged": {
"*.+(js|jsx)": ["eslint --fix", "git add"],
"*.+(json|css|md)": ["prettier --write", "git add"]
}
}
This configuration will allow you to run ESLint and fix both
.js
and .jsx
files. In the same time, it will run Prettier on .json
, .css
and .md
files. For both keys, we use git add
to add the potential modified files to the staging area before committing.In this part, we are going to see how to setup Prettier, husky and lint-staged on an app generated by create-react-app. More precisely, we will:
- 1.Add the Prettier’s recommended configuration to ESLint and add the following formatting rules (see Prettier’s options):
- The
printWidth
must be set to 90 - There should be no spaces between brackets in object literals.
- There should be trailing commas where valid in ES5
- 1.Add husky and lint-staged to lint and format only staged files:
- There should be a
pre-commit
hook that runslint-staged
- Non-javascript files (CSS, JSON, HTML, etc.) should be formatted using Prettier
- JS and JSX files should be linted (and reformatted thanks to Prettier’s plugin for ESLint)
Let’s create a React app. Go to the folder you usually work in and run:
npx create-react-app react-eslint-prettier-app
Note: If you’re not able to use npx, it probably means you’re using an outdated version of npm (< 5.2). To solve it, either update npm or install
create-react-app
globally using npm install -g
.First, let’s see how to setup Prettier. For that we need to install in our
devDependencies
Prettier and its plugins:npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier
As a reminder,
eslint-config-prettier
will disable all ESLint formatting rules that may conflict with Prettier’s rules. eslint-plugin-prettier
is the plugin that will add Prettier’s formatting rules.Then, let’s tell ESLint we’ll use Prettier’s recommended configuration:
{
"eslintConfig": {
"extends": ["react-app", "plugin:prettier/recommended"]
}
}
Once done, we need to specify the options to Prettier. For that either create a
.prettierrc
file or add a prettier
key to the package.json
. We’ll choose the second option:{
"prettier": {
"printWidth": 90,
"bracketSpacing": false,
"trailingComma": "es5"
}
}
Let’s add the
pre-commit
hook on staged files. We need to install both husky
and lint-staged
in the devDependencies
for that:npm install --save-dev husky lint-staged
Then, we’ll add the
pre-commit
hook itself by adding a husky
key to package.json
:{ "husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
If we leave this as-is, it will fail because we haven’t specified the configuration for
lint-staged
, so let’s do it right away:{
"lint-staged": {
"*.+(js|jsx)": ["eslint --fix", "git add"],
"*.+(json|css|md)": ["prettier --write",
"git add"]
}
}
Here we use ESLint to lint and format JS and JSX files. We also use Prettier to format
json
, css
and markdown
files. And voilà! You’re perfectly set up. Here is the full package.json
file:{
"name": "react-eslint-prettier-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1"
},
"devDependencies": {
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-prettier": "^3.1.0",
"husky": "^3.0.0",
"lint-staged": "^9.2.0",
"prettier": "^1.18.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": ["react-app", "plugin:prettier/recommended"]
},
"prettier": {
"printWidth": 90,
"bracketSpacing": false,
"trailingComma": "es5"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.+(js|jsx)": ["eslint --fix", "git add"],
"*.+(json|css|md)": ["prettier --write", "git add"]
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Last modified 2yr ago