akshy

Someone in this universe

I was able to reduce size of my wasm app by 29.6%

I have been working on one of my hobby projects, pixeldraw (a raster drawing software) for the last few days. It is written using the rust programming language and iced is used for the gui. While primary focus was the desktop, I discovered that iced has wasm(web assembly) support. So I gave a thought about it and compiled it to wasm using trunk and hosted on my website so anyone interested can try it without downloading. My first concern was about the size of the wasm file. It is actually quite larger, 6167170 bytes (5.9 Mb). For a desktop app it was not a problem, it's only a one time download and also it's small compared to normal desktop app size. But for web it's really a problem, for each request the whole wasm file should be send. It will start to consume bandwidth quickly. So I started to search for ways to reduce the size (even though it's not a big problem for a site with less traffic).

  1. The first logical step will be to use -Oz flag in wasm-opt to optimize for size, but I am already using it. The 5.9 Mb size was after using it. So I am not considering it here. I added it here just as a reminder of this feature.
  2. Then I checked the Cargo.toml file. The release profile was;
    [profile.release]
    opt-level = 's'
    lto = true
    strip = true
    codegen-units = 1

    It was optimized for size, but a little modification can be done here. I replaced the s flag in opt-level with z which reduces the size further with a little performance reduction (but according to the documentation on some cases s will result in smaller size than z).

    But the main change was to add panic = "abort"(The default behavior is panic = "unwind"). Since unwinding is useful for the desktop version and it is not recommended for the dependencies, I haven't added it to the [profile.release] section. Instead I created .cargo/config.toml and added it there specifically for the wasm target.

    [target.wasm32-unknown-unknown]
    rustflags = [ "-C", "panic=abort" ]

    Thus the normal build will use panic = "unwind" and panic = "abort" will be used while targeting wasm.

    After that I checked the size of wasm file. It is reduced to 5.1 Mb.

  3. After that I checked the dependencies section. There was a lot of space for improvement. Apart from the other crates common for both desktop and wasm, the initial structure of wasm target was like this;
    [target.'cfg(target_arch = "wasm32")'.dependencies]
    iced = { version = "0.14.0", features = ["canvas", "tokio", "image", "webgl", "fira-sans" ]}
    wasm-bindgen-futures = "0.4"

    And the dependencies field was;

    [dependencies]
    # ... other dependencies
    image = { version = "0.25.10", default-features = false }
    # ... other dependencies

    Yeah I am pulling most of the default features of iced which are not useful for the wasm. tokio is not at all useful (wasm-bindgen-futures handles it), so I removed it first. Instead of pulling all the default features of iced I disabled the default features and added only what is necessary (added wgpu-bare and web-colors). I am already using the image crate and iced is also using it. So I changed image with image-without-codecs in iced features and the image was moved from dependencies and in the wasm specific target I added png ang gif features to the image crate which is needed for the app to work. Finally it looks like this;

    [target.'cfg(target_arch = "wasm32")'.dependencies]
    iced = { version = "0.14.0", default-features = false, features = ["canvas", "image-without-codecs", "webgl", "fira-sans", "web-colors", "wgpu-bare"]}
    wasm-bindgen-futures = "0.4"
    image = { version = "0.25.10", default-features = false, features = ["png", "gif"]}

After the third step I checked the size again. It was 4340682 bytes (4.2 Mb). Totally I was able to reduce the size from 6167170 bytes to 4340682 bytes (1826488 bytes is removed), 29.6% reduction without changing my app logic or source code.