Delivering Components Continuously and with Confidence

- Lire en français
Resources: huchet-vue

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:

bash
mkdir -p .github/workflows && touch .github/workflows/ci.yml

We can then add the following content:

yaml
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:

  1. Lint: Installs dependencies, lints files, and type-checks the code.
  2. Build: Installs dependencies and generates files.
  3. 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:

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:

  1. Build the packages: Build the packages before publishing.
  2. Update the version: Update the version of the packages before publishing.
  3. Update the changelog: Update the changelog of the packages before publishing.
  4. Create a commit: Create a commit with the changes before publishing the packages.
  5. Create a tag: Create a tag with the version before publishing the packages.
  6. Publish the packages: Publish the packages to npm.
  7. 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:

bash
pnpm add -D fs-extra bumpp changelogen jiti

Then, create a file scripts/release.ts at the root of the project:

bash
touch scripts/release.ts

With the following content:

ts
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:

json
{
  "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:

  1. Run lint, typecheck, and test commands to ensure our packages are ready for publication. If any of these commands fail, the process will halt.
  2. Update the changelog of the repository.
  3. Execute the release.ts script to update the version, create a commit, and tag.
  4. Publish the packages to npm.
  5. Push the changes to the repository.

Finally, add the following scripts to each package in our monorepo:

json
{
  "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! 😎

Profil Picture of Estéban

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!

Continue readingCreating a Playground and a Storybook for Components