Swift for Wasm: Q3 2025 Updates

Since official support for Wasm in Swift 6.2 was announced at WWDC this year, Swift contributors have done a huge amount of work. To provide better visibility and to track progress, here’s a list of notable changes:

Surely I missed a few changes in this list unintentionally, so please feel free to post what you found notable in this thread. Many thanks to all of the contributors pushing the ecosystem forward!

54 Likes

(ง^_^)ง LET'S GOOO!!!

2 Likes

This is very cool. I really need to try out some WASM stuff soon if just to play with it!

1 Like

Are there any plans for Carton?

We are soft-deprecating Carton in favor of swift package js SwiftPM Package Plugin provided by JavaScriptKit, which just generates an ES Module + .wasm file. You can still use Carton for now, but I no longer have any further plans to invest in Carton for now.

4 Likes

I see. What are the plans for the debugging features?

We actually added enhanced Swift support in the Wasm debugging extension for Chrome DevTools early this year: Debugging - Swift and WebAssembly

5 Likes

I see. So that feature wasn’t necessarily required to be used with Carton. I apologize. I feel like I have been updating and changing how I build my project that I have lost track of where all the parts come into play. I have stayed with 5.10 so I can keep using carton. But I gotta move to 6.1+, so now I’m trying to move to whatever the newest way to do things is. I just finished building a new Ubuntu VM to test. The project compiled fine using the instructions form the page your provided.

Is there a mechanism to run 'wasm-opt’ after it is built?

Yes, swift package js -c release automatically applies wasm-opt after the build if the tool is installed.

I see. So the tool is installed. But the binary is at 120mb (should be like 20mb after wasm-opt). Is the stripped file being moved somewhere else? I guess, what I mean is there a folder like “Bundle” that is being created somewhere?

  1. Hmm, if you build with release configuration, debug symbols should be stripped unless you explicitly pass --debug-info-format option. Can you share your build command?
  2. The "Bundle" directory is generated under .build/plugins/PackageToJS/outputs/Package by default. If you pass --output option with --allow-writing-to-package-directory plugin option, you can keep the existing directory layout:
$ swift package --swift-sdk "${SWIFT_SDK_ID}" -c release plugin --allow-writing-to-package-directory js --output ./Bundle
1 Like

I was just building with

swift package --swift-sdk 6.1-RELEASE-wasm32-unknown-wasi js --use-cdn

I used your suggested command (which is a life saver thank you!)

$ swift package --swift-sdk 6.1-RELEASE-wasm32-unknown-wasi -c release plugin --allow-writing-to-package-directory js --output ./Bundle

The .wasm file is now 50mb. Still not the almost 20mb it was when building with 5.10 and Carton.

Is there an optimization option that I could pass?

The major binary size difference in the recent versions is ICU data. Recent Foundation brought full-featured ICU data into our binary, so you might be able to reduce the size into what we had before by replacing the data with slimmed version: GitHub - GoodNotes/swift-icudata-slim: A slimmed-down version of ICU for Swift

I did not realize at all that the increase in ICU data would cause an approaching 30MB (I fathom that some of the size increase is from other stuff too?) . I suppose it is not awful. The site will just take about twice as long to load, which given the niche-ness of it that is ok.

Lastly is there an argument to get the index.html file to copy into the ./Bundle folder? Other than that (which I can definitely do myself), this looks like it is going to be mostly a good replacement.

I think you actually don't need to copy index.html to ./Bundle. Instead, you can:

$ mkdir dist
$ cat <<EOS > dist/index.html
<!DOCTYPE html>
<html>
<body>
  <script type="module">
    import { init } from "./Bundle/index.js";
    init();
  </script>
</body>
</html>
EOS
$ swift package js --output ./dist/Bundle
$ echo "./dist/Bundle" >> .gitignore
$ # Serve ./dist by your HTTP server
1 Like

So I am a little confused, when I do --wasm-optimizations none with Carton on 5.10, the resulting binary is only about 32MB (with optimization it is about 20MB), but when I run it using the new method under 6.1 the resulting binary is larger than when there is no optimization at all on the old version.

I am wondering if something else is happening here that I am either skipping or missing. Like maybe certain unused functions are not being stripped?

Surly it isn't all ICU tables? Is it?

If you can provide me a reproducible example, I can take a closer look :folded_hands: