Eslint Cache in CI

25 April 2022

At Gatsby, we have a monorepo that contains all our internal packages. Our CI pipeline will lint, type check, test, and build our entire system. As we add more packages, CI times have taken longer and longer. I’m always on the lookout to shave off a few seconds or minutes of our CI time. One of our issues is that linting the entire monorepo can take about 2 - 2.5 minutes.

I read a post on hacker news about speeding up prettier in CI by using a cache with d-print. Knowing that eslint has a caching option, I wondered if I could do the same thing with our CI lint step.

Eslint has several flags related to caching:

Our full command might look something like the following. Which we can then save in our package.json.

{
  ...
  "scripts": {
    "lint": "eslint ./src/ --cache --cache-strategy content --cache-location ~/.cache/eslint/demo"
  }
}

Here, we have to use the content cache strategy. Git doesn’t save last edit times (metadata). If we were to use metadata strategy, when we checkout our repo in CI, we won’t get a valid cache hit.

Then we can configure the cache action in github actions:

name: CI
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: Use Node.js 16.x
        id: tests-workflow
        uses: actions/setup-node@v2
        with:
          node-version: 16.14.0
          cache: npm
      - name: Cache
        uses: actions/cache@v3
        with:
          path: |
            ~/.cache/eslint/
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json', '**/.eslintrc.js') }}
      - name: Install
        run: npm ci
      - name: Lint
        run: npm run lint

We must configure out actions cache path to be the same as our Eslint cache location. For our cache key, I use the checksum of pacakge-lock.json and .eslintrc.js. That way, the cache will bust if we make any changes to our Eslint config or update our dependencies.

The first run will still take the same amount of time as before since we have not yet built our cache. This run took about 11s.

initial eslint CI run

Subsequent runs will be much faster. This run took about 1s.

subsequent eslint CI run

You can find all the code used in this demo in this repository.

After implementing an eslint cache for our monorepo at Gatsby, our lint times went from about 2 minutes to 6 seconds with a cache hit.