# WebImage: JVM to WebAssembly

> Compile Java or Kotlin to a WebAssembly module and JavaScript loader with Elide's WebImage artifact: prerequisites, backends, hosts and compiler options.

A `WebImage` artifact compiles JVM bytecode to a JavaScript loader plus a WASM module. To run an existing WASM or
WASI binary, see [WebAssembly](https://elide.help/docs/wasm).

## Prerequisites

- [Binaryen](https://github.com/WebAssembly/binaryen) 119 or later, with `wasm-as` on `PATH` at build time.
- Code that meets the [Native Image requirements](https://elide.help/docs/toolchain/jvm/native-image): reachability metadata for reflection,
  resources and proxies; no native dependencies.
- A host with WASM exception handling (and WASM GC for `wasm-gc`): Node.js 25+ or a current browser.

The deployed bundle needs neither Elide nor Binaryen.

## Build a complete project

```text
hello-wasm/
  elide.pkl
  src/main/java/Hello.java
```

```java
public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello from WebImage");
  }
}
```

```pkl
amends "elide:project.pkl"
import "elide:Jvm.pkl" as jvm
import "elide:NativeImage.pkl" as nativeImage

name = "hello-wasm"

artifacts {
  ["app-jar"] = new jvm.Jar {
    sources { "main" }
  }
  ["web"] = new nativeImage.WebImage {
    from { "app-jar" }
    name = "hello"
    entrypoint = "Hello"
    backend = "wasm-gc"
    runtime = "generic"
    autoRun = true
    sourceMap = false
    options {
      optimization = "b"
    }
  }
}
```

```bash
elide build
```

Elide builds `app-jar`, then compiles it to `web`. `name` is the basename without `.js` or `.wasm`;
`optimization = "b"` is quick-build mode. Output goes to `.dev/artifacts/native-image/`:

| File | Purpose |
| --- | --- |
| `hello.js` | JavaScript loader and entrypoint. |
| `hello.js.wasm` | WASM module, loaded by `hello.js`. |
| `hello.js.wat` | WASM text form, for inspection. |
| `hello.js.map` | Source map for the loader, when `sourceMap = true`. |

Deploy `hello.js` and `hello.js.wasm` together. Kotlin and other JVM languages work the same way once packaged in a
JAR. A cache hit restores the whole bundle; turning `sourceMap` off removes the old `.js.map`.

## Choose a backend and host

| Property | Value | Meaning |
| --- | --- | --- |
| `backend` | `"wasm-gc"` (default) | Uses the host's WASM garbage collector. |
| `backend` | `"wasm-lm"` | Uses a garbage collector in linear memory; still needs the loader, not a WASI binary. |
| `runtime` | `"generic"` (default) | Loader supports browsers and Node. |
| `runtime` | `"browser"` | Loader specialized for browsers. |
| `runtime` | `"node"` | Loader specialized for Node-compatible APIs. |
| `autoRun` | `true` (default) | Starts the program when the loader loads; `false` leaves startup to the embedding page. |
| `sourceMap` | `false` (default) | `true` emits `hello.js.map`. |

An entrypoint is required even with `autoRun = false`. No `runtime` targets Cloudflare Workers, Fastly Compute or
WASI. Shared fields (`from`, `entrypoint`, `moduleName`, `options`) are listed under
[Native Image](https://elide.help/docs/toolchain/jvm/native-image#project-artifact). A WebImage cannot be the input to a
[container artifact](https://elide.help/docs/toolchain/jvm/containers).

Target guides: [Browser WASM](https://elide.help/docs/toolchain/web/wasm) and [Edge WASM](https://elide.help/docs/toolchain/edge/wasm).

### Run on a backend host

With `runtime = "generic"` or `"node"`:

```bash
node .dev/artifacts/native-image/hello.js
```

```text
Hello from WebImage
```

### Run in a browser

With `runtime = "browser"` or `"generic"` and `autoRun = true`, put this `index.html` in
`.dev/artifacts/native-image/`:

```html
<!doctype html>
<html lang="en">
  <meta charset="utf-8" />
  <title>Hello WebImage</title>
  <body>
    <p>Open the browser console to see the application output.</p>
    <script src="./hello.js"></script>
  </body>
</html>
```

```bash
elide dev --no-tui --port 8000 .dev/artifacts/native-image
```

Open `http://localhost:8000/`; the output appears in the browser console. `file://` URLs do not work.

## Compiler options

The artifact's `options` block takes the [Native Image compiler options](https://elide.help/docs/toolchain/jvm/native-image#compiler-options);
which apply depends on the backend. Two cases specific to WebImage:

- `options.driverMode = "external"` uses `native-image` from `GRAALVM_HOME` or `JAVA_HOME`; that installation must
  support the WebAssembly backends.
- `-H:EntryPointsConfig=config/entrypoints.json` in `options.flags` sets entry points; the path is relative to the
  project root, and editing the file triggers a rebuild.

## Compile without a project manifest

```bash
elide javac -- -d classes src/main/java/Hello.java
elide native-image -- --tool:wasm-gc -Ob -cp classes -o hello Hello
elide native-image -- --tool:wasm-lm -Ob -cp classes -o hello-lm Hello
```

Bundles are written to the current directory. A `NativeImage` artifact with `--tool:wasm-gc` or `--tool:wasm-lm` in
`options.flags` still builds. To migrate it, change the class to `WebImage`, drop the flag and any `type`, and set
`backend`.

## Troubleshooting

| Symptom | Fix |
| --- | --- |
| `wasm-as` not found | Install Binaryen and put it on the build's `PATH`. |
| External driver rejects the WebAssembly backend | Point `GRAALVM_HOME` or `JAVA_HOME` at an installation whose `native-image` supports it, or use `driverMode = "embedded"`. |
| Conflicting raw options | Remove backend, runtime, startup and source-map flags; use the typed properties. Do not override `Backend` or the launcher property. |
| Host rejects WASM instructions | Use a host with exception handling and, for `wasm-gc`, WASM GC. |
| Browser cannot load the module | Serve loader and module together over HTTP; check the requested URL. |
| `elide run hello.js.wasm` reports missing imports | Run `hello.js` instead; the module needs its loader. |
