A Guide to Order: Setting up Prettier and Eslint with Husky, Lint-staged and Commitizen

A Guide to Order: Setting up Prettier and Eslint with Husky, Lint-staged and Commitizen

Structure and consistency over style and comfort

TLDR: To save yourself a headache, use coding standards and become more efficient by automating your development workflow.

Intro

Have you ever had to code review a PR that has 2000+ changes, when the actual code change was less than 15 lines? I have and it’s quite annoying.

When we write code it is to the end that it’s understood by other developers and also your future self. As such we find value in guiding principles and conventions by which we’re able to develop in a consistent manner. This is the very reason for coding standards.

Order over chaos.

Coding standards are best-practices guides that developers follow in order to write code in a predictable and consistent manner. Basically, this makes it easier for the team to maintain and extend an existing codebase.

Engineers at their core tend to prefer automation over manual processes. Being willing to spend hours automating a task that’d take just a few minutes of manual labour. In like manner, there’s the obvious benefit of automating the enforcement of coding standards in a project.

Yes, it’ll fix your errors without additional effort!!! 🎉

This write-up contains explanations as well as a tutorial on how this can be done using Husky, Lint-staged, Eslint, Prettier, and Commitizen.

Learning outcome

  • Understand what coding standards are
  • Understand the advantages of coding standards
  • Understand git hooks
  • Setup git hooks with husky and lint-staged
  • Configure Eslint, Prettier, and Commitizen

Prerequisites

  • Node >= v14

NB: As usual, I lean towards a thorough approach to learning and understanding, therefore I go through the explanations first outlining the benefits and answering why we need to set standards for every project in an organization.

Alternatively, if you’d rather just know how to set up the tools don’t worry I’ve got you covered. You can go directly to the tutorial section here

The what, why, and how of coding standards?

Coding standards are a set of guidelines that recommend programming styles, practices, and methods for a given program.

Why do we need them? Because everyone writes code differently which is usually fine until you have to work with someone else's code.

In development, I generally go by the maxim:

Prefer structure and consistency over style and comfort.

This presents benefits for every developer that’s working on and will work on the project.

Benefits of coding standards

  • It gives a uniform appearance to the codes written by different engineers.
  • It improves the readability and maintainability of the codebase and it reduces complexity also.
  • It allows catching of errors in their infancy
  • It helps in code reuse and helps to detect errors easily.
  • It promotes sound programming practices and increases the efficiency of programmers.

This is general aids developer productivity and speeds up work. It reduces the guessing game and revisions that newer developers have to go through when making contributions.

Style guide

A style guide contains general rules about "how to write" code. This usually contains fine-grained instructions on how code should appear. These would provide guidelines for how the code should look, clearly stating anti-patterns.

To quote an article on the topic,

"All code in any code-base should look like a single person typed it, no matter how many people contributed."

Typical factors considered when choosing a style guide include: Indentation: tabs or spaces, indent width

  • Bracket placement
  • Quotes to use
  • Spacing
  • Ordering
  • Comment style and the use of documentary comments
  • Variable, class, and file naming conventions
  • Statement style and best practices of their usage
  • File organization
  • Declaration of classes and interfaces
  • Exception handling
  • Import and export types

For example, Airbnb’s style guide for React gives a perfect picture of the expectations of a style guide.

Airbnb style guide 1

Airbnb style guide 2

More examples of coding standards can be found here

In practice, it’s important that all these have to be chosen by the team to ensure the standards fit the needs of the project and the developers involved. Coding standards for each team are different as such they have to be decided appropriately. In the end, it’s these developers as well as those that would come who will maintain the codebase. Therefore, they have to be considered when deciding on coding standards.

Suggestion: Call a meeting with the dev team where coding standards are explicitly discussed and agreed upon.

Then, the golden question that this article aims to answer is, how can I enforce this in my codebase? 🤔

Linting

