MoonBit is a modern language plus workflow designed to create efficient WebAssembly projects; it can also target JavaScript. I last looked at MoonBit in June 2024, so I wanted to see if it had progressed in regards to using Wasm. (Aside: if you still don’t quite get the point of Wasm, you are not entirely alone.)
Now, MoonBit can also run within the Web Component model, which we’ll look at shortly. We’ll implement this and then check that it works. We’re only going to implement a simple addition method.
A Brief Refresher on the MoonBit Language
But we’d better just revisit the MoonBit language briefly, as a refresher.
Starting of course with Hello World:
fn print_hello() -> Unit { println("Hello, world!") } |
The only thing somewhat unexpected is that term Unit, but it’s just MoonBit’s first class object equivalent of void.
Here’s one more example:
fn add3(x : Int, y : Int, z : Int) -> Int { x + y + z } |
This is a “top level” function; and note that we have to be explicit with the types. But to go deeper into the language itself, just take the tour.
WebAssembly Integration Via the Component Model
We’re here for the WebAssembly integration, though. MoonBit works with the WebAssembly Component Model, which takes away some of the pain of implementing basic functionality. Component models are always the next stage in evolution for language projects, since they lock down interfaces, contracts and types.
Logically, a component is a structure that may contain core modules and/or other components. The component expresses the interfaces of these contained modules and sub-components using WebAssembly Interface Types (WIT). If this sounds like a classic overly academic definition, don’t worry too much. It will work just fine in a production toolchain. This post follows along from the MoonBit documentation on WebAssembly components.
Installing the Required Tools and Toolchains
Before we do anything else, we need the wit-bindgen CLI tool in order to generate bindings compatible with WIT; which happens to be Rust-based, so you will need Cargo. That comes for free if we just install Rust:
curl [https://sh.rustup.rs](https://sh.rustup.rs/) -sSf | sh |
(If you have brewed a Rust, you may need to decide whether you want rustup to keep Rust refreshed or not. I didn’t update properly and got into trouble later on, so I would recommend removing any brewed version.)
Before you do that, just type “cargo” first in case you already have it. I did:

That is a bit old, but works. So we install the binding CLI tool:
> cargo install wit-bindgen-cli |
You may get the warning to add the bin directory to your path. I’m installing on my MacBook, so I just added it to PATH.
Then we install Wasm tools for working with components:
> cargo install wasm-tools |
But we’d better get on with installing MoonBit itself:
> curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash |
After running or starting a new shell with (in my case):
You can now run the Rust-based project organiser Moon:

So now we have the moon on a stick.
Defining the Program with a WIT File
Before we can write any MoonBit, we need to create a WIT. This is so we can declare our work to be valid in the world of WebAssembly components:
We create wit/world.wit.
package docs:adder@0.1.0; interface add { add: func(x: u32, y: u32) -> u32; } world adder { export add; } |
This declares a package docs:adder at version 0.1.0, and the add interface with a single function of unsigned 32-bit numbers (these map to UInts in MoonBit). A WIT world is a container in which you put all the bits you need for this to work. In this case, it is just the add interface.
OK, let’s generate this description for our WIT and with MoonBit structure:
> wit-bindgen moonbit wit/world.wit --out-dir . --derive-eq --derive-show --derive-error |
So you should have this:

You’ll have guessed that the suffix .mbt is a MoonBit file. But of course we haven’t actually written any MoonBit yet. If we try checking this as a Wasm target, we get a whole load of warnings:

And if we look at the stub, we see the problem that matches our observation:

In short, there is no actual code in the stub, it’s just interface definition. Even using our minimal MoonBit tutorial from above, we can still easily complete this:

The main configuration file, gen/moon.pkg.json, should hold the relevant path information.
How to Build the WebAssembly Module and Component
So we finally build the main WebAssembly module:
> moon build --target wasm |
And we get a whole new target directory, including the Wasm file:

Just to confirm, let’s check that gen.wasm file:

OK, so it’s valid WebAsssembly. Now we need to wrap it correctly in the component model:
> wasm-tools component embed wit target/wasm/release/build/gen/gen.wasm \ --encoding utf16 \ --output adder.wasm |
…followed by:
> wasm-tools component new adder.wasm --output adder.component.wasm |
By which time it is a little different to the straight Wasm module:

We can use Wasmtime to confirm this all worked:

If Wasmtime is unhappy, get a recent version directly from here:
curl https://wasmtime.dev/install.sh -sSf | bash |
You can also get a more heavyweight example host to prove that our .wasm file is working.
Conclusion
I came to check up on MoonBit, and discovered WebAssembly components. Such is the journey of the developer — there is always one more hill.
At some point, MoonBit will have to do a bit more work on direct support for the component model; otherwise, the academic complexity will continue to keep Wasm a little distant from mass support (or nearer to Rust). However, this feels like other stubbing-style models — although the Web Services Description Language isn’t the kindest comparison — these work well in a solid toolchain, even one directed by an LLM.
YOUTUBE.COM/THENEWSTACK
Tech moves fast, don't miss an episode. Subscribe to our YouTube channel to stream all our podcasts, interviews, demos, and more.
.png)


