Automate Code Formatting with Prettier, ESLint, Husky, and lint-staged

Alyssa HollandAlyssa Holland
Jan 26, 2025·
8 min read

Introduction

Maintaining a formatted and tidy codebase is the ideal scenario for projects. However, ensuring that codebases adhere to these standards requires some work to make this a reality. In this article, I’ll describe the libraries and automations I’ve employed to achieve a polished and organized codebase a reality.

If you'd like to set up all these tools in a sample project, you can clone this GitHub repository. All the examples I provide will assume you're following along with the sample project unless I mention otherwise.

Benefits

Before we dive into how to automate this process, let's first explore why you would want to automate code formatting and linting. Instead of bike-shedding over rules, you can automatically format or lint files using a small set of configuration files. Prettier is used for formatting, and linters are used for catching bugs. ESLint offers a wide range of options, including framework-specific checks and accessibility warnings, to name a few. Both Prettier and ESLint work together to improve the structure and quality of the codebase.

Prettier

Prettier is an opinionated code formatter that integrates with many code editors and supports a variety of programming languages. Prettier intentionally limits the amount of options you can customize so that you spend less time debating styles like whether to use tabs vs. spaces or single quotes vs. double quotes and more time focusing on important things. Simply put, Prettier saves you time and energy by allowing you to agree on a set of configuration options and move on with your day.

Prettier logo

Installation & Configuration

To install Prettier into your project run the following command:

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

Then, create an empty config file at the root of your project:

touch .prettierrc.json

This .prettierrc.json file will house the configuration options for our project. To test out a few options, open the file and add the following text to enforce double quotes and semicolons.

{
  "singleQuote": false,
  "semi": true
}

Testing

Run the following command at the root of the project to utilize Prettier’s CLI and check for any formatting issues in src/counter.js. :

npx prettier --check src/counter.js

After checking that file, Prettier will output the following to the terminal:

Checking formatting...
[warn] src/counter.js
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

To fix the issue, Prettier gives us a big clue here telling us to use the —write flag to address the issues it found so let’s try doing just that with this update:

npx prettier --write src/counter.js

Now if you run a diff against the counter.js you will notice that semicolons have been added to the end of statements and the single quotes have been replaced with double quotes.

git diff showing the quote and semicolon changes made by Prettier in .

ESLint

ESLint is a powerful tool that performs static analysis of your code to identify potential issues and enforce coding standards. ESLint can detect syntax errors, potential bugs, and deviations from your defined coding style. This tool is widely integrated into many text editors, providing real-time feedback as you write code. Additionally, ESLint can be configured to run as part of your continuous integration pipeline, ensuring that all code changes adhere to your project's quality standards before they are merged. This helps maintain a consistent codebase and reduces the likelihood of introducing errors into your application.

ESLint logo

Installation & Configuration

To install ESLint into your project run the following command:

npm install eslint --save-dev

You can configure ESLint using this command:

npm init @eslint/config@latest

This will prompt you with a series of questions and after installation is complete, create a eslint.config.js file that you can edit and configure to your liking. By default the recommended configuration will be applied and you can configure some rules so that the file looks like this:

// eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js";

/** @type {import('eslint').Linter.Config[]} */
export default [
  {languageOptions: { globals: globals.browser }},
  pluginJs.configs.recommended,
  {
    rules: {
      "no-console": "warn"
    }
  }
];

The name "no-console" is an example of a rule in ESLint. When overriding rules the severity can be one of the following:

  • 0 or "off"

  • 1 or "warn"

  • 2 or "error"

The three severity levels provide you with fine-grained control over how ESLint applies rules (for more configuration options and details, see the configuration docs).

ℹ️ Bonus Tip
You can extend ESLint with plugins. The most common ones I’ve used include: eslint-plugin-jsx-a11y, eslint-plugin-react, eslint-plugin-react-hooks and eslint-plugin-storybook.

eslint-config-prettier

ℹ️ Use Prettier for code formatting concerns, and linters for code-quality concerns.

Linters often include rules for both code quality and style. However, most style rules aren't needed when using Prettier, so we need to install the eslint-config-prettier package to prevent ESLint from conflicting with Prettier.

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

Then add eslint-config-prettier to the ESLint config so that it includes the following:

// eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier"; //Import package

