Introduction
rasc compiles a variant of TypeScript — Rive AssemblyScript — to WebAssembly, and runs it on every Rive runtime.
rasc is a self-contained C++ compiler. There is no Node toolchain, no npm install,
no bundler: one binary takes .as sources and emits a WebAssembly module,
typically in 200–500 ms for a project-scale build. The language is
AssemblyScript — TypeScript syntax over WebAssembly types — extended with the
features Rive content actually needs: closures, value classes, allocation sinking,
SIMD, and a versioned host binding surface.
export function fib(n: i32): i32 {
let a = 0, b = 1;
for (let i = 0; i < n; i++) {
let t = a + b;
a = b;
b = t;
}
return a;
}
Sources use the .as extension. If it compiles with upstream
asc, rasc produces a byte-identical module by default — every rasc
extension is additive, and every optimization is opt-in.
From a WebAssembly perspective
Like upstream AssemblyScript, rasc gives you WebAssembly’s type system
directly: i32, i64, f32, f64, and
with SIMD enabled, v128. Numeric code compiles to the instructions you
would write by hand — an f32 stays in a wasm local, register-resident
end to end. This is the structural reason rasc code is fast: there is no
tagged value, no interpreter register file in linear memory, no boxing between your
arithmetic and the hardware.
From a JavaScript perspective
You also get a familiar standard library — Array<T>,
String, Map<K,V>, StaticArray<T>,
typed arrays, Math — with a garbage-collected managed heap when you want
it and a bump-allocator stub runtime when you don’t. rasc goes further than
upstream here: functions capture enclosing locals (real closures, upstream’s
oldest open request), and struct declarations give you C-struct semantics
inside the TypeScript syntax you already know.
Where it runs
A compiled module is engine-neutral wasm. Rive runs it four ways, chosen per platform rather than per rewrite:
- Browser engine — on web, scripts run directly as browser wasm. No WAMR, no second VM.
- WAMR fast interpreter — the dev-loop and fallback tier. Instant instantiation on every edit.
- wamrc AOT — the ship tier. Native-parity machine code, signed and bundled with the app.
- Hardware-bounds AOT — opt-in where the platform allows guard pages, roughly 20–30% faster again.
See Execution Lanes for the measured story.
Frequently asked questions
Is rasc a fork of AssemblyScript?
It is a from-scratch C++ implementation of the language, not a patched copy of the Node compiler. Compatibility is a hard rule — the test suite sweeps fixtures across runtimes and optimization levels for byte-identical default output — but the implementation owns its whole pipeline, which is what makes extensions like closures and value classes tractable.
How is this different from TypeScript?
Same syntax, different discipline. Types are real: an i32 is a 32-bit
integer, not a number that hopes. There is no any, no runtime type
juggling, no DOM. Code that type-checks compiles to static wasm with no interpreter
underneath.
How is this different from Luau scripting in Rive?
They are two compilers targeting the same substrate — WAMR is the only runtime on
native, and both languages answer the same module ABI. Luau is the drop-in tier for
existing content and dynamic scripting; rasc is the C-class lane for numeric-heavy
work. A .riv file can carry both, one module per language per file.
Can I bring existing C or C++ code?
Porting is the intended path, and it is pleasant: the Draco and Box2D ports mirror their C sources closely — value classes were designed so the free-function C style lands 1:1 — and both ship with differential harnesses proving bit-exact parity against the reference binaries.
Why "rasc"?
Rive AssemblyScript Compiler. The CLI is rasc, sources are
.as, and the name stays lowercase — it is a tool, not a brand.