Improve Frontend Code Quality with Tools — Version Automation
I’ve been building an internal UI component library to standardize product UI and improve delivery speed instead of reinventing components each time. Great goal, but it exposed a few release headaches.
Libraries evolve and need regular publishing. Our current flow is: change code, commit, bump the version manually, then run the publish command. That causes several issues.
Pain Points
- A change might be a
fix
, afeat
, or even abreaking change
. Raw commit messages rarely capture that nuance, and we produce no public release notes. Consumers just see a new version number and feel anxious about upgrading. - Manual version bumps are error-prone and inconsistent. A newcomer might mark a tiny CSS fix as a major release. Manual editing is also tedious.
- This isn’t limited to UI libraries — app repositories also deserve clear commits. Our internal history has plenty of throwaway messages with little value.
- Without standardized commits, every push can trigger CI and even a release pipeline. Documentation-only changes shouldn’t do that. The fix is to enforce structured, conventional commit messages.
Time to lean on tooling. Fortunately, the ecosystem is mature.
Angular as a Reference
Angular’s commit history and changelog are exemplary. How do they do it? https://github.com/angular/angular/commits/master
Semantic Versioning Refresher
Before wiring up the tools, revisit SemVer so version bumps stay meaningful.
MAJOR.MINOR.PATCH
with the rules:
- MAJOR: incompatible API changes.
- MINOR: backwards-compatible feature additions.
- PATCH: backwards-compatible bug fixes.
Pre-release identifiers and build metadata can be appended. Full spec: semver.org
Setup
Install the Tooling
yarn add -D commitizen # commit helper
yarn add -D commitlint # lint commit messages
yarn add -D cz-conventional-changelog # Angular-style prompts
yarn add -D standard-version # SemVer + changelog automation
yarn add -D @commitlint/config-conventional
IDE Plugin
- We use JetBrains IDEs; find equivalents for other editors.
- The GUI makes structured commits friendlier than the terminal prompt.
package.json
"scripts": {
"commitmsg": "commitlint -e $GIT_PARAMS",
"release": "standard-version"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
commitlint.config.js
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"subject-case": [2, "never", ["upper-case"]]
}
};
.huskyrc.json
{
"hooks": {
"commit-msg": "commitlint -e $GIT_PARAMS"
}
}
.versionrc.json
Example config: alanhe421/jhipster-starter
Common Commit Types
type
describes the scope of change. Frequently used values:
feat
: new feature (maps to MINOR in SemVer)fix
: bug fix (maps to PATCH)to
: tweak that moves toward a fix but isn’t final; useful for multi-commit fixes before the concludingfix
docs
: documentation changesstyle
: formatting, missing semicolons; no production code changesrefactor
: code restructure without new features or fixesperf
: performance improvementstest
: add/refactor tests; no production code changeschore
: tooling, build scripts, maintenance; no production code changesrevert
: rollback a commitbuild
: build system changesci
: CI-related changes such as hooks or Docker configs
Full spec: Conventional Changelog Config
The to
type comes from an Alibaba practice described here. It lets multiple incremental commits link to one eventual fix; the changelog groups them so you don’t get duplicates when to
entries are marked as hidden: false
.
Workflow
Making a Commit
Use the IDE commit panel and enable the commit template. Fill out each field according to the prompts.
When it’s time to publish, run
npm run release
. The script updatesCHANGELOG.md
, bumps the version, and creates a commit automatically.- First run:
npm run release -- --first-release
- Prefer triggering releases in CI.
- First run:
Notes
Commits that don’t match the format fail immediately thanks to Git hooks.
Because enforcement happens via local hooks, someone could disable
.huskyrc.json
and bypass checks. If that becomes a problem, add server-side hooks.
Pre-release Versions
Sometimes you need a fresh version for testing without blocking ongoing development. Use the pre-release flag:
# "alpha" can also be "beta" or "rc"
npm run release -- --prerelease alpha
Alpha, Beta, RC Refresher
- alpha: internal test builds with rough edges and incomplete features; meant for developers/testers.
- beta: public test builds; more stable than alpha but still experimental.
- rc: release candidate; feature complete and essentially what will ship.
- stable: the final GA release.
Ordering example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
.
Keep the process pragmatic for small projects.
Troubleshooting
Duplicate changelog entries
I once saw repeated sections across releases. It was an upstream bug; reinstalling/upgrading the package fixed it. Keep your release tooling current.
standard-version
derives the next release from two data points: the version inpackage.json
and the commits since the latest tag (git describe --tags --abbrev=0
).For example, if you delete local tags
v0.1.14
andv0.1.13
and runnpm run release
, the new version includes commits from both tags, so the changelog shows bothfeat
andbuild
entries.Bypassing hooks
Git hooks rely on shell scripts. If developers deliberately disable them, commits still pass locally. Server-side hooks are the only airtight safeguard.
Final Thoughts
- Well-structured commit messages matter; compare Angular’s history with a typical repo and the gap is stark.
- Quality history helps new contributors ramp up and makes defect tracking easier.
- Enforcing conventions has a cost, but the benefits outweigh it.
- Good habits pay dividends for years—embrace them.