[ANN] (Experimental) WasmTarget.jl & Therapy.jl: Julia-to-WebAssembly compiler, with a full-stack signals framework built on it

[ANN] (Experimental) WasmTarget.jl & Therapy.jl: Julia-to-WebAssembly compiler, with a full-stack signals framework built on it

I’m “announcing” two new packages, both registered in General. They might be usable, might not. The main goal is feedback from actual subject-matter experts, and to put these in front of anyone who wants to play around with them, like I do.

WasmTarget.jl: compiles real Julia functions to WebAssembly. It reads Julia’s fully-inferred IR (Base.code_typed()) and emits WasmGC bytecode directly. Targets modern WasmGC types (struct, array, externref) so Vector{T} and user structs map naturally instead of fighting linear memory.

  • Current scope: 176 Julia functions compile and produce correct E2E results across Int32 / Int64 / UInt32 / UInt64 / Float32 / Float64. 127 are native paths (real Base IR), and 48 are overlay reimplementations for IR tangled with GC internals or libm foreigncalls. This is the same pattern CUDA.jl / GPUCompiler.jl uses. 2409 tests passing. Optional wasm-opt pass yields ~85% size reduction with no behavioral regressions.
using WasmTarget: compile_multi

square(x::Float64)::Float64 = x * x
cube(x::Float64)::Float64   = x * square(x)

# Both functions in one WASM module. `cube` calls `square` inside the binary.
bytes = compile_multi([(square, (Float64,)), (cube, (Float64,))])
write("cubic.wasm", bytes)

Therapy.jl: a full-stack web framework with fine-grained signals (in the SolidJS / Leptos sense) and Astro-inspired islands architecture. Components are written in a JSX-style call syntax like Div(Button(...), Span(...)), but it’s plain Julia function composition, no macro or template DSL.

  • Islands: most of the page is static HTML, and only the parts that actually need reactivity ship JS / WASM. With Therapy each @island function compiles to its own per-island WASM module via WasmTarget. You write SSR layouts and reactive components in plain Julia; the build emits static HTML plus tiny WASM modules (1 to 12 KB per island) that hydrate on demand.
using Therapy: Div, Button, Span
using Therapy: @island, create_signal, create_memo, create_effect, js

@island function InteractiveCounter(; initial::Int = 0)
    count, set_count = create_signal(initial)
    doubled = create_memo(() -> count() * 2)
    create_effect(() -> js("console.log('count:', $1, 'doubled:', $2)", count(), doubled()))

    return Div(
        Div(
            Button(:on_click => () -> set_count(count() - 1), "-"),
            Span(count),
            Button(:on_click => () -> set_count(count() + 1), "+")
        ),
        Span("doubled ", Span(doubled))
    )
end

Both docs sites dogfood Therapy itself:

Both repos were built iteratively with LLM coding agents, so if you’ve shipped real compilers or built real web frameworks and have any interest in a real Julia to Wasm story, I’d love any and all feedback from those of you with much more domain knowledge than me.

Repos:

4 Likes

Neat!

Is this related to the previous (I think abandoned?) work some people did on WebAssemblyCompiler ? The ability to show DiffEq and (modified) Makie running in the browser always made for impressive demo material for Julia presentations.

Not really. That project was before WasmGC and didn’t take advantage of any of the garbage collection features now native to Wasm

1 Like