Linting is the process of using a tool to analyzes source code to flag programming errors, bugs, stylistic errors, and suspicious constructs. This is done using a Linter or Lint tool.

The most popular amongst the lint tools is Eslint. According to the docs,

ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs.

Can be installed using:

npm install eslint --save-dev

# or

yarn add eslint --dev

To set up a config file:

npm init @eslint/config

# or

yarn create @eslint/config

This will create an .eslintrc.{js,yml,json} file in your directory. In it you’d find something like:

{
    "rules": {
        "semi": ["error", "always"],
        "quotes": ["error", "double"]
    }
}

These rules guide Eslint when it scans your codebase to know what conventions you’ve specified to follow.

Your .eslintrc.{js,yml,json} configuration file will also include the line:

{
    "extends": "eslint:recommended"
}

This provides some (default standards](eslint.org/docs/rules) (all of the rules marked "✓") from Eslint. These can also be extended using Eslint’s rich plugin system.

You can create and publish your own config, and can also install plugins from other creators from NPM. Some notable ones include:

An alternative linter is JShint.

Formatting

Prettier is an opinionated code formatter with support for various languages. It ensures that all outputted code conforms to a consistent style. Prettier takes your code and reprints it from scratch by taking your configuration into account. You can learn more here.

The docs present extensively the benefits of using Prettier as a code formatter. Some of which include:

  • Building and enforcing a style guide
  • Helping Newcomers
  • Easy to adopt

Can be installed using:

npm install prettier --save-dev

# or

yarn add prettier --dev

Differences between linters and formatters

Linters analyze your codebase to catch errors and suggest best practices ( involving the use of the abstract syntax tree) while formatters fix code style. Linters should deal with tasks like function complexity, syntax improvements, etc while formatter should deal with tasks like spacing, line wraps, comments, etc.

Linters can be used as formatters, but they are not the best fit for those classes of tasks.

