Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@live-codes/scala-wasm

Client-side Scala 3 for LiveCodes: compile and run Scala entirely in the browser, no server.

This is a thin adapter. The compiler is scala-toolchain-wasm — the Scala 3 compiler and the Scala.js linker compiled to WebAssembly, with a browser host runtime that ships inside the distribution. We pin a release, verify it, ship it, and adapt its API to the shape LiveCodes consumes.

const runtime = await ScalaWasm.createRuntime(); // baseUrl defaults to the bundle's own URL
await runtime.warmUp();                          // optional: move the one-off cost off the first run

const result = await runtime.run(`
@main def hello(): Unit =
  val squares = (1 to 5).map(n => n * n)
  println("squares: " + squares.mkString(","))
`);
// result: { ok, output, error, exitCode, compileMs, linkMs, runMs, diagnostics }

// stdin is a preloaded buffer:
const echo = await runtime.run(
  '@main def echo(): Unit = println(scala.io.StdIn.readLine())',
  { input: 'hello\n' },
);
// echo.output === "hello"

runtime.destroy();

Run from a clone — no install, no build

dist/ and vendor/ are committed, so a fresh clone runs as-is. Only Node 18.17+ is required:

git clone https://github.com/live-codes/scala-wasm.git
cd scala-wasm
npm start                 # http://localhost:8932/poc/

Open http://localhost:8932/poc/ for the automated smoke cases, or http://localhost:8932/playground-scala.html for the interactive playground.

npm test also needs no install: it verifies the committed toolchain against the pinned sha256 checksums (npm run verify) and checks the bundle parses and keeps its capability gate. Rebuilding is only necessary after changing src/ or bumping the toolchain — see Build.

Why it is its own package

It shares the interface with @live-codes/browser-jvm (the createRuntime → run(source, {input}) shape, npm/jsDelivr delivery, lazy per-language loading) but no tooling: different artifacts, its own host runtime/worker/virtual-FS/module loader, a different browser gate (JSPI vs WasmGC), a different licence and a different upstream. LiveCodes' convention is a package per language toolchain (fsharp-wasm, rust-wasm, go-wasm, …), and this follows it.

Loading from a CDN

Point baseUrl at the directory containing toolchain/ and scala-wasm-worker.iife.js — normally .../dist/:

<script src="https://cdn.jsdelivr.net/npm/@live-codes/scala-wasm@0.0.1/dist/scala-wasm.iife.js"></script>
<script>
  const runtime = await ScalaWasm.createRuntime({
    baseUrl: 'https://cdn.jsdelivr.net/npm/@live-codes/scala-wasm@0.0.1/dist/',
  });
</script>

Browsers forbid new Worker() on a cross-origin URL, and a CDN URL is cross-origin. The package therefore also ships the host worker pre-bundled as dist/scala-wasm-worker.iife.js. The adapter bootstraps a classic worker from a small data: URL containing importScripts("<worker URL>") — not a blob, and not a copy of the worker source. No COOP/COEP headers, no worker proxy, and no same-origin hosting are required. A host with a restrictive Content-Security-Policy must allow worker-src data:.

It does not fall back

This package never imports another engine. When it cannot run a program — or cannot fake the stdin it was given — it refuses with a typed error and leaves the response to the host:

Situation Behaviour
No JSPI (Firefox, Safari) createRuntime() throws ScalaUnsupportedError
Stdin with an object main or parameterless @main faked from options.input
Stdin with a parameterized @main run() throws ScalaStdinUnsupportedError

runtime.capabilities reports supported, missingFeatures, supportsWasmTarget, supportsMacros, versionMismatch and the toolchain versions, so the decision can be made before loading anything heavy.

Stdin

