Contributing#
How to build, test, deploy and release SwiftBrowser itself. If you want to run your own app in the browser instead, start with docs/getting-started.md.
Local development#
You need Swift 6.4.0, its Swift SDK for WebAssembly, and Node 22. On Linux, swiftly installs the toolchain:
swiftly install 6.4.0 --use
swift sdk install \
https://download.swift.org/swift-6.4.0-release/wasm-sdk/swift-6.4.0-RELEASE/swift-6.4.0-RELEASE_wasm.artifactbundle.tar.gz \
--checksum f07b7be3c586d92d7a07051fc6d303b87ebea67eadc40640ba59d5a8b79aa86d
Then:
swift test # unit tests for the shim and reconciler (native Linux)
npm ci && npm run build:packages # the CLI, which builds the examples (and the web renderer's Node side)
scripts/install-binaryen.sh # once: wasm-opt, which shrinks the modules by about half
scripts/build-wasm.sh # builds every Examples/<App> through the CLI into Web/public/<App>.wasm
scripts/snapshot-test.sh # runs them headless (events + virtual clock, en_US) and diffs the op streams (--update to accept)
cd Web && npm run dev # serves the apps in an iPhone frame at http://localhost:5173
Each example's __snapshots__/events.txt is the script the snapshot replays,
one event per line, naming elements by what they show rather than by id:
tap:"Add", text:"Name":Paul, select:"Sort":"Rating", back. A selector
that matches nothing or more than one element fails the run
(docs/ops-protocol.md).
The dev server shows Counter by default and any other example at
?app=Todos. It watches Sources/ and Examples/, rebuilds the Wasm module
on save, and reloads the page with the previous @State values restored
where the view tree still matches (see the state snapshot section of
docs/ops-protocol.md).
scripts/build-wasm.sh Todos # one app only
Do not build the package on macOS: the shim module is named SwiftUI and
collides with the system framework. On a Mac, open the example in Xcode as
plain SwiftUI, or let CI's ios-source-compat job type-check it for you.
Release modules go through Binaryen's wasm-opt -Oz --strip-debug when
scripts/install-binaryen.sh has been run (CI always does): a small example
drops from 35 MB to 17.4 MB (4.5 MB with brotli). The dev server's hot
rebuilds skip the step to stay fast and keep function names in stack traces.
Deploying the examples#
The deploy job of .github/workflows/ci.yml
publishes the web renderer with every example to Cloudflare Pages, so the apps
can be opened in a real mobile browser. It takes the modules the examples
matrix built, so it compiles no Swift. Pushes to main deploy the production site;
every pull request gets a preview deployment on a branch alias and a comment
with the links. The deployed site has a landing page at / that lists the
apps with QR codes, and runs each app at /app/?app=<Name>. On a phone the
app page drops the fake iPhone frame and uses the real screen and safe areas;
in Safari, Share → Add to Home Screen runs it full screen.
The job needs two repository secrets (Settings → Secrets and variables → Actions); without them it still builds the site, and skips the deploy:
| Secret | Value |
|---|---|
CLOUDFLARE_API_TOKEN |
an API token created at dash.cloudflare.com → My Profile → API Tokens with the Cloudflare Pages: Edit permission |
CLOUDFLARE_ACCOUNT_ID |
the account id from the dashboard URL (dash.cloudflare.com/<account id>) |
The Pages project is named swiftbrowser (CLOUDFLARE_PAGES_PROJECT in the
workflow); the workflow creates it on the first run if it does not exist. The site is
then at https://swiftbrowser.pages.dev, and previews at
https://<branch>.swiftbrowser.pages.dev.
Deploying the docs#
The documentation site in DocsSite/ is built from the
Markdown in this repository (the root README, docs/, the package READMEs and
this file) by .github/workflows/docs.yml,
which deploys it to the Cloudflare Pages project swiftbrowser-docs
(https://swiftbrowser-docs.pages.dev) with the same two secrets. Edit the
Markdown, not the site; cd DocsSite && npm install && npm run dev previews
it at http://localhost:5174.
Releasing to npm#
.github/workflows/publish.yml publishes the
four packages (@swiftbrowser/cli, @swiftbrowser/web, @swiftbrowser/xcodeproj,
@swiftbrowser/swift) together, at one version, with npm provenance while the
repository is public (npm rejects provenance from a private one, so a private
repository publishes without it). Start it
from Actions → Publish to npm → Run workflow with the version to release
(0.2.0, or patch / minor / major). The run bumps every manifest and the
lockfile (npm run set-version <version> does the same locally), runs the
typecheck and unit tests, builds, commits Release vX.Y.Z to the branch it
ran on, tags it, publishes the packages in dependency order and creates the
GitHub release with generated notes. Tick dry run to do all of that except
the push and the publish. Pushing a vX.Y.Z tag by hand also publishes, if
the manifests already carry that version.
Publishing authenticates in one of two ways:
| Option | Setup |
|---|---|
NPM_TOKEN secret |
Settings → Secrets and variables → Actions: a granular access token with read and write access to the @swiftbrowser packages, allowed to bypass 2FA |
| Trusted publishing | On npmjs.com, each package's Settings → Trusted Publisher: this repository, workflow publish.yml. No secret; the workflow's OIDC token is enough |
A release that failed half-way is resumed by running the workflow again with the same version: the job publishes from the commit the existing tag points at, packages already on the registry are skipped and an existing release is reused.
Continuous integration#
.github/workflows/ci.yml runs these jobs on every
push and pull request:
swift-tests: native Linux build andswift test.hello-wasm: buildsHelloWasmfor Wasm and runs it in wasmtime, a canary for the SDK install.examples: a matrix with one runner per example (the list is in the workflow; thewasmjob fails when anExamples/directory is missing from it), each building its module through the CLI and uploading it. Serially the seven took 13 minutes; in parallel the stage takes one build.wasm: downloads the modules, runs every example's headless snapshot under Node's WASI, type-checks and unit-tests the workspaces, builds the web renderer and runs the Playwright tests against the real modules in Chromium. Needs no Swift.deploy: builds the site from the matrix's modules and publishes it to Cloudflare Pages (see "Deploying the examples").cli: the CLI against Hacking with SwiftUI's Xcode projects.ios-source-compat: type-checks every example against Apple's SwiftUI with the iOS simulator SDK on macOS.
.github/workflows/docs.yml runs when Markdown
or DocsSite/ changes: it type-checks, tests and builds the docs site, and
deploys it (see "Deploying the docs").
Layout#
Sources/SwiftUI/ the shim: API/ (public SwiftUI surface) and Runtime/ (state, reconciler, ops)
Sources/HelloWasm/ smallest possible Swift-on-Wasm program
Examples/Counter/ the first app and its headless snapshot
Examples/Todos/ navigation, lists, input and @Observable in one app
Examples/Gallery/ layout, animation, sheets and typography screens
Examples/Settings/ tabs, forms, pickers, path navigation, tasks and GeometryReader
Examples/FoodTruck/ Apple's Food Truck sample, trimmed: Foundation, asset catalogs, ObservableObject
Sources/FoundationBridge/ what apps need from Foundation that corelibs lacks on WASI: Timer, UserDefaults, Bundle, URLSession, AsyncImage, Locale
Sources/CoreImage/ Core Image's built-in filters and QR code generator, as recipes the page draws
Examples/HackingWithSwift/ five Hacking with SwiftUI projects: gestures, swipe actions, edit mode, alerts, controls
Examples/CupcakeCorner/ Hacking with SwiftUI project 10: AsyncImage and URLSession over the browser's fetch
Examples/PhotoPicker/ PhotosUI's PhotosPicker over the browser's file chooser
Examples/Filters/ Core Image: Instafilter's filters on a photo, Hot Prospects' QR code
Examples/HotProspects/ Hacking with SwiftUI project 16 (no Me tab): the CodeScanner and UserNotifications stand-ins
Web/ Vite + TypeScript renderer and device frame
DocsSite/ the documentation site, built from the repository's Markdown
Tests/SwiftUITests/ swift-testing suite for the shim
scripts/ build-wasm.sh, snapshot-test.sh, run-wasi.mjs, set-version.mjs (release versions)
docs/ getting started, compatibility, architecture, the ops protocol and the history