Node Projects: Linting + Formatting + Conventional Commits
What are we making?
We're going to setup a Node.js project with a linting and formatting automation that will use git hooks
to act upon staged files when we git commit
. Although we're using Next.js and TailwindCSS in this article, the automation that we're building is completely applicable to any and all Node.JS projects, if the main steps are followed, and project specific configuration files are used. This is by no means, a framework-specific guide.
As an added extra, we can also use this approach to standardise commit messages with conventional-commit
syntax! There'll be a future article diving deeper into the concept of conventional commits and the potential it may have to add value to projects, but for now, we'll go through an optional example of how to set it up.
Why?
I'm a bit of a weirdo. I like to ensure that before my work is committed to git locally, staged files comply with my project's linting and formatting rules, there are no TypeScript compilation errors and commit messages are formatted in a consistent manner. I stop short of adding a test run locally because I'm just a weirdo, not a masochist.
We could rely upon IDE extensions to automatically format and lint as we progress through our work, but having a setup like this both acts as a fail-safe to ensure only compliant code is being committed, and makes configuration portable within the repo. Collaborators can automatically inherit the setup when the repo is cloned.
Together with IDE extensions, this workflow makes for a robust toolchain.
What are we using?
The key libraries are:
- eslint: For highlighting potential problems which may lead to errors within the runtime environment.
- prettier: For enforcing a common style for our source code to aid readability.
- prettier-plugin-tailwindcss: A prettier plugin for auto-sorting tailwind classes.
- @trivago/prettier-plugin-sort-imports: A prettier plugin for sorting imports. Inspired by CJ Reynold's walkthrough of his next-start project. It's a fab video which can be seen here!
- husky: For accessing git hooks.
- lint-staged: For running commands against staged files.
- gitmoji-cli: An interactive terminal for formatting commits with emojis and conventional-commit syntax.
I don't have time to read! Is there a video I can watch or a repo I can clone?
I hear you, life is busy... I haven't made a video yet but here's a link to a demo repo that you can clone!
What are the steps?
I love the enthusiasm! Let's get started!
1. Bootstrap Project with Next.js
Use npx create-next-app@latest
to perform the initial setup. The cli will step you through some options. We'll be choosing the configuration in the tab below:
npx create-next-app@latest linting-formatting-setup
This bootstrap already contains an eslint
configuration which works great for our purposes so we're not going to implement any changes to that.
2. Install Dependencies
cd
into our project and install the remaining libraries that we'll need:
npm install -D prettier prettier-plugin-tailwindcss husky lint-staged @trivago/prettier-plugin-sort-imports
3. Configure Prettier
With prettier and our included plugins installed, all we need to do is to add a configuration file at project root. We're going to use a prettier.config.mjs
file (there are a few types of configuration files that prettier offers) as follows:
/*** @see https://prettier.io/docs/en/configuration.html* @type {import("prettier").Config}*/const config = {printWidth: 80,tabWidth: 2,semi: true,singleQuote: false,trailingComma: "es5",bracketSpacing: true,proseWrap: "preserve",plugins: ["@trivago/prettier-plugin-sort-imports","prettier-plugin-tailwindcss",],importOrder: ["^[./]", "<THIRD_PARTY_MODULES>", "^@/(.*)$"],importOrderSeparation: true,importOrderSortSpecifiers: true,};export default config;
This config can be adjusted to suit formatting preferences but the important thing to be aware of is that the plugins array must be populated correctly with our plugin names. The last three properties in the config (importOrder
, importOrderSeparation
and importOrderSortSpecifiers
) are provided by @trivago/prettier-plugin-sort-imports
. Please read their docs for more information on how to configure them.
4. Configure Lint-Staged
Similar to prettier, once installed we just need to add a configuration file which again can be accomplished in a variety of ways but we're going to use a lint-staged.config.mjs
file:
import path from "node:path";const config = {"*.{cjs,mjs,js,ts,jsx,tsx}": (stagedFiles) => [`next lint --fix --file ${stagedFiles.map((f) => path.relative(process.cwd(), f)).join(" --file ")}`,`prettier --write ${stagedFiles.join(" ")}`,],"*.{css,md,mdx,json}": (stagedFiles) => [`prettier --write ${stagedFiles.join(" ")}`,],};export default config;
The above contains two different linting flows. Firstly for JS and TS filetypes, it uses next lint
(which runs eslint under the hood) to fix any linting errors and then invokes prettier
to format those files. The second configuration is for CSS, Markdown and JSON files and it simply invokes prettier
to format those files.
5. Configure Husky
Finally, let's setup husky which will bring all our work together.
npx husky init
Initialise husky with npx husky init
. This will bootstrap a .husky
directory and add a prepare
script to the project (see the husky docs for a full explanation). Within the .husky
directory you'll see a pre-commit
hook has already been created for you. We're going to replace its contents with that of the tab above.
The git life-cycle has many stages within it and we can execute scripts during those stages by using git hooks. The pre-commit hook is invoked prior the staged files being merged into the current branch. We're adding into the pre-commit hook, the ability to typecheck our repo and run our lint-staged configuration against those currently staged files. The hook will fail if an error is encountered by either command, thereby interrupting the git process, prompting us to address the errors that were flagged in the terminal.
That's it, all the setup and configuration is complete. Congrats!
Now What?
Well, all we need to do now is to develop as we normally would! The fruit of our labour should be seen within the terminal when we issue git commit
! Our staged files will be linted and formatted prior to inputting our commit message and all will be well with the world and we'll be rewarded with coffee and chocolate for the rest of our glorious days!
Bypassing Git Hooks...
Git hooks are really powerful and really useful and this setup works super well in almost all instances. However, there may come a time where it might be necessary to perform the unspeakable task of committing work that doesn't pass static analysis or typechecking. In those (reckless but necessary) circumstances, the hook can be bypassed by adding the --no-verify
flag to the git commit
command. E.g.
git commit -m "your commit messsge here" --no-verify"
With great power, comes great yada yada yada. You get it.
Added Extra!
We touched on conventional commits above in the introduction, but as a reminder, it's a concept for standardising commit messages, which in turn makes it easier to understand the unit of work which was added into the branch with each commit. We're going to use the prepare-commit-msg
git hook to invoke the gitmoji
cli which will take over the default terminal experience for adding a commit message, and replave it with an interactive terminal that will guide us through writing a commit message which complies with conventional-commit syntax. But wait, that's not all... This setup will also add emojis to our commit messages, leading to our project earning infinite stars on github, infinite VC investment and more money than even Erlich Bachmann would know what to do with...
npm install -D gitmoji-cli
Let's start with installing gitmoji-cli as a project devDependency. When installed, lets add some configuration to specify how we would like the interactive terminal to behave once it's invoked by adding teh above into the root of our package.json.
Finally lets add a prepare-commit-msg hook with the details above into our .husky directory.
Now when you run git commit
once typchecking, linting and formatting has been completed, we'll be provided an interactive terminal to format our commit message!
If we want to modify the gitmoji-cli configuration, we can checkout the docs here. It's highly recommended to do a bit of background reading into conventional-commit syntax. We're using gitmoji in this example as emojis are objectively awesome... fact. They work great for personal projects, but if team projects require a less delightful way to standardize commit messages, commitizen is a great alternative.
Super Bonus Extra!
For the vscode users reading this, here's some IDE-specific configuration to complement our code quality workflow. Create a .vscode
directory at project root and create each of the following within:
{"recommendations": ["dbaeumer.vscode-eslint","esbenp.prettier-vscode","bradlc.vscode-tailwindcss"],"unwantedRecommendations": []}
- Recommended extensions for projects using eslint, prettier and tailwindcss
- A debugging configuration for Next.js projects, inspired by the official docs.
- project-scoped settings for invoking the prettier and eslint extensions to do their thing as files are saved.
Thanks for taking the time to read this article. I hope it helps and inspires you to learn and share your knowledge with others!