Delivering Components Continuously and with Confidence
We've built our Vue.js component library, but if we can't utilize it in our projects, what's the point? In this article, we'll establish a continuous integration pipeline with GitHub Actions and a release process to publish our components to npm.
Continuous Integration with GitHub Actions
A continuous integration (CI) pipeline is a set of automated processes that run every time a new commit is pushed to a repository. It ensures the code is always in a deployable state and meets the project's quality standards.
GitHub Actions is a CI/CD service provided by GitHub that automates your workflow by running your code in a clean environment and executing the specified commands.
To create a CI pipeline with GitHub Actions, we need to create a .github/workflows
directory at the root of our project and add a ci.yml
file inside it:
mkdir -p .github/workflows && touch .github/workflows/ci.yml
We can then add the following content:
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- run: corepack enable
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Lint files
run: pnpm run lint
- name: Type check
run: pnpm run typecheck
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- run: corepack enable
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Generate files
run: pnpm run build
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- run: corepack enable
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm run test
This configuration file defines three jobs:
- Lint: Installs dependencies, lints files, and type-checks the code.
- Build: Installs dependencies and generates files.
- Test: Installs dependencies and runs tests.
With this setup, every time a new commit is pushed to the main
branch or associated with a pull request, GitHub Actions will execute the CI pipeline.
To make it work, we need to add related scripts to our package.json
:
{
"scripts": {
"build": "pnpm --filter './packages/**' prepack",
"test": "pnpm --filter './packages/**' run test",
"typecheck": "pnpm --filter './packages/**' run typecheck"
}
}
These scripts will run the build, test, and typecheck commands for each package in our monorepo, ensuring we execute commands for each package rather than the entire monorepo.
Publishing to npm
The process of publishing a package to npm is more intricate than it appears. It involves several steps that must be executed in the correct order to ensure the package is published correctly. In a monorepo, this is even more complex due to multiple packages.
The process is:
- Build the packages: Build the packages before publishing.
- Update the version: Update the version of the packages before publishing.
- Update the changelog: Update the changelog of the packages before publishing.
- Create a commit: Create a commit with the changes before publishing the packages.
- Create a tag: Create a tag with the version before publishing the packages.
- Publish the packages: Publish the packages to npm.
- Push the changes: Push the changes to the repository.
Executing these steps manually is error-prone and time-consuming. To simplify the process, we will automate it to publish our packages with a single command.
First, we need to install some dependencies at the root of our project:
pnpm add -D fs-extra bumpp changelogen jiti
Then, create a file scripts/release.ts
at the root of the project:
touch scripts/release.ts
With the following content:
import { execSync } from 'node:child_process'
import process from 'node:process'
import { readJSONSync } from 'fs-extra'
const { version: oldVersion } = readJSONSync('package.json')
// Update the version in package.json
execSync('bumpp -r --no-commit --no-tag --no-push', { stdio: 'inherit' })
const { version } = readJSONSync('package.json')
if (oldVersion === version) {
console.log('canceled')
process.exit()
}
// Create the commit and tag
execSync('git add .', { stdio: 'inherit' })
execSync(`git commit -m "chore: release v${version}"`, { stdio: 'inherit' })
execSync(`git tag -a v${version} -m "v${version}"`, { stdio: 'inherit' })
This script updates the version of the packages, creates a commit with the changes, and tags the version.
Next, create a script in our package.json
to publish the packages:
{
"scripts": {
"release": "pnpm run lint && pnpm run typecheck && pnpm run test && changelogen --output CHANGELOG.md && jiti scripts/release.ts && pnpm -r run release:publish && git push --follow-tags"
}
}
This script will:
- Run lint, typecheck, and test commands to ensure our packages are ready for publication. If any of these commands fail, the process will halt.
- Update the changelog of the repository.
- Execute the
release.ts
script to update the version, create a commit, and tag. - Publish the packages to npm.
- Push the changes to the repository.
Finally, add the following scripts to each package in our monorepo:
{
"scripts": {
"prepack": "pnpm run build",
"release:publish": "pnpm publish --access public"
}
}
The prepack
script builds the package before publishing, and the release:publish
script publishes the package to npm.
Note
Ensure you are logged into the npm registry to publish a package. Use the npm login
command to do so.
Note
Remember to initialize a git repository at the root of the project with git init
and add node_modules
and dist
to the .gitignore
file.
If you need more control over the release process and increased automation, explore the nuxt/nuxt and vueuse/vueuse repositories. However, as each project differs, I strive to keep the release process as straightforward and generic as possible.
With this setup, you can publish your packages to npm with a single command, ensuring they are always delivered with confidence—perfect for a professional developer like you! 😎
Thanks for reading! My name is Estéban, and I love to write about web development.
I've been coding for several years now, and I'm still learning new things every day. I enjoy sharing my knowledge with others, as I would have appreciated having access to such clear and complete resources when I first started learning programming.
If you have any questions or want to chat, feel free to comment below or reach out to me on Bluesky, X, and LinkedIn.
I hope you enjoyed this article and learned something new. Please consider sharing it with your friends or on social media, and feel free to leave a comment or a reaction below—it would mean a lot to me! If you'd like to support my work, you can sponsor me on GitHub!