Optimize your PNG’s with oxipng and pre-commit

PNG’s can be losslessly optimized to reduce their file size, by picking a better compression algorithm for the given image. The savings can be significant, like 50% (especially for macOS screenshots). This is an easy way to make web sites load faster.
For a long time I used the pngcrush. It works okay, but its CLI is a bit clunky, and it doesn’t support nice features like parallel processing.
I recently came across oxipng as a modern, Rust-based alternative. It’s fantastic! I found it can shrink PNG’s further, and faster thanks to its use of threading.
I wanted to run oxipng under pre-commit. This way, every time I added a PNG to my repositories, it would be optimized before being committed. To create the pre-commit hook, I took a path of configuring it in three different ways.
(If you just want to set up oxipng, skip to level 3.)
Level 1: Local Repository Hook
Initially I installed oxipng with cargo, and used a pre-commit system hook:
repos:
- repo: local
hooks:
- id: oxipng
name: oxipng
entry: oxipng
language: system
types: [png]
This worked well for individual use, but it isn’t ideal for teams. Every developer has to install and update oxipng themselves, and different developers could use different versions. Also the whole hook definition needs copying to each repository.
So it’s better to use configuration in a separate repository, which allows pre-commit to handle isolated installation and updates. This is what I did next.
Level 2: Mirror Repository
I set up a “mirror” repository on my account, adamchainz/pre-commit-oxipng. This contains a .pre-commit-hooks.yaml
file that tells pre-commit to install oxipng
in a separate environment, and how to run the hook. Project repositories could then use it like so:
- repo: https://github.com/adamchainz/pre-commit-oxipng
rev: v5.0.1
hooks:
- id: oxipng
I made this repository with pre-commit-mirror-maker. This is a small tool that can create the hook configuration, and update it when new versions of the underlying tool are released. It’s used for “official” mirror repositories like mirrors-mypy.
My oxipng mirror worked well, but it’s a shame that it requires a stanadalone repository with the mirror maker running daily to pull in new versions. It’s easier if the upstream tool repository can maintain the configuration...
Level 3: Upstream Inclusion
I opened an issue on the official oxipng repository, asking if it could include .pre-commit-hooks.yaml
. The creator, Josh Holmer, replied yes, so I made my first pull request to a Rust project!
So now you can use oxipng under pre-commit like so:
- repo: https://github.com/shssoichiro/oxipng
rev: v8.0.0
hooks:
- id: oxipng
Since pre-commit 2.21.0, pre-commit will install Rust for you.
The above version in rev
is the version at time of writing. When there’s a new oxipng
version, pre-commit autoupdate
will update this to the new tag.
One thing to note: optimizing all the PNG’s in your repository is expensive and fairly pointless after they’ve been optimized. You probably want to skip oxipng on your CI system, and run it only on commit. On pre-commit.ci declare it to be skipped like so:
ci:
skip:
- oxipng
On other CI systems, or when running locally, use pre-commit’s SKIP
environment variable:
$ SKIP=oxipng pre-commit run --all-files
Fin
I enjoyed this open source exercise. I saw how pre-commit’s configuration model (YAML in Git repositories) is flexible. It gives us a different ways to try out tools as hooks, and make them “official”.
May your PNG’s be well optimized,
—Adam
Learn more about pre-commit, particularly for Python projects, in my DX book.
One summary email a week, no spam, I pinky promise.
Related posts:
Tags: pre-commit