The Prettier documentation makes reference to (formatters vs linters](prettier.io/docs/en/comparison.html#how-doe..), stating to:

Use Prettier for formatting and linters for catching bugs!

Both do their job well as such should be used together.

However, there’s a known problem when using both Eslint and Prettier. A good approach is to run prettier first and then Eslint. There, however, exists a simpler solution. Enter prettier-standard.

Prettier-standard helps to format with prettier (actually prettierx) and lints with eslint preconfigured with standard rules. No eslint config has to be set.

Tip: On VScode, an extension that can make this experience better is Error lens. This highlights sections of the codebase that require linting or formatting thereby creating a better experience for the developer.

error lens

Git hooks

Git hooks are scripts that Git executes before or after events such as commit, push, and pull. They can be used to alter internal behavior and receive notifications when certain events occur in a repository. Git hooks are built into Git so there’s no need to download anything. These Git hooks run locally.

Some example hook scripts include:

  • pre-commit: Can be used to check that the commit message conforms to convention.
  • post-commit: Can be used to email team members of a new commit.
  • post-merge: Can be used to install new dependencies

A comprehensive list of Git hooks can be found here

Hook execution timeline

Hooks are regular scripts that reside in the .git/hooks directory of your project. This makes them easy to install and customize. They plug into the entire development life cycle. We now know how to perform customizable actions at every stage in the commit creation process, therefore can greatly increase developer productivity.

A more elaborate explanation of the hooks overview, concept, and scope can be found here

Set up Husky and Lint-staged

Install husky

First, we have to initialize husky. We do this using npx,

npx husky-init && npm i

#Or

npx husky-init && yarn

This would edit the package.json file by adding the husky dev dependency and the script,

"scripts":{
…
"prepare": "husky install"
}

This helps to install husky on other devices.

It also creates a .husky folder in the root directory.

|-- .husky/
   |-- _/
       |-- husky.sh
   |-- .gitignore
   |-- pre-commit

The pre-commit file contains a default script:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm test

This script contains commands that’d run just before a commit is made. Let’s edit this to run type checks and test scripts before a commit is made.

Omit the typescript portions of the script if you’re using Javascript

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run tsc
npm test

And we add the script to the package.json

"scripts":{
…
"tsc": "tsc",
"prepare": "husky install"
}

But there’s a problem. This would run the test files and type checks every time we change anything at all. To solve this we use lint-staged.

Install Lint-staged along with Prettier and Eslint

Install lint-staged, prettier and eslint using,

npm i --save-dev lint-staged prettier eslint

Lint-staged allows us to run scripts only when certain files are about to be committed. Let’s edit our package.json to reflect this,

"scripts":{
…
"lint": "eslint . --fix",
"tsc": "tsc",
"prepare": "husky install"
}
…
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
"lint-staged": {
    "**/*.{js,jsx,ts,tsx}": [
 "npm run lint",
"npm run test --watchAll=false --findRelatedTests --bail",
],
    "**/*.{json,css,md}": "prettier --write ."
  },

Please play around with the flags to obtain the suited result.

We can then go back to edit the pre-commit file,

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run tsc
npx lint-staged

You can then add .eslint.rc, .eslintignore, .prettier.rc and .prettierignore as you see fit.

Now, when a commit is to be made lint-sage would run the type checker, eslint and prettier.

Set up a commit message hook and Commitizen

The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history.

We can create a hook that’d check if the commit message is according to the conventional commit standard.

To add a hook to check the commit message, run the command:

npx husky add .husky/commit-msg

Then paste in the script below,

#!/bin/sh
if ! head -1 "$1" | grep -qE "^(feat|fix|chore|docs|test|style|refactor|perf|build|ci|revert)(\(.+?\))?: .{1,}$"; then
    echo "Aborting commit. Your commit message is invalid." >&2
    exit 1
fi
if ! head -1 "$1" | grep -qE "^.{1,88}$"; then
    echo "Aborting commit. Your commit message is too long." >&2
    exit 1
fi

In order to simplify the process of making a commit according to conventional commit standards, you can install Commitizen.

Install commitizen as a dev dependency,

npm i –save-dev commitizen

Then initialize commitizen,

commitizen init cz-conventional-changelog --save --force

This would provide a setup such that when you make a commit(after git add … of course) using the command,

git cz

You get this,

Commitizen cli

Now you’ve set up an automated workflow with husky, lint-staged, eslint, prettier and commitizen.

This implementation can be extended using dependency cruiser to lock down certain dependencies from being used across the codebase. This is however quite optional and should be adopted as the need demands.

Caveats

There are some caveats to this approach though.

  • Might be overkill for small apps
  • Doesn’t eliminate the need for proper communication
  • Not a replacement for CI

Conclusion

Compliance with coding standards leads to more maintainable software and a team that works well better and ships faster. This is as a result of less back and forth on pull requests.

A research article that studied two small projects even shows a dropping error rate when coding standards are adopted.

Its authors' quote,

The dropping of error trends indicates that as developers align with the coding standard, there will be less fault corrections, hence fewer probability to new faults. Nevetheless although less, developers will be making errors in any circumstances, which justifies the coding standard check step in the software development process. Another thing that is evident through this research is that the use of checking tools in the implementation step mitigates the errors in the code checking step.

The adoption of coding standards can lead to better performance from the team generally as well as boost individual performance and growth. It provides a focus on reliability, security, and structure.

Consequently, it dampens individual coding style yet I argue that priority should be placed on structure over style and consistency over comfort. In my view, its benefits far outweigh its cost.

Hope this article helps.

Resources

  1. youtube.com/watch?v=oWty0Nw1ydk&t=486s&..
  2. youtube.com/watch?v=jNxDNoYEGVU&t=1220s..
  3. githooks.com
  4. github.com/aitemr/awesome-git-hooks
  5. researchgate.net/publication/328912784_The_..
  6. medium.com/docon/airbnb-javascript-style-gu..
  7. taiyr.me/what-is-the-difference-between-cod..