We recently released face.camp, a Progressive Web App (PWA) that allows you to take animated gifs of your face and share them with your fave Slack channel. We’ve also open sourced the code on GitHub.
It was the first project I worked on when I (also) came back from a three-month sabbatical. I had previously burned out on coding and I found myself for the first time getting excited about having the freedom to go down rabbit holes to solve even the smallest of problems. Most of these problems weren’t even practical to solve, but I had a good time solving them.
Preact
Knowing that the site was going to be a PWA, I wanted to keep the frameworks used as small as possible and take advantage of any tooling that would allow me to do that. preact
and preact-cli
seemed like a perfect fit.
Browser Support and Byte Shaving
face.camp uses the getUserMedia
API which has been available for a while in most modern browsers. I took this list of browsers and updated them to include only some of the more recent versions of each so that the site would be able to ship newer JavaScript features supported by these browsers including Promise
, const
& let
, fetch
, and arrow functions (=>
). The JavaScript for the site gets built via preact-cli
and @babel/present-env
, but I noticed that even when supplying the list of supported browsers there was still room to tweak the final build. So I went digging to see what else I could do.
The first thing I wanted to do was found out how many bytes I was saving by shipping ES2015+. Using a script that would build multiple versions of the app with different build configurations, I found that by switching the supported browsers from the default of ['> 0.25%', 'IE >= 9']
, I saved 506 bytes. Armed with my slightly over-engineered script, I went out looking for what other configurations would save even the smallest amount.
The next thing I found, is that I could also use native async/await
instead of the fast-async
plugin. This saved me 234 bytes! Sure that’s only a savings of 0.74% of the total bundle size, but…actually I don’t really have a justification for savings 234 bytes. Remember what I said above about doing things that weren’t practical?
I also noticed that some of the babel plugins were using an inline helper to replace the object spread operator. Since the list of supported browsers for the project all supported Object.assign
, I added useBuiltIns: true
to the settings for each plugin. And yes, the savings were even smaller than before (93 bytes, but who’s counting).
When I combined all those build settings I lost some of the aggregate savings, but still came in at 808 bytes smaller than when I started. If you’re as interested as me in shrinking a build by ~2.5%, you can check out the raw gist of the script’s output or try running it yourself.
Slack Max Inline Gif Size
One of our favorite parts about face.camp is how Slack is able to put the gifs inline into whichever channel or conversation you want. However, we found out that Slack will only do this for images less than 2MB in size. The site uses gif.js and a solution I learned from Philip Roberts’ gifhu.gs to generate the gifs. But they would just often enough come out to greater than 2MB depending on the complexity of the image. I tried lowering the framerate, quality, and size, but found that in order to always get the size lower than 2MB those options noticeable degraded the image quality for most other images.
The easiest solution I found was to make the computer keep re-rendering the gif until the size was under 2MB! It progressively lowers the size of the image and keeps track of the scaling multiplier so any future image in that session will use the new scale. Eagle-eyed users might notice that the progress meter sometimes runs twice, this is why!
PWAs and iOS
In 2018, iOS 11.3 added support for Progressive Web Apps. However, one big part that was missing is support for getUserMedia
inside those PWAs. Since I wanted to keep support for PWAs on Android, I didn’t want to drop support altogether. I ended up stumbling upon the open-source Pinafore project and finding an issue about another shortcoming in iOS PWAs that they had to work around. I ended up copying their solution, which is to use user agent sniffing to remove the manifest.json <link>
tag.
Let Us Know What You Think
The code is now open source on GitHub. Please play around it with and let us know if you have any issues, questions, or enhancements by opening an issue.