Speeding up Prettier locally and on your CI with dprint
Prettier is a JavaScript-based code formatter with support for many languages.
dprint is a Rust-based code formatting platform that formats code with formatting plugins including a Prettier plugin.
Overview
Many developers often run a step to verify their code has been formatted with an automated code formatter like Prettier.
In this post, we'll speed up Prettier's format checking of a TypeScript codebase from ~40s to under 1s on the second run.
Previous Prettier setup
Say we have a Node-based repo with about 520 TypScript files in the src
folder
for a total size of 16MB. This repo has a package.json with Prettier installed
that verifies formatting. It looks similar to the following:
// package.json
{
// etc...
"scripts": {
"fmt-check": "prettier --check --end-of-line lf \"src/**/*.ts\""
},
"devDependencies": {
"prettier": "^2.6.2"
}
}
Verification is run by executing the fmt-check
script:
npm run fmt-check
Our GitHub actions CI executes the following steps:
# .github/workflows/ci.yml
steps:
# check out the repository
- uses: actions/checkout@v3
# setup node.js
- uses: actions/setup-node@v3
with:
node-version: '16'
# cache the node_modules folder
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# install the packages in the package.json
- run: npm ci
# verify formatting
- name: prettier
run: npm run fmt-check
When running this on our CI, the "prettier" step takes about 41s.
Switching to dprint
We can speed this up by using dprint as our code formatting CLI with the dprint-plugin-prettier plugin.
CLI setup
To begin, run the following commands in the root directory of the project:
# install dprint as a dev dependency
npm install --save-dev dprint
# initialize a dprint.json file
npx dprint init # note: uncheck all the default plugins and press enter
# add the specified GitHub repo to configuration file we just created
npx dprint config add dprint/dprint-plugin-prettier
A dprint.json file will have been created with the Prettier plugin specified:
// dprint.json
{
"includes": ["**/*.*"],
"excludes": [],
"plugins": [
"https://plugins.dprint.dev/prettier-0.7.0.json@4e846f43b32981258cef5095b3d732522947592e090ef52333801f9d6e8adb33"
]
}
For this example, update the includes
glob to only have the src
directory
and match the extensions Prettier supports that you use in your project. In this
case, I'm only interested in formatting ts files, but in your case you may
specify any of the file extensions Prettier supports (ex.
src/**/*.{ts,js,html,css}
).
// dprint.json
{
"includes": ["src/**/*.ts"],
"excludes": [],
"prettier": {
// optionally, add prettier config here
},
// etc...
}
Now remove prettier
from your package.json and update the fmt-check
script
to use the dprint check
command instead:
// package.json
{
// etc...
"scripts": {
"fmt-check": "dprint check"
},
"devDependencies": {
"dprint": "^0.25.1"
}
}
Try running npm run fmt-check
locally a couple times. The first time will be
faster than Prettier because it formats files on multiple threads using Prettier
snapshotted in a Deno runtime (via
deno_core). The second run should complete
almost instantaneously because dprint will skip files it already knows are
formatted based on past runs.
CI setup
Although the above changes will be faster than Prettier as-is in the CI, we want to take advantage of dprint's incremental formatting and save its cache.
To do this, we need to update our GitHub actions file to cache the
~/.cache/dprint
folder and also use dprint.json
as a cache key in order to
bust the cache whenever we update the dprint.json file:
# .github/workflows/ci.yml
steps:
# check out the repository
- uses: actions/checkout@v3
# setup node.js
- uses: actions/setup-node@v3
with:
node-version: '16'
# cache the node_modules folder
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.npm
~/.cache/dprint
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json', '**/dprint.json') }}
# install the packages in the package.json
- run: npm ci
# verify formatting
- name: dprint w/ dprint-plugin-prettier
run: npm run fmt-check
Testing it out
On first run, we can see dprint takes 35s to execute:
This is only slightly better than Prettier because our GitHub action runner only has 2 cores. It's still not great. Looking at the "Post Cache" step output, we can see our cache was saved:
Let's test running our CI again by pushing a message only commit:
git commit --allow-empty --only -m "Test out incremental formatting."
git push
Watching the GitHub action workflow run, we can see the cache being loaded:
...and the formatting verification step now takes under 1 second to run:
Final notes
We've seen how we can use dprint's CLI to speed up formatting with Prettier. For an example repo, see faster_prettier_example.
This is great for projects already using Prettier that don't want to switch to a new formatter, but the initial format without an incremental cache was still kind of slow. We could speed it up by using dprint's TypeScript and JavaScript Rust-based code formatter instead (see dprint-plugin-typescript) with the caveat that it will format code slightly differently that Prettier (but it has a lot of configuration settings you could tune to try to get it close).
In this scenario, on the CI it takes 10s to run the first time before the cache gets it under 1s:
Thanks for reading!