Swift compiles to WebAssembly via the official Swift WebAssembly SDK, and JavaScriptKit gives you typed access to browser globals. Any Swift program that can drive a <canvas> through JavaScriptKit runs on Wavedash.
Toolchain
Apple's bundled toolchain doesn't ship a wasm-capable clang, so install a standalone Swift 6.3+ toolchain from swift.org. swiftly is the easiest path:
swiftly install 6.3.0
Then install the Swift WebAssembly SDK once:
swift sdk install https://download.swift.org/swift-6.3-release/wasm-sdk/swift-6.3-RELEASE/swift-6.3-RELEASE_wasm.artifactbundle.tar.gz \
--checksum 9fa4016ee632c7e9e906608ec3b55cf13dfc4dff44e47574c5af58064dc33fd9
Build for web
In your Package.swift, add JavaScriptKit and its PackageToJS plugin. Build with:
swift package --swift-sdk swift-6.3-RELEASE_wasm js -c release
cp -R .build/plugins/PackageToJS/outputs/Package/. dist/
cp Public/index.html dist/
The PackageToJS plugin emits the WASM binary plus a loader in .build/plugins/PackageToJS/outputs/Package/. Point upload_dir at the dist/ folder you assemble.
SDK integration
Wavedash injects window.Wavedash before your wasm loads. Progress reporting happens in two phases:
- JS loader (
index.js) — streamPong.wasmbyte-by-byte, reporting 0 → 0.95 as bytes arrive. - Swift (
main.swift) — callupdateLoadProgressZeroToOne(1.0)theninit()once the game is ready.
In your index.js entrypoint (generated by PackageToJS, then customized):
const sdk = await window.Wavedash;
sdk.updateLoadProgressZeroToOne(0);
const response = await fetch(new URL("Pong.wasm", import.meta.url));
const total = +response.headers.get("Content-Length") || 0;
let bytes;
if (!total || !response.body) {
bytes = new Uint8Array(await response.arrayBuffer());
} else {
const reader = response.body.getReader();
const chunks = [];
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
sdk.updateLoadProgressZeroToOne((received / total) * 0.95);
}
bytes = new Uint8Array(received);
let pos = 0;
for (const c of chunks) { bytes.set(c, pos); pos += c.length; }
}
sdk.updateLoadProgressZeroToOne(0.95);
// then instantiate and run as normal
In your Swift entry point, once the game is ready to play:
import JavaScriptKit
let window = JSObject.global
// Set up canvas, input, game loop with requestAnimationFrame...
if let sdk = window.Wavedash.object {
_ = sdk.updateLoadProgressZeroToOne!(1.0)
let opts = JSObject.global.Object.function!.new()
opts.debug = .boolean(true)
_ = sdk.`init`!(opts)
}
Calling async SDK methods (leaderboards, UGC, etc.) works through JavaScriptKit's JSPromise — see the SDK setup and Functions reference pages for the full API.
wavedash.toml
game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./dist"
entrypoint = "index.html"
The example repo commits a prebuilt dist/ so you can run wavedash dev without a Swift wasm toolchain. Regenerate it by re-running the swift package ... js -c release command whenever you change Swift sources.