Formatting and linting with ESLint and Prettier

March 17, 2020 (updated March 19, 2022)

In this article we'll use ESLint and Prettier to lint and format our code. We'll go through the configuration process and make sure the two libraries play nicely together.

This is part two of the series Create a React App From Scratch With Webpack and Babel, but you don't need to have read part one to follow along.

You can read part one here.

(This series has a companion Git repository: github.com/nicoqh/react-boilerplate)


Coding style guides help you avoid stylistic inconsistencies and potential problems in your code base.

A style guide consists of a set of rules. Using these rules, a linter or a formatter analyzes your source code and flags (or automatically fixes) potential problems and stylistic errors.

Rules are generally divided into two categories:

  • Formatting rules enforce the formatting of your code (how your code looks). For example, the rule "lines should be no longer than 80 characters" is a rule that affects how your code is formatted, but it doesn't affect the code's runtime behavior.

  • Code quality rules enforce coding (best) practices and aim to reduce bugs and incorrect code. For example, the rule "eliminate unused variables" is a rule that helps you remove unused variables from your code base (these variables could be left over from a refactoring). The rule has nothing to do with formatting; the code can be perfectly formatted even though it contains an unused variable.

We'll use two popular tools to lint and format our source code: ESLint and Prettier.

Linting with ESLint

ESLint is the most popular linter for JavaScript. It analyzes your code to find both stylistic (formatting) errors and code quality issues.

Before we start using ESLint, let's go through some important concepts.

ESLint offers a long list of rules, but none are enabled by default. To use a rule, you add it to an ESLint configuration file. For example, if you want to eliminate variables that are declared but not used, add the no-unused-vars rule to your .eslintrc.js configuration file:

rules: {
  "no-unused-vars": "error"
}

Configuration files can be shared between projects. Instead of maintaining your own list of rules, you can extend another configuration file. Many configuration packages have been published on npm. These are named eslint-config-<name> by convention.

To extend a configuration package, your create an extends array in your ESLint config file, and add the name of the config package (omitting the eslint-config- prefix):

extends: [
  "standard" // Extend eslint-config-standard
],
rules: {
  "no-unused-vars": "error" // Your own rules
}

Your own rules take precedence over the extended configurations.

What if you need rules that aren't provided by ESLint?

In addition to the rules provided by ESLint, there are several ESLint plugins that can help you with specific enforcements. Plugin packages are named eslint-plugin-<name> by convention, and you can omit the eslint-plugin- prefix when referencing them in your own ESLint config.

For example, the plugin eslint-plugin-react provides a set of rules that are useful for React and JSX projects. Adding this plugin to your .eslintrc.js config makes its rules available for use:

plugins: [
  "react", // eslint-plugin-react
],
rules: {
  "react/no-typos": "warn", // Use the 'no-typos' rule from eslint-plugin-react
}

In addition to providing custom rules, ESLint plugins often contain configuration files. For example, the React plugin provides both React specific rules, and configuration files that use those rules. This is a convenient way to provide users with a recommended way of using the provided rules.

These plugin-provided configurations can also be extended from your ESLint config:

extends: [
  "plugin:react/recommended", // A configuration from the React plugin
]
plugins: [
  "react", // eslint-plugin-react
],
rules: {
  "react/no-typos": "warn", // Your own rules
}

The syntax of the extends array can be a bit confusing. How you reference an external configuration depends on whether it's a local file, a configuration package or a configuration provided by a plugin. You can read more about the "extends" property if you're interested. Here's an overview of the differences:

extends: [
  "./myConfigFile.js", // Local file
  "standard", // Configuration npm package `eslint-config-standard`
  "plugin:react/recommended" // A configuration from a plugin
]

Let's get started by installing ESLint locally:

npm install eslint --save-dev

We won't add any rules or configurations manually. Instead, we'll let ESLint generate a config based on some sensible defaults.

By answering a set of questions, ESLint can install any necessary plugins and configurations, and then automatically create a configuration file.

To set up a configuration file, run the ESLint initialization command:

./node_modules/.bin/eslint --init

ESLint will ask you to install @eslint/create-config.

Then you will be presented with a set of questions. I answered them like this:

  • How would you like to use ESLint? To check syntax and find problems
  • What type of modules does your project use? JavaScript modules (import/export)
  • Which framework does your project use? React
  • Does your project use TypeScript? No
  • Where does your code run? Browser
  • What format do you want your config file to be in? JavaScript

