How to cache node_modules in GitHub Actions with Yarn

mattpocockuk

Matt Pocock

Posted on June 22, 2020

How to cache node_modules in GitHub Actions with Yarn

The Problem

I run a small team working on a growing monorepo. Every commit, some CI checks run on the entire codebase, from GitHub actions. The checks were taking ~8 minutes to complete. We wanted them to run faster.

We use yarn workspaces to manage dependencies, so a single yarn install at root is enough to install the dependencies for all clients.

Trouble is, this yarn install was taking ~4.5 minutes on the CI. On my local machine, where the node modules have already been saved, this can take as little as 5 seconds. I wanted to speed up the CI.

The first thing I tried

GitHub actions recommends that you cache yarn’s cache. This means you end up with 2 steps that look like this:

- name: Get yarn cache directory path
  id: yarn-cache-dir-path
  run: echo "::set-output name=dir::$(yarn cache dir)"

- uses: actions/cache@v2
  id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
  with:
    path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-yarn-
Enter fullscreen mode Exit fullscreen mode

The first step grabs the yarn cache directory path, and saves it. The second step looks for anything stored in the cache, and restores it.

This sped things up a little, but it didn’t reach the heights I was hoping for.

The Solution

Instead of caching the yarn cache, you should cache your node_modules.

- uses: actions/cache@v2
  with:
    path: '**/node_modules'
    key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
Enter fullscreen mode Exit fullscreen mode

This caches all of your node_modules folders throughout your repository, and busts the cache every time a yarn.lock file changes.

This works for our monorepo, and it should also work for single folder projects too.

This took our install step from ~4.5 minutes to ~30 seconds.

The Full Snippet

name: Automated Tests and Linting

on: [push]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1

      - uses: actions/cache@v2
        with:
          path: '**/node_modules'
          key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}

      - name: Install packages
        run: yarn install

      - name: Autogenerate GraphQL
        run: yarn codegen

      - name: Run Typescript Checks
        run: yarn lint

      - name: Run Tests
        run: yarn test:ci
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
mattpocockuk
Matt Pocock

Posted on June 22, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related