Blog about software development


Using Brotli to deliver smaller WASM files

04 Apr 2024 - by 'Maurits van der Schee'

I ran into the free online solitaire card games by Oddstream Games and I loved the (open source) implementation of solitaire card games in Go and Ebitengine. I noticed that the 12.4MB WASM file had been compressed with GZIP which resulted in only 4.8MB of transfer. I compared the support for Gzip with the support for Brotli and also compared the resulting file sizes. The Brotli compression is not only better supported, but it also compressed the same 12.4MB file to 3.5MB instead of 4.8MB. In this blog post I'll explain how to serve Brotli compressed WASM files using Apache. Note that I am using pre-compressed files (not on-the-fly compressed) as Brotli compression can be heavy.


As the author of AceCardGames.com (a game written in Typescript) I am always interested in other web based implementations of solitaire games. I love that the Oddstream solitaire game is open source as this allows me to analyze the performance optimizations done. This was especially interesting to me as I wrote an open source Minesweeper game in Go and Ebiten and I wasn't too happy about the performance. The first good thing I noticed about the Oddstream game was the relative small file size, which is what this post is about. The second thing I noticed was the high frame-rate (even when full screen), but I didn't analyze that yet.


Most browsers know how to decompress Brotli encrypted files. WASM files can achieve good compression ratios with Brotli compression. You can compress files using PeaZip. PeaZip is an awesome open source cross platform compression tool (written in Lazarus/FreePascal) that supports all popular compression formats. On Debian based systems you can install the command line "brotli" tool to compress your wasm file.

sudo apt install brotli

Now you can compress your WASM file(s) using:

brotli -Z --suffix=-brotli `ebiten-mines.wasm`

This command creates the "ebiten-mines.wasm-brotli" compressed file and takes several seconds to run (as it is using "best" compression setting).

Serving pre-compressed WASM files

We want to look at the "Accept-Encoding" request header and check it for the value "br", meaning that the browser can understand Brotli encoded content. In that case we want to serve the Brotli encrypted file and set the "Content-Encoding" response header to "br". In Apache we can achieve that with the following ".htaccess" file:

<FilesMatch "\.wasm$">
  RewriteEngine On
  RewriteCond %{HTTP:Accept-Encoding} br
  RewriteCond %{REQUEST_FILENAME}-brotli -f
  RewriteRule (.*) $1-brotli

<FilesMatch "\.wasm-brotli$">
  Header set Content-Encoding br
  Header set Content-Type application/wasm
  Header append Vary Accept-Encoding

The first block matches files ending on ".wasm" and then turns on the rewrite engine. It then checks for the presence of the "br" accept encoding. If so, it will check for the existence of the requested file with "-brotli" suffix. I that exists it will rewrite the request and append the suffix.

The second block matches the rewritten request and in that case it will set a few response headers. The first is the correct content encoding. The second will set the correct content type. The content type is normally derived from the extension, but the "wasm-brotli" extension is unknown. The last header will ensure that the request is cached different based on accept encoding header request header, ensuring intermediate caches (such as CDNs) will give the correct results.


By using Brotli compression we can achieve really small WASM downloads without sacrificing much compatibility. It does require some configuration on the server, but with the information in this post it should be easy to set up. If you want really small WASM files, you should consider using TinyGo, but note that only a subset of Go is implemented and thus Ebiten is not supported (yet).


PS: Liked this article? Please share it on Facebook, Twitter or LinkedIn.