Answer yes when it asks to install the dependencies required by the configuration.

Your ESLint config file has been written to .eslintrc.js. If you open the file you'll see that it extends eslint:recommended and plugin:react/recommended. At the bottom you'll find an empty rules object. You can use this to add additional rules, or to overwrite the ones provided by the recommended configs that you're extending.

To see the result of your config file and all the active rules, run ESLint with the --print-config flag:

./node_modules/.bin/eslint --print-config .eslintrc.js

Let's test ESLint by intentionally adding an error to our code. Open src/index.js and define a variable:

const name = "Jon Snow";

Because this variable isn't used, ESLint will complain due to the no-unused-vars rule.

❯ ./node_modules/.bin/eslint src/index.js

/src/index.js
  4:7  error  'name' is assigned a value but never used  no-unused-vars

If you want to disable this rule (you probably shouldn't), add it to the rules property in your .eslintrc.

This is how you override rules from the configuration(s) you're extending.

To integrate ESLint with your code editor, check out the available integrations.

You can also add it as an npm script in package.json:

"scripts": {
    // ...
    "lint": "eslint ./src"
}

This lets you simply run npm run lint.

Formatting with Prettier

Prettier is a code formatter. It parses your code and re-prints it according to a set of opinionated formatting rules. Prettier doesn't care about the meaning of the code, only about how it looks.

Prettier can enforce consistent maximum line lengths, make sure you don't mix single and double quotes, add trailing commas to each array item, and help you with other formatting concerns.

Let's install Prettier:

npm install prettier --save-dev --save-exact

(The Prettier documentation recommends using the --save-exact flag, which saves an exact version to your package.json instead of a version range.)

Then, run Prettier on a JavaScript file using the default formatting rules:

./node_modules/.bin/prettier src/index.js

Prettier will parse the code and re-print it according to its default rules. The command's output is the new code, correctly formatted. It didn't change (write to) the file, because we didn't tell it to.

Instead of manually running Prettier from the command line, check out the official documentation on how to integrate Prettier with your text editor.

Prettier formatting options

Prettier offers a set of customizable formatting options (rules about how the code should be formatted). The number of options is intentionally limited in order to avoid bike-shedding (arguing over trivial stuff). Due to the limited number of formatting options, Prettier can be seen as a style guide as well as a formatter. A big selling point for Prettier is that you don't need to argue with your co-workers on how the code should be formatted.

In this guide we'll use Prettier's default formatting options, except for one: use single quotes (') instead of double quotes (").

Prettier has a "singleQuote" option that we can use.

Create the config file .prettierrc (notice the leading dot) and add the following JSON (view commit):

{
  "singleQuote": true
}

This will tell Prettier to prefer single quotes.

Using Prettier with ESLint

As mentioned, ESLint can detect both stylistic (formatting) errors and coding errors.

The recommended approach is to use Prettier for code formatting, and let ESLint take care of code quality concerns.

This means we should disable any existing formatting rules in ESLint. If you're extending other ESLint configuration, this can be hard to do manually.

To achieve this, Prettier provides an ESLint configuration package that disables all formatting rules that conflict with Prettier.

Install eslint-config-prettier:

npm install --save-dev eslint-config-prettier

In order for this configuration to overwrite and disable ESLint formatting rules, it must be the last configuration we extend from .eslintrc.js:

// ...
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",

    // Disables formatting rules from *all* ESLint configs.
    // Provided by `eslint-config-prettier`.
    "prettier",
  ],
// ...

In summary, we still extend some ESLint configurations, but we let Prettier's ESLint config disable any formatting rules from ESLint. Now, only Prettier is responsible for code formatting concerns.

You should integrate Prettier with your text editor so that your code is formatted automatically.

(It's also possible to have ESLint report formatting errors detected by Prettier. The ESLint plugin eslint-plugin-prettier runs Prettier as an ESLint rule and reports formatting errors as individual ESLint issues. We won't do that in this article, but check it out if it makes sense to your workflow.)

That's it for now. This series of articles will likely be expanded in the future to cover more Wepack, Babel and React concepts. For now, you can check out the bottom of the first article for tips on what to do next.