Pass input to run. The adapter discovers the entry point, then compiles a launcher that calls it inside Console.withIn and sets System.in:

  • scala.io.StdIn.readLine() (and the typed readInt/readDouble/… methods), scala.Console.in, System.in.read(), and BufferedReader(new InputStreamReader(System.in)) read the supplied buffer.
  • The buffer is one-shot and non-interactive. Once exhausted, readLine() returns null and the typed StdIn readers throw EOFException.
  • It works with object Main { def main(args: Array[String]) } and parameterless @main methods, including in packages. A parameterized @main cannot be called from the launcher and raises ScalaStdinUnsupportedError.
  • java.util.Scanner and scala.io.Source.stdin have no working Scala.js implementation.

Browser support

Chrome / Edge 137+ only — the compiler is built with the Scala.js WebAssembly backend and needs JSPI (WebAssembly.JSTag / Suspending / promising). No SharedArrayBuffer, so no COOP/COEP headers are required.

Measured (headless Chrome 152, localhost)

first steady state
compile ~1.4 s 26–36 ms
link ~0.6 s ~0.45 s
run ~5 ms ~5–10 ms
edit→run wall ~2.0 s ~0.5 s

Cold to interactive is init + warmUp() (the one-off classpath scan and runtime-IR parse) ≈ 9.5 s excluding download. A macro-free Wasm session downloads 46.5 MB; the 23 MB macro IR is fetched only if a program actually uses macros.

Known gaps

  • Stdin is emulated, not interactive. options.input is preloaded into Console.in and System.in, so scala.io.StdIn, System.in, and BufferedReader work. java.util.Scanner and scala.io.Source.stdin do not (Scala.js library gaps), and parameterized @main methods cannot be wrapped.
  • Scala 3 only. (The pinned toolchain is Scala 3.8.3 / Scala.js 1.20.2.)
  • Macros work but cost ~80 s the first time (a second linked compiler + 22 MB of IR).
  • Single-maintainer upstream, no npm package — hence the pinned tag and per-asset checksums.

Build (maintainers)

A clone needs none of this. Rebuild when you change src/ or bump the pinned toolchain.

npm install
npm run vendor:scala   # fetch the pinned release (~58 MB tarball, resumable), verify sha256, extract
npm run build          # esbuild the adapter + worker bundle, copy the distribution into dist/toolchain/
npm test               # verify committed checksums + static smoke (runs without install too)
npm run check:dist     # fail if either committed bundle no longer matches its source
npm run serve          # http://localhost:8932/poc/

npm run vendor:scala is a no-op once the pinned distribution is vendored; pass --force to re-download and re-extract. prebuild runs it automatically, so after npm install, npm run build is enough.

Open poc/scala.html for the browser smoke (hello, case class, structured diagnostics, stdin refusal).

Publishing

npm publish runs prepublishOnly (npm run check:dist && npm test), so a stale bundle or a checksum mismatch blocks the release. publishConfig.access is public; the package is scoped and would otherwise default to restricted. Check the tarball with npm pack --dry-run — roughly 58 MB, almost all of it the pinned toolchain.

Artifact provenance and licences

Artifact Source Licence
dist/toolchain/compiler/main.wasm, main.js, __loader.js scala-toolchain-wasm v0.3.4 (univalence-xyz/scala3-on-wasm@8fdbb99d) Apache-2.0
dist/toolchain/host/*.js, vendor/*.js same Apache-2.0
dist/scala-wasm-worker.iife.js bundled build of the same host worker Apache-2.0
dist/toolchain/classpath/rt.jar OpenJDK java.base from the build JDK GPLv2 + Classpath Exception
dist/toolchain/classpath/scala-lib.jar Scala 3 library Apache-2.0
dist/toolchain/classpath/scalajs-lib.jar, runtime/runtime-sjsir.zip Scala.js Apache-2.0

dist/scala-wasm.iife.js and the adapter source are MIT (LICENSE). Redistributed binaries keep their upstream licences: Apache-2.0 text in licenses/Apache-2.0.txt, GPLv2 + Classpath Exception text in licenses/GPL-2.0-with-Classpath-Exception.txt, and the attribution table plus the upstream NOTICE in NOTICE. All of these ship in the npm package, and every redistributed byte is covered by the per-asset checksums in dist/toolchain/CHECKSUMS.json (npm run verify).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages