Summary
- Scala.js has a Wasm backend, but for now it assumes a JavaScript host.
- scala-wasm is a fork where we are experimenting with Wasm modules and Wasm Component Model support without JavaScript. This makes a few things possible:
- running programs safely in a sandbox, such as AI-generated code or user-uploaded untrusted code
- interoperability between components written in different languages, with better protection against supply chain attacks
- very fast cold starts.
- scala-wasm/component-example has examples of Scala + Component Model usage.
- There are also server-side examples/templates with Spin in scala-wasm/scala-wasm-spin-templates.
- Limitations:
- Libraries depending on JavaScript cannot be used as-is. They need a WASI-oriented port. Experimental cats-effect + FS2 WASI work is in progress.
- GC+EH binaries are not supported by SpinKube or Akamai Functions yet. SpinKube support is being worked on.
The API is subject to change without notice. Please refer to the scala-wasm/scala-wasm repository for the latest information.
Introduction
Wasm, WASI, and Wasm Components
Recently, Wasm is used in different areas: safely running AI-generated code, server-side applications with ultra-fast cold starts, and language-independent plugin systems.
What Wasm itself can do is just a pure computation. It cannot affect the outside world directly. Things like file I/O, clocks, and networking have to go through interfaces imported from the host.
So unless the host explicitly gives it access, a Wasm module cannot perform external effects. This is why Wasm is often described as a sandbox.
For example, the following Wasm module does not implement log by itself. run can have an external effect only if the host environment (like JS) passes an import for log. Filesystems, clocks, networking, and so on work in the same way: the host has to provide those interfaces explicitly.
WASI is an effort to standardize these system interfaces. The current version, WASI Preview 2 (and 3), is defined on top of the Wasm Component Model.
The Wasm Component Model gives components typed imports and exports through interfaces written in WIT interface description language. With Component Model, components written in different languages can be composed without depending on the ABI or runtime representation of each different language.
Wasm Components also use a shared-nothing architecture. One component cannot directly access outside worlds’ memory or data. Communication has to go through WIT-defined interfaces. This helps keep the impact of a malicious dependency inside component boundaries, which is good for supply chain security.
I will not go into the details of WASI or the Component Model here. These documents are good starting points:
Scala.js and Wasm
Scala.js has supported a Wasm backend since 1.17.0, so Scala.js programs can be compiled to Wasm modules. However, the current Wasm backend assumes a JavaScript environment such as Node.js or a browser. It cannot directly target Wasm runtimes without JavaScript, such as wasmtime, nor the Wasm Component Model.
To explore that area, we are experimenting in a Scala.js fork called scala-wasm. The fork supports Wasm modules and Wasm Components without JavaScript.
The goal of this fork is to eventually upstream this work into Scala.js. Actually, as a first step, we are working on upstreaming Minimal Wasm, a feature for generating Wasm modules without JavaScript.
https://github.com/scala-wasm/scala-wasm
In this article, I will summarize where Scala and Wasm are today, and show how to compile Scala code into Wasm Components with scala-wasm.
Compiling Scala to Wasm Components
Let’s compile Scala code to Wasm Components with scala-wasm.
[!Disclaimer] scala-wasm is an experimental project. APIs may change significantly across versions.
I will cover Scala/Rust interoperability and a TODO-list server example using Spin, a Wasm framework. For complete code and more examples, see scala-wasm/component-example.
Requirements
- wasm-tools
- wasmtime
- scala-wasm/wit-bindgen
- a scala-wasm-compatible fork
- wkg
- wac
- cargo-component
- Spin canary
The wit-bindgen version compatible with scala-wasm has not yet been upstreamed to bytecodealliance/wit-bindgen, so install it from the fork:
sbt plugin
scala-wasm is published under the io.github.scala-wasm organization. Add this to project/plugins.sbt:
In build.sbt, enable WebAssembly in ESFeatures, and set scalaJSLinkerConfig’s moduleKind to WasmComponent. Then the linker produces a Wasm Component. If there are WIT files under scalaJSWitDirectory, wit-bindgen scala runs at compile time and generates Scala bindings under target/scala-*/src_managed.
Scala + Rust Interoperability with Wasm Components
First, let’s create a project where Scala and Rust call each other through Wasm Components. The example is simple: Scala calls a greeter interface implemented in Rust.
I will only show some snippets here. The full code is in component-example/rust-compose.
First, implement the Scala component for the scala world.
Then we can use the generated bindings in managed sources to call greeter.greet.
On the Rust side, generate bindings from the same WIT with cargo component, and implement the greeter interface. Here we pass the received string to ferris-says, and return the resulting string.
After building both components, use wac plug to compose the Rust component into the Scala component’s import.
From Scala’s point of view, this looks like a normal function call. Under the hood, it crosses a WIT-typed component boundary and calls the Rust implementation.
Running an HTTP + SQLite Component with Spin
Next, let’s implement a server application as a Wasm Component.
Wasm/WASI behaves like a sandbox: it cannot access external resources unless explicit permissions are granted. Therefore, Wasm can be executed directly on Kubernetes without going through a userland layer of Linux container. This makes cold starts extremely fast.
So how do we implement a practical server application in Wasm? If Wasm cannot freely access the outside world, how do we access databases? One current approach is to use a Wasm framework such as Spin.
Here, we will implement a server-side application in Scala using Spin. The full code is in scala-wasm/component-example/spin-todo. The templates are in scala-wasm/scala-wasm-spin-templates.
One caveat: the Wasm Components emitted by Scala.js use Wasm GC and exception handling features, so Spin canary is currently required.
Create a project from the scala-wasm Spin templates:
The TODO API template generates a component that exports WASI HTTP and imports Spin’s SQLite interface.
wasi:http/incoming-handler is the interface for handling incoming WASI HTTP requests. A containerd shim for Wasm containers can use it to bridge incoming requests into a Wasm Component.
Build and start the application with spin up --build. For now, GC and related Wasm features must be enabled with --experimental-wasm-feature.
Once it starts, you can call the HTTP API:
Applications built with Spin can normally be deployed to Kubernetes with SpinKube, or to Akamai Functions for Functions at the Edge. However, at the moment, binaries generated by scala-wasmcannot be deployed to SpinKube. Scala.js-generated Wasm uses features such as WasmGC and exception handling, and those environments do not support them yet.
SpinKube support is being worked on in containerd-shim-spin#444. For Fermyon Cloud and Akamai Functions, I am looking forward to future support.
FAQ
Can I use library XXX?
If the library depends on JavaScript, it cannot be used as-is. Wasm Components are meant to run without a JavaScript runtime. If code paths depending on JS APIs like scala.scalajs.js.Dynamicand @JSImport, linking should fail with an error like this:
In particular, libraries depending on system features such as HTTP, filesystems, processes, and sockets cannot be implemented in Wasm alone. They need to be rewritten to use explicitly imported host interfaces, such as WASI HTTP or WASI filesystem.
For target-specific implementation branching like this, use scala.scalajs.LinkingInfo.linkTimeIf. linkTimeIf is resolved at link time, and the unused branch is dropped from linking. So we can switch between an implementation for a JavaScript host and an implementation for Wasm-without-JS.
In scala-wasm’s javalib, this pattern is used in many places, including System.currentTimeMillis()and the regular expression engine, so that JS-dependent code is not linked for Wasm Components.
While many system libraries doesn’t work for Wasm Components, experimental cats-effect and FS2 Wasm/WASI ports are in progress.
Why not split out Wasm-specific library artifacts?
linkTimeIf is similar to conditional compilation in C/C++ or Rust. Another option would be to add a wasm compile target at build time, like Scala.js or Scala Native. Then libraries would publish wasmartifacts, and dependency resolution could tell whether a library supports Wasm-without-JS.
On the other hand, Scala.js IR for libraries that do not depend on JavaScript can already be used by scala-wasm as-is. Splitting artifacts would mean every library has to republish for Wasm-without-JS, even when the existing Scala.js IR would have worked.
When will this be upstreamed?
The immediate goal is to upstream Minimal Wasm into Scala.js as a stepping stone toward Wasm Component support. Internally, this means upstreaming large changes that remove JavaScript dependencies from JDK APIs and the Wasm backend. After that, the next step will be to upstream Wasm Component API support into Scala.js.




