How to speed up linting locally and in CI_
Today, I will be showing you how to speed up your linting locally and in your continuous integration pipeline (CI). This should help save you time and make your development process more enjoyable.
For linting, we run the following:
- ESLint - To lint our code
- Typescript - To type check our code
- Prettier - To format our code
Our code is stored in a monorepo, so we have a lot of code to lint. I was noticing that as the repo grew, linting was taking longer and longer. I decided to see if I could speed it up.
Enabling ESLint’s Cache Feature
We have a lot of linting rules, and we noticed that linting was taking a long time to run. We decided to use ESLint’s cache to speed it up.
We updated our lint command to use the cache:
{
"scripts": {
"lint": "eslint --cache . && tsc --noEmit"
}
}
This will cache the results of the linting, and only lint the files that have changed since the last run. As a result, the first time you run this, it will take a while to run. However, subsequent runs will be much faster.
Enabling Typescript Incremental Builds
We use Typescript to typecheck our code. We noticed that it was taking a long time to run, so we decided to use Typescript’s incremental builds.
By enabling this in our tsconfig.json file, we were able to speed up our builds by a lot.
{
"compilerOptions": {
"incremental": true
}
}
Similarly to ESLint, the first time you run this, it will take a while to run, but subsequent runs will be much faster. This is because Typescript will cache the results of the typechecking, and only type check the files that have changed since the last run.
Adding caching to our CI pipeline
We leveraged GitHub actions, so your mileage may vary, but we were able to speed up our CI pipeline by caching the node_modules folder, the ESLint Cache, and the Typescript Cache.
-
Cache the node_modules folder - This one comes for free if you are using the actions/setup-node action. It will cache the
node_modulesfolder for you automatically if you use thecacheflag in your workflow.- uses: actions/setup-node@v4 with: node-version: 20 cache: 'yarn'This won’t directly speed up your linting, but it will speed up your CI pipeline by a lot.
-
Output cache manifests for ESLint and Typescript - We need to update our
lintcommand to output the results of ESLint and Typescript to a cacheable location. We do this by using the--cache-locationin flag ESLint and the--tsBuildInfoFileflag in Typescript.{ "scripts": { "lint:ci": "eslint . --cache --cache-strategy content --cache-location ~/.cache/eslint && tsc --noEmit --tsBuildInfoFile ~/.cache/tsc" } }A couple notes:
- We use the
--cache-locationflag to specify where we want to store the cache. We use the~/.cachefolder in this example, but you can use any folder you want. - We use the
--cache-strategy contentflag to tell ESLint to use the content of the file, not the last modified date. We do this because when cloning the repo, the last modified date will be different, but the content will be the same. - We made a new
lint:ciscript because we don’t need to use thecontentcache strategy locally. This is optional, but it felt cleaner to me.
- We use the
-
Re-use cache manifests across runs - we also need to update our workflow to reuse the cache between runs. We do this by using the actions/cache action.
- name: Setup Linting Cache uses: actions/cache@v3 with: path: | ~/.cache/eslint/ ~/.cache/tsc/ key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock', '**/.eslintrc.js', '**/tsconfig.json) }}A couple notes on this one:
- We use the
pathflag to specify which folders we want to cache. You could do the wholecachefolder if you wanted, but in my case other things were changing in there, so I wanted to be more specific. - We use the
keyflag to specify the key for the cache. We use thehashFilesfunction to hash files that should result in a full re-test of all files. This way, if any of those files change, the cache will be invalidated. This is important because if you change your linting rules, you want to make sure that the cache is invalidated, so that the new rules are used.
- We use the
Conclusion
Hopefully, this will help you speed up your linting locally and in your CI pipeline. Feel free to mess around with these various changes and see if you can get even more performance gains.
For our team, we saw the following performance gains after implementing these changes:
| Action | Before | After |
|---|---|---|
| Linting Locally | 1m | 10s |
| Linting in CI | 10m | 3m |