Recently, I published an npm package. This isn't the first package I have made in my life, but this time around it felt "authentic": the other two packages I had made before didn't quite feel professional enough, especially from the point of view of DX — Developer Experience. What changes did I make?
- Enforced following Conventional Commits for my git commit messages, using Commitizen,
- Used semantic-release and Github Actions for automated versioning and releases.
- Used lint-staged to lint my Javascript files before every commit.
- Adding emojis to my commit messages
In this article, I am going to share with you the things I learned and help you get up and running with a similar set-up for your own npm package (or just any personal project, in general).
The Endgoal
How exactly is this set-up going to influence your workflow? Let's take a look:
Any time you make a git commit, you'll have a GUI-in-CLI prompt in your terminal, to help you make a Conventional Commit, without having to memorize the format of such commits.
A relevant emoji will also be added to your commit message, based on the type of the message.
Whenever you push your code to your remote's main branch, depending on the commits since the last push, an npm release, a Github release and a git tag will automatically be made! All in accordance with Semantic Versioning
Changelogs for individual releases will also be automatically published.
Before moving on, I strongly recommend you to read up on Conventional Commits and Semantic Versioning, if you're unsure what they mean. It won't take long!
Heads up!
If you don't wanna do all the work, you can check out this package I have made that will get you up and running in no time. Do checkout the readme for instructions on how to install and set it up, and star it if you like it!
Let's get rolling!
Step 0 – Set up the project
Start by creating a Node.js project. cd
into your project folder and run npm init
. Answer the prompts that appear.
Also initialize an empty git repo and add a Github repo as your remote.
Make sure to set your package version to 0.0.0-development
as a sort of indicator for anyone looking at your code that your releases and version numbers are automatically managed for you.
Step 1 – Install dependencies
We'll be needing quite a few dependencies for our set-up. Let's install all of them in one go. Add the following development dependencies to your package.json
:
"devDependencies": {
"@commitlint/cli": "^17.0.3",
"@commitlint/config-conventional": "^17.0.3",
"cz-conventional-changelog": "3.3.0",
"eslint": "^8.20.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"semantic-commit-emoji": "^0.6.2",
"semantic-release": "^19.0.3",
"semantic-release-gitmoji": "^1.4.4"
}
And now run npm install
to install them all.
Step 2 – Customizing commits
For customizing commits, the tools we need are Husky, Commitizen, commitlint and semantic-commit-emoji.
Basically, Git exposes some hooks onto which functionality can be tied. I like to think of them as events: you can specify the code that gets executed whenever a particular hook is triggered.
Husky is a tool that makes Git hooks easier to work with. Code for Husky hooks is specified in dedicated files in the .husky
folder at the project root. To create the .husky
folder, run npx husky install
.
Run npx husky add .husky/commit-msg
in the project root to generate a hook that modifies the commit message. This will create a commit-msg
shell file in the .husky
folder. Put the following into that shell file:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit $1
npx --no -s semantic-commit-emoji $1
Add commitlint.config.cjs
to your root directory and add the following contents:
const Configuration = {
extends: ["@commitlint/config-conventional"],
formatter: "@commitlint/format",
ignores: [(commit) => commit === ""],
defaultIgnores: true,
helpUrl:
"https://github.com/conventional-changelog/commitlint/#what-is-commitlint",
};
module.exports = Configuration;
Run npx husky add .husky/pre-commit
to generate another hook (which, as the name suggests, runs just before the pre-commit) and in the generated shell file, write:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no lint-staged
This tells Husky to lint all our staged files and make any necessary changes.
Add the following to your package.json
to specify the files to be linted:
"lint-staged": {
"*.(js|ts|jsx|tsx)": "eslint"
}
We also need to specify the configuration for eslint. Add the following to your package.json
:
"eslintConfig": {
"extends": "eslint:recommended",
"env": {
"browser": true,
"node": true,
"jest": true
}
}
If you are using something other than Jest for testing, change accordingly, and if you're using Jest, ensure that you have its dependencies downloaded and configured properly too: install @babel/core
, @babel/preset-env
, babel-jest
and jest
as development dependencies, and create a babel.config.cjs
at the root of your package with the following contents in it:
module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
};
If eslint gives you grief for using some latest Javascript features, you can add the following to the eslintConfig
object above:
"parserOptions": {
"ecmaVersion": "latest"
}
If your package is of module type (i.e. you're using the fancier import
statements instead of require
), you also need to add "sourceType": "module"
to this parserOptions
object. Depending on your requirements (maybe JSX, Vue templates etc), you may want to customize this eslint configuration further.
To configure Commitizen, add the following to your package.json
:
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
NOTE
Now that you have configured Commitizen, you must use
git cz
orcz
instead ofgit commit
to make use of it.
Step 3 – Configure automated npm and Github releases
Create a .github
directory in your project root. Inside, create a subdirectory workflows
and add a .yml
file with whatever name you want (say, publish.yml
). Add the following contents to that file:
name: Package release and publish
on:
push:
branches:
- main
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm ci
- run: npm run build --if-present
- run: npm test -- --watch=false --browsers=ChromeHeadless
- run: npx semantic-release
This tells Github to run some checks every time you push to your Github repo's main branch. It will run your tests and release a new Github and npm release. But this wouldn't work just yet!
First we'll need to make some repo secrets. Here's a nice guide from Azure explaining how to do just that. It isn't difficult at all.
You'll need to make the following two secrets:
-
GH_TOKEN
– This is supposed to contain a Github Personal Access Token. Here's how you can make one. Make sure to make it permissive enough (might as well check all the boxes if you are unsure). -
NPM_TOKEN
– This one is supposed to contain an npm automation token. Check out this guide to know how to make one.
We're almost there! We just need to configure semantic-release to work as we intend it to. To your package.json
, add the following:
"release": {
"branches": [
"main"
],
"plugins": [
"semantic-release-gitmoji",
"@semantic-release/npm",
"@semantic-release/github"
]
},
This tells semantic-release about the branch we want to use as our "release branch", and also extends the default functionality to support emojis using the semantic-release-gitmoji plugin.
Step 4 – Add a nice README
Create a nice README.md
using readme.so and add it to your project.
Voila! We're done. Hopefully, you found this post useful.
Do checkout the npm-pkg-gen package if you want to get going fast, and star it on Github if you like it!
Later!
Top comments (0)