With the official release of Swift 6.1, SwiftWasm has also undergone a major upgrade. This milestone update marks the first time SwiftWasm has achieved a build entirely based on the official Swift open-source toolchain—leaving behind the era of custom patches. This change not only significantly simplifies the installation process for developers and greatly reduces the consumption of system storage, but more importantly, the genuine build method drastically lowers platform maintenance costs and injects new vitality into the Swift ecosystem. In this article, we will explore how to build WebAssembly applications using Swift, showcasing the endless possibilities of Swift’s cross-platform development.
Understanding WebAssembly (WASM)
WebAssembly, commonly abbreviated as WASM, is a revolutionary lightweight binary instruction format that enables secure and efficient execution in modern browsers and other compatible environments. It was designed to provide a truly cross-platform, high-performance execution environment, effectively addressing JavaScript’s performance shortcomings in handling compute-intensive tasks.
As an intermediate code format, WASM breaks through the limitations imposed by programming languages, allowing developers to write code in languages such as C/C++, Rust, Go, and Swift, and compile it into a unified WASM bytecode. This feature greatly expands the technical boundaries of web development and provides developers with unprecedented freedom of choice.
The Swift community began exploring the integration of Swift and WASM as early as 2020. However, a real breakthrough occurred after the release of Swift 6.1—Swift’s support for WASM finally reached maturity as the official toolchain and SwiftWasm became integrated, leading to a qualitative leap in the development experience (including enhancements such as VSCode integration and swift-testing support). Today, for developers familiar with Swift, the technical threshold for building WASM applications has been significantly lowered, making it an ideal time to experience the charm of Swift’s cross-platform development.
Installing the Required Tools
Since SwiftWasm does not support the Swift toolchain integrated within Xcode, developers need to install the open-source Swift toolchain provided officially. The most convenient method currently is to manage Swift versions with swiftly.
Installing swiftly
swiftly is a convenient version management tool for the Swift toolchain, supporting rapid installation, switching, and updating of Swift toolchains. It is compatible with macOS and major Linux distributions, and allows a team to unify the Swift version via a .swift-version
file.
On macOS, it can be installed with the following commands:
curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \
installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \
~/.swiftly/bin/swiftly init --quiet-shell-followup && \
. ~/.swiftly/env.sh && \
hash -r
For Linux installation instructions, please refer to the official installation guide.
After installation, swiftly will automatically download the latest Swift toolchain. You can check the installed toolchain versions with the following command:
% swiftly list
Installed release toolchains
----------------------------
Swift 6.1.0 (in use) (default)
Installed snapshot toolchains
-----------------------------
Note: If Xcode is also installed on macOS, you must verify the order of the PATH environment variable to ensure that the
swift
command calls the toolchain installed by swiftly.
Installing the WebAssembly Swift SDK
Starting from SwiftWasm 6.1, it is only necessary to install the SDK for compiling WASM on top of the existing Swift toolchain.
After confirming that the Swift toolchain is installed, execute the following command to install the WebAssembly Swift SDK:
swift sdk install "https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.1-RELEASE/swift-wasm-6.1-RELEASE-wasm32-unknown-wasi.artifactbundle.zip" --checksum "7550b4c77a55f4b637c376f5d192f297fe185607003a6212ad608276928db992"
It is especially important to note that from version 6.1 onward, the Swift toolchain and WASM SDK versions must correspond exactly. If Swift is upgraded, you must install the corresponding version of the SDK simultaneously.
After installation, verify that the SDK is successfully installed using the following command:
% swift sdk list
6.1-RELEASE-wasm32-unknown-wasi
Installing Wasmtime
Although most WASM code runs in browsers, we can also run WASM code in non-browser environments using compatible platforms like Wasmtime.
Wasmtime provides a rich set of APIs that enable WASM code to interact with the host environment via the WASI standard—somewhat similar to the relationship between Node and JavaScript.
It can be installed as follows:
curl https://wasmtime.dev/install.sh -sSf | bash
Configuring VSCode
The latest vscode-swift extension now fully supports WASM. Once installed, it allows you to easily write and test Swift code in VSCode and related editors (such as Cursor, WindSurf, Trae, etc.).
For a better development and debugging experience, it is recommended to also install plugins such as SwiftFormat, SwiftLint, and CodeLLDB.
At this point, all preparations for building WASM applications with Swift have been completed, and you can officially begin your development journey.
Writing Your First WASM Project
Creating the Hello Wasm Project
First, create a project directory and enter it:
mkdir HelloWASM && cd HelloWASM
Create a new Swift executable package:
swift package init --type executable
Check the name of the installed WASM SDK:
swift sdk list
# Expected output:
6.1-RELEASE-wasm32-unknown-wasi
Compile the project using the SDK name mentioned above:
swift build --swift-sdk 6.1-RELEASE-wasm32-unknown-wasi
# Expected compilation output:
Building for debugging...
[8/8] Linking HelloWASM.wasm
Build complete! (1.78s)
Run the WebAssembly project using Wasmtime:
wasmtime .build/wasm32-unknown-wasi/debug/HelloWASM.wasm
# Expected output:
Hello, world!
Configuring the VSCode Project
In practice, an IDE is typically used for compiling, running, and debugging. After opening the project directory in VSCode, the vscode-swift
extension will automatically generate a launch.json
file in the .vscode
directory.
Within the .vscode
directory, create a settings.json
file to specify the path to the swiftly toolchain:
{
"swift.path": "/Users/yangxu/.swiftly/bin/"
}
Next, create a tasks.json
file to define a custom build task:
{
"version": "2.0.0",
"tasks": [
{
"label": "swift: Build HelloWASM",
"type": "shell",
"command": "swift",
"args": [
"build",
"--swift-sdk",
"6.1-RELEASE-wasm32-unknown-wasi",
"--product",
"HelloWASM"
],
"group": "build",
"problemMatcher": [],
"detail": "Custom build for WASM target"
}
]
}
Modify the launch.json
file to ensure that the debugging program path points to the WASM executable and that the pre-launch tasks are set correctly:
{
"configurations": [
{
"type": "swift-lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:HelloWASM}",
"name": "Debug HelloWASM",
"program": "${workspaceFolder:HelloWASM}/.build/debug/HelloWASM",
"preLaunchTask": "swift: Build Debug HelloWASM"
},
{
"type": "swift-lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:HelloWASM}",
"name": "Release HelloWASM",
"program": "${workspaceFolder:HelloWASM}/.build/release/HelloWASM",
"preLaunchTask": "swift: Build Release HelloWASM"
}
]
}
Finally, press Cmd + Shift + P
and select Reload Window
to reload the VSCode window.
Now you can take advantage of VSCode’s built-in debugging features to develop and debug your Swift WASM application.
Creating a Browser-Based WASM Project
In most cases, WASM projects run in browser environments and interact with web page content. However, manually writing all the interaction and communication code can be both technically challenging and labor-intensive. Fortunately, the Swift community offers the JavaScriptKit library, which significantly simplifies such tasks.
JavaScriptKit provides a convenient way for Swift to interact with JavaScript. It allows you to access JavaScript objects and functions, create callable closures in JavaScript, perform data type conversions, and even implement multithreading support.
Below, we demonstrate how to create a simple webpage project using WASM for performing addition: the user can input two numbers into the webpage, click a button, and Swift code will calculate the sum and display the result.
Create and enter the project directory:
mkdir SumCalculator && cd SumCalculator
Initialize a Swift executable package:
swift package init --type executable
Add the JavaScriptKit dependency to your Package.swift
:
let package = Package(
name: "SumCalculator",
dependencies: [
.package(url: "https://github.com/swiftwasm/JavaScriptKit.git", from: "0.20.0"),
],
targets: [
.executableTarget(
name: "SumCalculator",
dependencies: [
.product(name: "JavaScriptKit", package: "JavaScriptKit"),
]),
])
Modify the code in Sources/main.swift
to implement the addition functionality and interact with the webpage:
import JavaScriptKit
// Define a function to calculate the sum of two Int32 numbers
func sum(_ first: Int32, _ second: Int32) -> Int32 {
first + second
}
// Get the button element with the id "calculateSum"
let calculateSumElement = JSObject.global.document.getElementById("calculateSum").object!
// Add a click event listener to the button element
_ = calculateSumElement.addEventListener!(
"click",
JSClosure { _ in
// Get the value of the input element with id "firstNumber" and convert it to Int32
let firstNumber = Int32(
JSObject.global.document.getElementById("firstNumber").object!.value
.string!)
// Get the value of the input element with id "secondNumber" and convert it to Int32
let secondNumber = Int32(
JSObject.global.document.getElementById("secondNumber").object!
.value.string!)
guard let firstNumber, let secondNumber else {
_ = JSObject.global.alert!("Input Number")
return JSValue.undefined
}
// Call the sum function to calculate the sum and alert the result
let sum = sum(firstNumber, secondNumber)
_ = JSObject.global.alert!("\(firstNumber) + \(secondNumber) = \(sum)")
return JSValue.undefined
})
// Get the global document object
let document = JSObject.global.document
// Create a div element
var divElement = document.createElement("div")
// Set the text content of the div element
divElement.innerText = "Welcome to WASM Calculator"
// Append the div element to the body
_ = document.body.appendChild(divElement)
Create an index.html
file in the project root:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Swift WASM Demo</title>
</head>
<body>
<div id="calculator">
<input type="number" id="firstNumber" placeholder="First number" value="5">
<input type="number" id="secondNumber" placeholder="Second number" value="3">
<button id="calculateSum">Calculate Sum</button>
</div>
<script type="module">
// Import the WASM module, adjust the path as necessary
import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js";
// Initialize WASM when the page loads
init();
</script>
</body>
</html>
Compile the project for a JavaScript/WebAssembly target:
swift package --swift-sdk 6.1-RELEASE-wasm32-unknown-wasi js --use-cdn
# Expected output:
Building for debugging...
[0/2] Write swift-version--47A8281FCF6E46BB.txt
[1/4] Write sources
[3/6] Compiling SumCalculator main.swift
[4/6] Emitting module SumCalculator
[5/7] Wrapping AST for SumCalculator for debugging
[6/8] Write Objects.LinkFileList
[7/8] Linking SumCalculator.wasm
Build of product 'SumCalculator' complete! (0.67s)
Packaging...
[1/14] .build/plugins/PackageToJS/outputs/Package/SumCalculator.wasm: building
[2/14] .build/plugins/PackageToJS/outputs/Package.tmp/wasm-imports.json: building
[3/14] .build/plugins/PackageToJS/outputs/Package/index.d.ts: building
[4/14] .build/plugins/PackageToJS/outputs/Package/index.js: building
[5/14] .build/plugins/PackageToJS/outputs/Package/instantiate.d.ts: building
[6/14] .build/plugins/PackageToJS/outputs/Package/instantiate.js: building
[7/14] .build/plugins/PackageToJS/outputs/Package/package.json: skipped
[8/14] .build/plugins/PackageToJS/outputs/Package/platforms/browser.d.ts: building
[9/14] .build/plugins/PackageToJS/outputs/Package/platforms/browser.js: building
[10/14] .build/plugins/PackageToJS/outputs/Package/platforms/browser.worker.js: building
[11/14] .build/plugins/PackageToJS/outputs/Package/platforms/node.d.ts: building
[12/14] .build/plugins/PackageToJS/outputs/Package/platforms/node.js: building
[13/14] .build/plugins/PackageToJS/outputs/Package/runtime.d.ts: building
[14/14] .build/plugins/PackageToJS/outputs/Package/runtime.js: building
Packaging finished
- js: Specifies that the build target is for a JavaScript environment using WebAssembly.
- —use-cdn: Automatically loads the required JavaScript dependencies from a CDN during the build.
Start a simple web server in the project root:
python -m http.server
# Expected output:
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
::1 - - [08/Apr/2025 13:26:06] "GET / HTTP/1.1" 200 -
::1 - - [08/Apr/2025 13:26:06] "GET /.build/plugins/PackageToJS/outputs/Package/index.js HTTP/1.1" 200 -
Open your browser and visit http://localhost:8000
to experience a WASM application where Swift and JavaScript interact seamlessly.
Although JavaScriptKit has significantly reduced the complexity of communication between Swift and JavaScript, there remains a learning curve for some Swift developers.
Currently, TokamakUI is one of the simplest methods for building browser applications with SwiftWasm. It offers syntax and APIs very similar to SwiftUI, which greatly lowers the barrier to building web applications with Swift. However, since TokamakUI has not yet been adapted for Swift 6.x and still requires Swift 5.9.x, it is not discussed in this article. Additional content will be provided once TokamakUI becomes compatible with Swift 6.
Conclusion
With the continuous improvements in WebAssembly support within the Swift ecosystem, there is every reason to believe that, in the near future, Swift developers will be able to build vibrant, high-performance, cross-platform web applications in a more natural and seamless manner. As frameworks like TokamakUI mature, the entry barrier will be further lowered, allowing more Swift developers to expand their skills into the web domain.