Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hello world (WASI)

This tutorial walks through a minimal Hello, world app that uses WASI APIs and runs on Wasmtime. You build a command component that exports the WASI entrypoint wasi:cli/run, then run it with sbt run or wasmtime run.

This example uses wasi4s, which is a pre-generated WASI bindings, to call WASI from Scala. 1

For a complete project example, see component-examples, the helloworld/ subdirectory and the build.sbt.

Project layout

helloworld-wasi/
  build.sbt
  project/
    plugins.sbt
    build.properties
    build.sbt
  src/main/scala/
    Main.scala

project/

// project/plugins.sbt
addSbtPlugin("io.github.scala-wasm" % "sbt-scalajs" % "1.22.1-wasm.6")

// project/build.properties
sbt.version=2.0.7

// project/build.sbt
// https://github.com/scala-wasm/scala-js-env-wasmtime
// runs the linked component on wasmtime when you sbt run
libraryDependencies +=
  "io.github.scala-wasm" %% "scalajs-env-wasmtime" % "0.1.1"

build.sbt

import org.scalajs.ir.WitScope
import org.scalajs.jsenv.wasmtime.WasmtimeEnv
import org.scalajs.linker.interface.ESVersion
import org.scalajs.linker.interface.WasmComponentModuleInitializerExport
import org.scalajs.linker.interface.WasmComponentModuleInitializerExport._

lazy val root = project
  .in(file("."))
  .enablePlugins(ScalaJSPlugin)
  .settings(
    name := "helloworld-wasi",
    version := "0.1.0-SNAPSHOT",
    scalaVersion := "2.13.18",

    libraryDependencies +=
      "io.github.scala-wasm" %%% "wasi4s" % "0.1.0+wasi-0.2.12",

    scalaJSLinkerConfig ~= {
      _.withESFeatures(_.withUseWebAssembly(true).withESVersion(ESVersion.ES2022))
        .withModuleKind(ModuleKind.WasmComponent)
        .withWasmFeatures(_.withModuleInitializerExport(Some(
          WasmComponentModuleInitializerExport(
            scope = WitScope.Interface("wasi", "cli", "run", Some("0.2.12")),
            functionName = "run",
            resultType = ResultType.ResultUnitUnit,
          ))))
    },

    scalaJSUseMainModuleInitializer := true,

    jsEnv := new WasmtimeEnv(),
  )
  • withUseWebAssembly and withModuleKind(ModuleKind.WasmComponent) tell the linker to emit a Wasm component.
  • scalaJSUseMainModuleInitializer := true places your main as a Wasm module initializer.
  • withModuleInitializerExport exports that initializer as wasi:cli/run@0.2.12.

src/main/scala/Main.scala

package example

import io.github.scalawasm.wasi4s.wasi.cli.stdout._

object Main {
  def main(args: Array[String]): Unit = {
    val out = getStdout()
    out.blockingWriteAndFlush("Hello, world!\n")
  }
}

This example writes to the host using WASI’s low-level stdout API. 2

Run

sbt fastLinkJS
[info] Fast optimizing .../component-examples/target/out/sjs1/scala-2.13.18/helloworld/helloworld-fastopt

main.wasm is generated in that directory. If build.sbt sets jsEnv := new WasmtimeEnv(), then sbt run runs main.wasm using wasmtime from sbt.

sbt run
[info] Running wasi:cli/run.
Hello, world!

To run the artifact yourself using wasmtime:

wasmtime run -W function-references,gc,exceptions path/to/main.wasm

(-W flags might be no longer needed with the latest version of wasmtime).


  1. You can instead add a project wit/ directory with world.wit that imports WASI explicitly and generate bindings with wit-bindgen-scala. We'll cover world.wit in another tutorial. ↩

  2. Why not println? scala-wasm does not ship a println implementation today; you define stdout yourself via WASI (or a small wrapper). See Limitations. ↩