Few weeks ago I wrote rainbow, a color picker in plain old HTML/CSS/JS. I needed a color palette as quick as possible so I made this tool the quick-and-dirty way.

I then gave this tool some thoughts, rewrote it in Rust 🦀 and called it bifröst. It’s a single page application in WASM1 and it’s loaded with many more features than rainbow.

Let me tell you something : it’s HEAVY! The first iteration weighed 1.4Mo. Way too much for such a small application.

This blog post is about the settings and tools to divide its weigh by three.

The problem

Well, 1.4Mo takes almost 20 seconds to load on a regular 3G connexion according to my favorite browser tools. While I doubt anyone will need this code to run on a mobile, it’s still unreasonable.

The solution

There are two steps for the diet : pick the correct settings for the compiler and then strip the output even further.

Cargo

This is something I usually do for natively executables. And I simply did not think about it for WASM at all. Shame on me.

[profile.release]
lto = true
opt-level = 'z'

The first line allows for link time optimizations. In other words : please, take your time to compile but make a smaller output.

The second line ask specifically for size optimization and not for speed optimization.

With those two settings only, the weigh dropped down to roughly 700Ko. It halved the WASM asset !

More informations about cargo settings here.

CI/CD

When it comes to native binaries, I use strip to fully remove any useless piece of data from the file. It turns out their is another tool for WASM called wasm-opt in the binaryen package.

Last time I wrote about hosting a yew SPA on gitlab, I used a custom docker image. All it takes is to add the binaryen to this very image and use wasm-opt in the CI/CD pipeline.

FROM rust:slim
RUN \
  rustup target add wasm32-unknown-unknown && \
  cargo install --locked trunk &&             \
  apt-get update &&                           \
  apt-get install -y binaryen &&              \
  apt-get clean &&                            \
  rm -rf /var/lib/apt/lists/*
---
build:
  stage: build
  image: $CI_REGISTRY_IMAGE:latest
  script:
  - trunk build --release --public-url $CI_PROJECT_NAME
  - for wasm in dist/*.wasm; do wasm-opt -Oz $wasm -o $wasm -c; done
  variables:
    CARGO_HOME: ${CI_PROJECT_DIR}/.cargo
  cache:
    paths:
      - Cargo.lock
      - target
      - .cargo
  artifacts:
    paths:
    - dist

pages:
  stage: deploy
  rules:
  - if: $CI_COMMIT_TAG != null
  needs: [build]
  script:
  - mv dist public
  - cp public/index.html public/404.html
  - echo "Deploying to $CI_PAGES_URL."
  artifacts:
    paths:
    - public

And, done ! Down to ~500Ko.

Conclusion

It literally took 5 lines of configuration to divide the WASM asset by three.

Now, I know that 500Ko for such a small application is still a problem. I’ll investigate and write down what I find in another post. And if a fellow reader knows anything about that, it’s possible to drop me a note on Mastodon and share with the world!

1

Web Assembly