A very minimal tool for packaging Swift WebAssembly products. Forked from Carton.
Unlike the original Carton, the central idea of Archer is to only do things that are not better handled by other tools. Archer emphasizes transparency, user-friendliness, and ease of integration into existing projects.
Archer supports Linux and macOS. We provide prebuilt binaries for several platforms.
Platform | Architecture | Download |
---|---|---|
macOS 15 | arm64 | tar.gz |
Ubuntu 24.04 | arm64 | tar.gz |
Ubuntu 24.04 | x86_64 | tar.gz |
Ubuntu 22.04 | arm64 | tar.gz |
Ubuntu 22.04 | x86_64 | tar.gz |
Download the correct binary for your platform from the table above, extract it, and add the archer
binary to your PATH
. The pre-built Linux binaries do not require the Swift runtime to be installed on the system.
We recommend installing the esbuild
and binaryen
tools on your system.
# Linux
sudo apt install esbuild binaryen
# macOS
brew install esbuild binaryen
ESBuild is a popular TypeScript/JavaScript compiler, and must be installed in order to build the frontend of a WebAssembly project.
Binaryen is a WebAssembly optimizer, which is optional but recommended for use with Archer, as it significantly reduces the size of the final WebAssembly binary.
These steps are not specific to Archer, this section is a general guide to compiling WebAssembly from Swift.
If you have not done so already, install the Swift WebAssembly SDK:
swift sdk install \
https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.0.2-RELEASE/swift-wasm-6.0.2-RELEASE-wasm32-unknown-wasi.artifactbundle.zip \
--checksum 6ffedb055cb9956395d9f435d03d53ebe9f6a8d45106b979d1b7f53358e1dcb4
Important: On macOS, you must use one of the downloadable toolchains from swift.org. The default Xcode toolchain does not support WebAssembly. You can (temporarily) point the
swift
command to the downloaded toolchain by runningexport TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/$TOOLCHAIN_NAME.xctoolchain/Info.plist)
, whereTOOLCHAIN_NAME
is a string such asswift-6.0.3-RELEASE
.
Below is a trivial SwiftPM template you can use to get started with WebAssembly.
π Sources
- π Hello
- ποΈ Hello.swift
ποΈ Package.swift
// swift-tools-version:6.0
import PackageDescription
let package:Package = .init(name: "WebAssembly Example",
products: [
.executable(name: "Hello", targets: ["Hello"]),
],
dependencies: [
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.21.0"),
],
targets: [
.executableTarget(name: "Hello",
dependencies: [
.product(name: "JavaScriptKit", package: "JavaScriptKit"),
]),
])
import JavaScriptKit
guard
case .object(let div) = JSObject.global.document.createElement("div")
else
{
fatalError("Could not create elements")
}
div.innerHTML = .string("Hi Barbie!")
_ = JSObject.global.document.body.appendChild(div)
Use the command below to build the project, replacing SWIFTWASM_SDK
as needed.
SWIFTWASM_SDK=swift-wasm-6.0.2-RELEASE
swift build -c release \
--product Hello \
--swift-sdk $SWIFTWASM_SDK \
-Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor \
-Xlinker --export=__main_argc_argv
Yes, all of the trailing build flags are necessary.
USAGE: archer init [--wasm-path <wasm-path>] [--js-name <js-name>] [--js-runtime <js-runtime>] --bundle <bundle>
OPTIONS:
-w, --wasm-path <wasm-path>
Where the browser should load the WebAssembly (wasm) binary from (default: main.wasm)
-m, --js-name <js-name> What to name the generated JavaScript file (default: main.js)
-J, --js-runtime <js-runtime>
Where to find the JavaScriptKit runtime (default:
.build/release/JavaScriptKit_JavaScriptKit.resources)
-o, --bundle <bundle> Where to write the generated resources to
-h, --help Show help information.
A WebAssembly bundle consists of the resources which will be deployed to the cloud and served to clients. The archer init
command will build a minified JavaScript runtime for you and write it to a directory of your choice.
RUNTIME_NAME="runtime.js"
WASM_NAME="main.wasm"
archer init -m $RUNTIME_NAME -w $WASM_NAME -o Bundle
You need to provide the expected URL for the WebAssembly binary (main.wasm
) and a path to a version of the JavaScriptKit runtime matching the version of JavaScriptKit you are using to build the WebAssembly.
There is no requirement to have built the project at this stage, but the easiest way to obtain the correct version of the runtime is to simply build the project (in release
mode) with SwiftPM.
You probably also want to generate an HTML stub for previewing the WebAssembly, although in a larger project you would likely get this from elsewhere.
(
cat <<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebAssembly Example</title>
<script type="module" src="$RUNTIME_NAME"></script></head>
<body>
<h1>WebAssembly Example</h1>
</body>
</html>
EOF
) > Bundle/index.html
USAGE: archer crush [<wasm-file>] [--output <output>] [--Xwasm-opt <Xwasm-opt> ...] [--preserve-debug-info]
ARGUMENTS:
<wasm-file> Where to find the WebAssembly (wasm) binary to crush
OPTIONS:
-o, --output <output> Where to write the optimized WebAssembly (wasm) binary (default: main.wasm)
--Xwasm-opt <Xwasm-opt> Extra flags to pass to the WebAssembly optimizer
-g, --preserve-debug-info
Whether to preserve debug info or not
-h, --help Show help information.
The archer crush
command is used to optimize a WebAssembly binary. The raw .wasm
output of the Swift compiler will work out-of-the-box, but archer crush
can make it much smaller and faster.
archer crush .build/release/Hello.wasm -o Bundle/$WASM_NAME
Once you have added the WebAssembly file to your bundle, you can use esbuild
to preview it in a browser.
esbuild --servedir=Bundle
Archer is Apache 2.0 licensed.