4/19/2022 Updates. I published a second post according to this one: Add lint-staged to SvelteKit Project
Recently I spent a lot of time improving our internal SvelteKit project by aligning coding style & repo quality, especially the commit message and versioning.
In this post, I will share how I added Conventional Commits and SemVer standards to our project and walk you through the setup of commitlint, commitlizen, standard-version, and husky.
What are Conventional Commits & SemVer?
Here, I copy the summary from each website. You can find more via the link.
Conventional Commits
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, which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages.
The commit message should be structured as follows:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
/* Example */
feat(landing): add register button and authentication
SemVer - Semantic Versioning
Given a version number MAJOR.MINOR.PATCH, increment the:
MAJOR version when you make incompatible API changes
MINOR version when you add functionality in a backward-compatible manner
PATCH version when you make backward-compatible bug fixes
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
How to adopt in your existing SvelteKit (or any other JavaScript) Project?
You can find the demo repo here: conventional-commits-sveltekit.
commitlint
First, install commitlint, a linting tool to check if your commit messages meet the conventional commit format:
# Install commitlint cli and conventional config
pnpm add -D @commitlint/config-conventional @commitlint/cli
# Configure commitlint to use conventional config
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.cjs
Use .cjs instead of .js because
"type": "module"is set inpackage.jsonin SvelteKit project.
Now let's test if commitlint works (no prompt if linting passed):

πΆ husky
It's recommended to use husky with commitlint, a handy git hook helper, to check your commit message before making any git commit.
Will add more git hooks later.
# Install husky
pnpm add -D husky
# Active hooks
pnpx husky install
# Add hook
npx husky add .husky/commit-msg 'pnpx --no -- commitlint --edit $1'
huskywould abort the commit if it didn't pass the commitlint
commitizen
It isn't very pleasant if we need to type the valid commit message every time.
commitizen is a CLI tool to help us fill out any required commit fields at commit time.
# Install commitizen,
# @commitlint/cz-commitlint (adapter), and
# inquirer (peer dependency of @commitlint/cz-commitlint)
pnpm add -D commitizen @commitlint/cz-commitlint inquirer
Simply use pnpm cz instead of git commit when committing. Or add a pnpm script to your package.json:
...
"scripts": {
"commit": "cz"
}
We use
@commitlint/cz-commitlintas a commitizen adapter to ensure the commit adheres to the commit convention configured incommitlint.config.cjs.
standard-version
We've configured commitlint, husky, and commitizen to easily create and validate our commit message.
Now, let's see how we can automate versioning and changelog generation using standard-version.
# Install standard-version
pnpm add -D standard-version
Add pnpm script to your package.json.
...
"scripts": {
"release": "standard-version --no-verify"
// use --no-verify to skip git hooks we'll introduce later
}
Here is how standard-version works:
- Retrieve the current version of your repository by looking at
packageFiles, falling back to the lastgit tag.bumpthe version inbumpFilesbased on your commits.- Generates a
changelogbased on your commits (uses conventional-changelog under the hood).- Creates a new
commitincluding yourbumpFilesand updatedCHANGELOG.md.- Creates a new
tagwith the new version number.
Let's try:

By default, CHANGELOG.md would only show commits belonging to feat or fix, but I like to have every commit in my changelog and add emojis.
We can customize it by creating .versionrc under your project root folder. Here is my setting:
{
"types": [
{
"type": "feat",
"section": "β¨ Features"
},
{
"type": "fix",
"section": "π Bug Fixes"
},
{
"type": "chore",
"hidden": false,
"section": "π Chores"
},
{
"type": "docs",
"hidden": false,
"section": "π Documentation"
},
{
"type": "style",
"hidden": false,
"section": "π Styling"
},
{
"type": "refactor",
"hidden": false,
"section": "β»οΈ Code Refactoring"
},
{
"type": "perf",
"hidden": false,
"section": "β‘οΈ Performance Improvements"
},
{
"type": "test",
"hidden": false,
"section": "β
Testing"
}
]
}
You can see more options in Conventional Changelog Configuration Spec.
πΆ husky - more git hooks
Before we push commits and tags, we can consider adding more git hooks to improve our code quality.
Like formatting, linting, and testing.
Maybe you've already set "Format On Save", run linting, or unit testing in watch mode, but it's a personal preference. To ensure a better code quality and efficient collaboration, we can add git hooks like pre-commit, and pre-push to format, lint, and test before opening a PR.
Below we'll take the SvelteKit skeleton project as an example. I choose Typescript, Prettier, ESLint, and Playwright.
First, add formatting and linting at pre-commit stage:
# Add pre-commit hook
npx husky add .husky/pre-commit 'pnpm format && pnpm lint && git add .'

Second (optional), add svelte-check at pre-push stage:
# Add pre-push hook
npx husky add .husky/pre-push 'pnpm check'
I didn't put
playwright testhere because I think it's better to do the e2e test in CI.
If you're using a test runner like jest, uvu, or vitest, you may consider running unit/ component tests in thepre-commitorpre-pushstage.
Caveats
git hooks is not a silver bullet. Your colleague can still "bypass" it in specific ways, but it'd be good to use and align with your team.
Some argue that "Run linting and testing in
git hooksis just a drag on productivity and can't force anything. Why do this if we've already had them in CI? It's even useless if your tests didn't run in a production-like environment."
That might be true and depends on your team and your focus.
We use it anyway while thinking about the "Shift-Left" approach in DevOps.
Wrap up
Thank you for your reading! π
We've walked through the steps to adopt Conventional Commits and SemVer in your project.
Also, there are lots of different commitizen adapters and commitlint configurations you can try.
I tried gitmoji and followed Make everyone in your project write beautiful commit messages using commitlint and commitizen by @sohandutta, but end up using the setting in this article because it's not compatible to changelog generation of standard-version (Ref: Issue #859)
My team and I are still working on our first CI/ CD pipeline, and this article is our first step to improve our workflow.
Please kindly share your thoughts and experience. Love to learn from you!
You can find me on Twitter: @davipon
Useful resources:
Continuous Delivery by Dave Farley (Full of wisdom)
Master the Code Review by Curtis Einsmann (I haven't finished yet, but the content is so great that I need to share it here!)



Top comments (1)
Hey, quick question - if you have husky and commitlint verifying that your commit message structure is correct - doesn't it throw errors when standard-version does its regular commit message? By default it is not conforming to those rules, right?