Enforcing coding standards on the front-end
Introduction
In this blog post, we'll explore a typical code quality setup, compare single projects and monorepos, and provide a step-by-step guide to setting up husky
, pre-commit
, and lint-staged
. We'll also highlight the benefits of using a build system like Nx to manage code quality task execution across multiple projects.
Why enforce quality standards?
Maintaining code quality is an important task for any team working on the front-end. Whether it's a small application or a huge project, many developers prefer to have defined coding standards and automated processes to help keep things in order.
A good code quality setup helps achieve a couple of things:
- Consistency — enforcing styling and coding standards makes the codebase more maintainable and readable.
- Reliability — tests, linters, and type checkers catch errors before code is committed, reducing the chances of issues creeping into production.
- Developer Experience — automated formatting and linting removes the manual overhead from developers, allowing them to focus on more important things.
Code quality setup
A visualization of the most common code quality checks for a typical project
Even though the setup depends on the team and project specifics, a typical setup often includes the following parts:
- Type checking — static type checking, catching type-related errors during development. Comes out of the box with a language like
typescript
. - Linters — catching syntax errors and enforcing coding standards. Usually handled by
eslint
andstylelint
. - Tests — ensuring code behaves as expected.
jest
is a popular testing framework for most kind of projects. - Code formatters — ensuring code is consistently styled.
prettier
is an opinionated code formatter that is considered the default when it comes to front-end development.
In smaller projects, ensuring code quality is relatively straightforward - all tools and configurations are contained within a single project. The setup is different though for monorepos, which house multiple projects or packages in a single repository. Each package usually has its own dependencies and configurations, so ensuring code quality across the entire monorepo requires additional efforts and a custom setup:
A visualization of a monorepo setup with multiple applications and separate code quality checks
What tools to use?
husky
This tool allows you to easily manage Git hooks within a project. Git hooks are scripts that run automatically on specific events in your Git workflow, such as before a commit or a push. husky
simplifies the process of setting up and maintaining these hooks, making sure that certain tasks (like running linters or tests) are executed automatically.
pre-commit
This tool helps manage and maintain multi-language hooks. It allows you to specify a set of hooks that should run before a commit is made, ensuring that the process is aborted if certain checks are not successful. pre-commit
works and integrates with many languages and tools, making it super useful in a code quality setup.
lint-staged
This tool allows you to run linters on staged files in Git. This means that only the files that are about to be committed are checked, which can help developers avoid unchecked code being merged accidentally. lint-staged
works well with husky
and pre-commit
, allowing you to create a complete quality check workflow.
How to configure the setup?
👉 Install the dependencies
First, we need to add husky
and lint-staged
to our development dependencies:
$ npm install husky lint-staged --save-dev
👉 Initialize husky
Next, we need to execute the install
command for husky
. This command automatically adds some relevant changes and configs to your project:
$ npx husky install
Once done, we can execute the automatically generated prepare
script that was added to package.json
:
$ npm run prepare
This command will create a couple of files in your project. To make things work, we need to manually modify .husky/pre-commit
to run lint-staged
:
. "$(dirname -- "$0")/_/husky.sh"
npm lint-staged
👉 Add a custom lint-staged
config
To configure lint-staged
, we need to add a .lintstagedrc
file for each application. This file will make sure that we execute various checks depending on the type of file being committed:
{
"**/*.js": "eslint",
"**/*.{css,scss}": "stylelint",
"**/*.{js,ts,jsx,tsx,json,css,scss,md}": "prettier --write"
}
👉 All done!
Once complete, the code quality setup for our project will look somewhat similar to this one:
A visualization of a code quality system built with husky, pre-commit, and separate lint-staged configurations per application
Introducing a build system
A visualization of how the Nx build system can be used to run code quality tasks across projects in a monorepo
Nx is a build system that offers many helpful tools for working with monorepos. It handles managing dependencies between applications, running tasks in parallel, and ensuring consistent configurations across multiple projects.
By integrating husky
, pre-commit
, and lint-staged
with a powerful build system like Nx, you can execute commands across multiple applications and cache the outputs. This can significantly reduce the development time, as redundant tasks are not re-calculated, but fetched directly from the cache.
A build system like Nx can help scale task execution and maintain consistency across multiple applications, making it a great tool for managing large-scale monorepos.
Conclusion
Using husky
, pre-commit
, and lint-staged
to (gently) enforce coding standards can help developers keep track of code quality and ensure code is merged only if the quality checks pass.
For individual projects and monorepos, automating repetitive tasks and ensuring coding standards can help prevent errors, reduce technical debt, and significantly improve the developer experience.