Compile BSWFoundation for WebAssembly (browser) + unified BSWLogger - #184
Open
piercifani wants to merge 18 commits into
Open
Compile BSWFoundation for WebAssembly (browser) + unified BSWLogger#184piercifani wants to merge 18 commits into
piercifani wants to merge 18 commits into
Conversation
Second step of the WASM effort (after the swift-http-types adoption). The whole package now builds for wasm32 (swift build --swift-sdk ...wasm) and gains a browser network fetcher. - Package.swift: JavaScriptKit (+ JavaScriptEventLoop, JavaScriptFoundationCompat) and swift-log linked WASI-only; HTTPTypesFoundation restricted to the platforms where URLSession exists (its URLSession bridge doesn't compile for wasm). - APIClient+FetchFetcher.swift: FetchNetworkFetcher, an APIClientNetworkFetcher backed by the browser fetch API via JavaScriptKit, wired as the default fetcher on wasm (replacing the fatalError stub). - Logging: OSLog is Apple-only; on wasm a small Logger/OSLogType shim backed by swift-log keeps the existing OSLog-shaped call sites compiling unchanged. - Deferred off wasm for now (follow-up PR): KeychainBacked/UserDefaultsBacked (need localStorage backing) and Observable.stream(for:) (needs a DispatchQueue-free, concurrency-correct reschedule). Platform detection uses `#if os(WASI)` (per swift.org's WASM guide). Router and JSONParser needed no changes; this SDK's Foundation provides JSONSerialization/CharacterSet/date formatters. Apple/Android builds unchanged (guards are additive; Apple build + RouterTests verified green). Compile-verified only; browser runtime validation (a real fetch) is a follow-up via a JavaScriptKit harness. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the per-file `#if os(Android) import AndroidLogging #elseif !os(WASI) import OSLog #endif` dance (which relied on every platform happening to vend a `Logger`/`OSLogType` symbol) with a single, owned, public `BSWLogger`. It forwards to each platform's native backend behind one set of guards: - Apple: OSLog (Console.app / unified logging) - Android: AndroidLogging (logcat) - WebAssembly: swift-log (browser console via a LogHandler) - other: print `APIClient+Logging`, `JSONParser` and `FailableCodableArray` now use `BSWLogger` and no longer import logging modules directly, and apps built on BSWFoundation get one consistent logging surface. Apple + wasm builds and RouterTests are green; the Android branch uses only the AndroidLogging API the code already exercised. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The self-hosted `mobile` runners don't have the WebAssembly Swift SDK, so the new wasm job runs on ubuntu-latest using the official `swift:6.3.3` image as the toolchain, installs the matching wasm Swift SDK, and compiles the package with `swift build --swift-sdk swift-6.3.3-RELEASE_wasm`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
KeychainBacked / CodableKeychainBacked / UserDefaultsBacked / CodableUserDefaultsBacked now work on WebAssembly (previously excluded), backed by a new WASMKeyValueStore that wraps window.localStorage via JavaScriptKit. - New WASMKeyValueStore (#if os(WASI)): string/data get + set (nil removes), no-ops gracefully if localStorage is unavailable. - The property wrappers gain #if os(WASI) branches and are now included on wasm (guard relaxed from `!os(Linux) && !os(WASI)` to `!os(Linux)`); the KeychainAccess import is tightened to `canImport(Darwin)`. Documented warning: localStorage is NOT secure, so KeychainBacked values are not encrypted at rest on wasm. AuthStorage stays Apple-only for now (it isn't cross-platform even on Android); porting it is a separate follow-up. Verified green on Apple (build + UserDefaultsBackedTests), wasm (swift build --swift-sdk), and Android (SKIP_ENABLED=1 skip android build). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- FetchNetworkFetcher now surfaces the response's HTTP header fields, read from the JS Headers object (Array.from → [[name, value], …]) into the returned APIClient.Response's HTTPResponse. - Observable.stream(for:) is no longer excluded on wasm. Reworked into a coordinator whose reschedule captures a Sendable `self` instead of a recursive local closure (which Swift 6 region-based isolation rejected as a `sending` data-race risk). It reschedules with a Task on wasm (the JavaScriptKit event loop) and DispatchQueue.main elsewhere. Verified green: Apple (build + ObservationTests), wasm (swift build --swift-sdk), and Android (SKIP_ENABLED=1 skip android build). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A small executable package that proves BSWFoundation's WebAssembly paths work at runtime, not just at compile time: - a real `fetch` GET through FetchNetworkFetcher, decoded by JSONParser, and - a `localStorage` round-trip through KeychainBacked → WASMKeyValueStore. Bundled with JavaScriptKit's PackageToJS (`swift package … js`). Runs in Node (main.mjs shims localStorage, which node lacks; fetch is built in) and in a browser (index.html; both APIs native there). The built bundle (.wasm) and node_modules are gitignored. Verified locally in Node against swift-6.3.2-RELEASE_wasm: ✅ fetch GET https://httpbingo.org/ip → origin = … ✅ KeychainBacked localStorage round-trip → 'hello-from-wasm' Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "WebAssembly / Browser Support" section to the top-level README (parallel to Android Support) with a from-scratch harness runbook: install the wasm Swift SDK, build via the JavaScriptKit `js` plugin, and run in Node or a browser, incl. expected output and the localStorage-not-secure caveat. Add a matching prerequisite pointer in WASMHarness/README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The test target now builds and runs on wasm — in Node via JavaScriptKit's `js test` — with 44 tests across 12 suites passing. - Link JavaScriptEventLoopTestSupport (wasi-only) so async tests (Task.sleep, observation streams) get the JS event-loop executor instead of hitting an unsupported WASI async-io syscall. - Guard the network / URLSession / file tests in APIClientTests off wasm; the deterministic mock-backed tests run there. Extend APIClientErrorTests' localization expectations to os(WASI) (plain "could not", as on Android/Linux). - Add an `isWASI` test flag and disable the Apple-coupled UserDefaultsBackedTests on wasm (it asserts against UserDefaults.standard; the wasm storage path is covered by WASMHarness). - .github/wasm-prelude.js shims `localStorage` for Node. - CI: the wasm-build job now installs Node + the WASI shim and runs `js test` after the compile gate. Apple/Android builds are unchanged (the test dependency is wasi-conditioned; Apple `swift build --build-tests` verified green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
piercifani
force-pushed
the
feature/wasm-port
branch
from
July 5, 2026 10:04
6bc1ae2 to
8b21d08
Compare
Explain that the ~76 MB figure is a debug artifact: for deployment, build in release with Binaryen's wasm-opt on PATH (so PackageToJS runs its size pass) and serve the wasm brotli/gzip-compressed. Include reference sizes for the WASMHarness bundle (release + wasm-opt ≈ 45 MB raw / ~12 MB brotli) and note the reflection-metadata stripping option and the Foundation baseline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds two consumer-facing subsections to the WebAssembly docs: - Building a browser app on BSWFoundation: the JavaScriptKit event-loop dependencies, installGlobalExecutor(), the PackageToJS `js` plugin, serving, and the UserDefaultsBacked<T> -> CodableUserDefaultsBacked caveat. - Reusing a Swift @observable ViewModel from a JS framework such as React: the globalThis bridge contract plus the Vite /public loading gotcha. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # Sources/BSWFoundation/APIClient/APIClient+Logging.swift # Sources/BSWFoundation/Extensions/Observation+Ext.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
This is the browser WebAssembly support pass for BSWFoundation. The goal is to let the package compile for
wasm32-unknown-wasip1and run inside a browser-hosted SwiftWasm app, while preserving the existing Apple and Android behavior.The main platform split is networking and persistence: browsers do not expose
URLSessionor Keychain-equivalent secure storage to SwiftWasm, so this PR adds browser-specific runtime hooks, fetch-backed APIClient networking, and a narrow localStorage-backedUserDefaultsBackedpath for non-sensitive values.What changed
BSWBrowserRuntime.installJavaScriptEventLoop()so JavaScript hosts can install JavaScriptKit's event-loop executor before starting Swift async work.FetchNetworkFetcher, backed by JavaScriptfetch, and uses it as the default WASM network fetcher while keeping URLSession defaults on platforms that support it.KeychainBackedunavailable on WASM with a clear diagnostic, and backsUserDefaultsBacked/CodableUserDefaultsBackedwith browserlocalStorage.BSWLogger, using OSLog on Apple platforms, AndroidLogging on Android, and swift-log for WASM.swift-android-native; keeps Skip-only storage integrations behindSKIP_ENABLED=1with targeted unavailable stubs when those modules are missing.wasm-buildjob alongside the existing macOS, iOS, and Android jobs.Platform behavior
KeychainBacked,CodableKeychainBacked,UserDefaultsBacked, andCodableUserDefaultsBackedrequire Skip storage modules, so consumers should build withSKIP_ENABLED=1when using those wrappers.fetchand browserlocalStorage. Browser fetch keeps normal browser security behavior for TLS, CORS, and mixed content;Environment.shouldAllowInsecureConnectionsis not emulated.String.hmacremains available becauseswift-cryptois still linked for the target.Verification
Current PR checks are green:
macos-buildios-buildandroid-buildwasm-buildAlso smoke-tested locally during the port:
swift testswift build --swift-sdk swift-6.3.2-RELEASE_androidswift build --swift-sdk swift-6.3.2-RELEASE_wasm