/** @type {import('eslint').Linter.Config[]} */
export default [
  {languageOptions: { globals: globals.browser }},
  pluginJs.configs.recommended,
  eslintConfigPrettier, //Ensure this is the last option in the list
  {
    rules: {
      "no-console": "warn"
    }
  }
];

To ensure everything is set up correctly and that none of the ESLint rules conflict with Prettier, we can use the CLI helper tool included with eslint-config-prettier. For example, if I run npx eslint-config-prettier src/counter.js, it will output, “No rules that are unnecessary or conflict with Prettier were found.”

Testing

Run the following command at the root of the project to utilize ESLint’s CLI and check for any formatting issues in counter.js:

npx eslint src/counter.js

You’ll notice that ESLint does not output anything, indicating that no issues were found. To purposely create an error and a warning, update counter.js to add a console.log() statement and create a new variable without referencing it anywhere.

If you’re using an editor like VSCode and have the ESLint extension installed, you might notice that the editor shows the following tooltips when you hover over the code associated with the issues:

VSCode highlighting two ESLint warnings. One warns about an unexpected console statement, and the other notifies that a variable 'empty' is assigned a value but never used.

However, you can use ESLint’s CLI tool to also check for any issues by running the following command:

npx eslint src/counter.js

After running the command, the terminal should output the following error and warning messages:

CLI output with two ESLint warnings. One warns about an unexpected console statement, and the other notifies that a variable 'empty' is assigned a value but never used.

This indicates that ESLint is set up properly and correctly identifying issues.

Husky

Husky is a tool that allows you to manage Git hooks more easily. Git hooks are scripts that run automatically at certain points in the Git workflow, such as before a commit or push. Husky allows you to configure these hooks making it easier to enforce code quality standards, run tests, or perform other tasks automatically as part of the development process.

Installation & Configuration

To install husky run the following command:

npm install --save-dev husky

Next, we need to run the init command which will simplify setting up husky in a project:

npx husky init

This creates a pre-commit script in .husky/ and updates the prepare script in package.json. In the next section we’ll see how we can use this to run Prettier and ESLint whenever committing new files.

lint-staged

lint-staged enhances the code quality assurance process by running linters on files that are staged for commit, rather than the entire project. This approach ensures that only the files intended for the next commit are checked, preventing errors from entering the repository and enforcing consistent code style. By focusing on staged files, lint-staged significantly reduces the time and resources required for linting, making it a practical solution for maintaining code quality without unnecessary overhead. The tool provides the ability to execute custom shell tasks on these files, filtered by specified glob patterns, streamlining the pre-commit workflow.

Sample output from running git commit with a lint-staged configuration

Installation & Configuration

Run the following command to install lint-staged

npm install --save-dev lint-staged

Next, we need to setup the pre-commit hook to run lint-staged. If you remember from the previous section, husky created this file for us so we can open the file directly and edit it or run the following command:

echo "npx lint-staged" > .husky/pre-commit

Now we need to instruct lint-staged to run Prettier and ESLint. We do this by creating a configuration file named .lintstagedrc.cjs and adding the following code to it:

//.lintstagedrc.cjs
module.exports = {
    'src/**/*.js': ['eslint --fix', 'prettier --write --ignore-unknown']
}

This file uses a glob pattern to match all JavaScript files in the src directory, and it lints and formats all staged files that are about to be committed.

Testing

Now if we add and commit counter.js with the intentional issues that were added earlier in the article, we will see the following output:

Command line output showing a failed ESLint and Prettier task during a pre-commit script run. The errors include an unused variable and an unexpected console statement, resulting in one error and one warning.

ℹ️ Bonus Tip
If you want to test the lint-staged output and configuration without adding and undoing commits, you can run npx lint-staged --debug

El Fin 👋🏽

Hopefully, you can start to see the benefits this combination of tools offers. Prettier formats files, ESLint helps you catch potential bugs, Husky makes it easier to create pre-commit hooks, and lint-staged is the glue that keeps this automation process consistent.

By leveraging these tools, you can automate code formatting and maintain a clean, efficient codebase. These tools not only enhance code quality but also streamline the development process, allowing you to focus more on building and less on manual formatting.

As always, thank you for reading, and happy coding!

10
Subscribe to my newsletter

Read articles from Alyssa Holland directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Alyssa Holland
Alyssa Holland

Welcome to my blog 👋🏽 I'm a Front-End Developer with a passion for learning! I write about Programming 👩🏽‍💻 and Productivity Tips ✅