Boosting CI Performance with pnpm in GitHub Actions

Every second counts in continuous integration. For the learn-cicd-typescript-starter project, which focuses on building robust CI/CD pipelines for TypeScript applications, optimizing dependency management was a key area for improvement. While npm and yarn have served us well, the shift to pnpm offered a compelling opportunity to enhance performance and efficiency within our GitHub Actions workflows.

The Need for Speed: Why pnpm?

Traditional package managers can sometimes be slow, especially in CI environments where dependencies are installed repeatedly. pnpm tackles this by using a content-addressable filesystem to store packages. This means:

  1. Disk Space Efficiency: Packages are stored in a single, global content-addressable store. If multiple projects use the same dependency, it's only stored once on the disk, saving significant space.
  2. Faster Installations: When installing dependencies, pnpm creates hard links from the global store to the project's node_modules directory. This process is often much faster than copying or extracting files, leading to quicker CI build times.
  3. Strictness: pnpm creates a strict node_modules structure, preventing projects from accessing undeclared dependencies and helping to avoid common "works on my machine" issues.

Integrating pnpm into GitHub Actions

Integrating pnpm into an existing GitHub Actions workflow is straightforward. The primary change involves updating the ci.yml file to include pnpm-specific actions for setup and installation. This typically involves using actions/setup-node followed by pnpm/action-setup to ensure pnpm is available, and then replacing npm install or yarn install with pnpm install.

Here’s a simplified example of how this might look in a .github/workflows/ci.yml file:

name: CI with pnpm

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Setup pnpm
        uses: pnpm/action-setup@v3
        with:
          version: 8
          run_install: false # We'll run pnpm install explicitly

      - name: Get pnpm store directory
        id: pnpm-cache-dir
        run: echo "cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Cache pnpm dependencies
        uses: actions/cache@v4
        with:
          path: ${{ steps.pnpm-cache-dir.outputs.cache_dir }}
          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: | 
            ${{ runner.os }}-pnpm-

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run tests
        run: pnpm test

      - name: Build project
        run: pnpm build

This workflow ensures that pnpm is used for dependency installation, leveraging caching to further accelerate subsequent runs. The --frozen-lockfile flag guarantees that the exact versions specified in pnpm-lock.yaml are installed, promoting build reproducibility.

The Outcome

By integrating pnpm, we observed a noticeable reduction in the time spent on dependency installation during CI runs. This optimization, while seemingly small, adds up across numerous commits and pull requests, contributing to faster feedback loops for developers and a more efficient overall development cycle for the learn-cicd-typescript-starter project.


Generated with Gitvlg.com

Boosting CI Performance with pnpm in GitHub Actions
A

Ana Villanueva

Author

Share: