MoonBit for Component Model#
This guide demonstrates how to build WebAssembly components using MoonBit,
leveraging WIT (WebAssembly Interface Types) for interface definitions and the
wit-bindgen
toolchain for code generation.
This tutorial walks through building a component that implements the
adder
world defined in the docs:adder
package. The component
will export an add
interface containing an add
function that sums two
numbers.
1. Install the Tools#
Installing MoonBit#
First, install the MoonBit compiler and toolchain. Follow the installation instructions from the MoonBit download page.
Verify your MoonBit installation (below are the versions at the time of writing):
$ moon version --all
moon 0.1.20250826 (8ab6c9e 2025-08-26) ~/.moon/bin/moon
moonc v0.6.25+d6913262c (2025-08-27) ~/.moon/bin/moonc
moonrun 0.1.20250826 (8ab6c9e 2025-08-26) ~/.moon/bin/moonrun
moon-pilot 0.0.1-95f12db ~/.moon/bin/moon-pilot
Installing Wasm toolchain#
Install the
wit-bindgen
CLI tool, which generates MoonBit bindings from WIT files:$ cargo install wit-bindgen-cli
Install
wasm-tools
for working with WebAssembly components:$ cargo install wasm-tools
Verify the installations (below are the versions at the time of writing):
$ wit-bindgen --version
wit-bindgen-cli 0.45.0
$ wasm-tools --version
wasm-tools 1.238.0
2. Define the Interface (WIT)#
Before generating the MoonBit project, you need to define the component interface using WIT. Create a directory for your project and define the WIT file:
$ mkdir moonbit-adder && cd moonbit-adder
$ mkdir wit
Create wit/world.wit
with the following content:
package docs:adder@0.1.0;
interface add {
add: func(x: u32, y: u32) -> u32;
}
world adder {
export add;
}
This WIT definition:
Declares a package
docs:adder
with version0.1.0
Defines an
add
interface with a single function that takes twou32
parameters and returns au32
Creates an
adder
world that exports theadd
interface
3. Generate MoonBit Project Structure#
Use wit-bindgen
to generate the MoonBit project structure and bindings:
$ wit-bindgen moonbit wit/world.wit --out-dir . \
--derive-eq \
--derive-show \
--derive-error
This command generates the following directory structure:
.
├── ffi
│ ├── moon.pkg.json
│ └── top.mbt
├── gen
│ ├── ffi.mbt
│ ├── gen_interface_docs_adder_add_export.mbt
│ ├── interface
│ │ └── docs
│ │ └── adder
│ │ └── add
│ │ ├── moon.pkg.json
│ │ ├── stub.mbt
│ │ └── top.mbt
│ ├── moon.pkg.json
│ ├── world
│ │ └── adder
│ │ ├── moon.pkg.json
│ │ └── stub.mbt
│ └── world_adder_export.mbt
├── moon.mod.json
├── wit
│ └── world.wit
└── world
└── adder
├── ffi_import.mbt
├── import.mbt
├── moon.pkg.json
└── top.mbt
The generated files include:
moon.mod.json
: MoonBit module configurationgen/
: Generated export bindingsinterface/
: Generated export interface bindingsworld/
: Generated export world bindingsstub.mbt
: Main implementation file
interface/
: Generated import interface bindingsworld/
: Generated import world bindings
4. Examine the Generated Code#
The wit-bindgen
tool generates MoonBit bindings that handle the WebAssembly
component interface. Let’s examine the generated
gen/interface/docs/adder/add/stub.mbt
:
// Generated by `wit-bindgen` 0.45.0.
pub fn add(_x : UInt, _y : UInt) -> UInt {
...
}
The ...
is the placeholder syntax in MoonBit. When executing
moon check --target wasm
, ‘unfinished code’ warnings will appear.
5. Implement the Component Logic#
Now implement the add
function in gen/interface/docs/adder/add/stub.mbt
:
// Generated by `wit-bindgen` 0.45.0.
///|
pub fn add(x : UInt, y : UInt) -> UInt {
x + y
}
6. Configure the Build#
Ensure your gen/moon.pkg.json
is properly configured for WebAssembly target:
{
// link configuration for Wasm backend
"link": {
"wasm": {
"exports": [
// Export for cabi_realloc
"cabi_realloc:cabi_realloc",
// Export per the interface definition
"wasmExportAdd:docs:adder/add@0.1.0#add"
],
"export-memory-name": "memory",
"heap-start-address": 16
}
},
"import": [
{
"path": "docs/adder/ffi",
"alias": "ffi"
},
{
"path": "docs/adder/gen/interface/docs/adder/add",
"alias": "add"
}
]
}
7. Build the WebAssembly Component#
Build the MoonBit code to WebAssembly:
$ moon build --target wasm
This generates a WebAssembly module. To create a proper WebAssembly component,
use wasm-tools
:
$ wasm-tools component embed wit target/wasm/release/build/gen/gen.wasm \
--encoding utf16 \
--output adder.wasm
$ wasm-tools component new adder.wasm --output adder.component.wasm
You can verify the component’s interface using wasm-tools
:
$ wasm-tools component wit adder.component.wasm
Expected output for both commands:
package root:component;
world root {
export docs:adder/add@0.1.0;
}
package docs:adder@0.1.0 {
interface add {
add: func(x: u32, y: u32) -> u32;
}
}
8. Testing the Component#
Using the Example Host#
To test your component, use the example-host
provided in this
repository:
$ git clone https://github.com/bytecodealliance/component-docs.git
$ cd component-docs/component-model/examples/example-host
$ cp /path/to/adder.component.wasm .
$ cargo run --release -- 5 3 adder.component.wasm
Expected output:
5 + 3 = 8
Using Wasmtime#
You can also test the component directly with wasmtime
:
$ wasmtime run --invoke 'add(10, 20)' adder.component.wasm
30
9. Configurations#
–derive-eq –derive-show#
These two options will add derive(Eq)
and / or derive(Show)
for all the
generated types.
–derive-error#
This option will generate variants / enums whose names containing ‘Error’ as suberrors. This allows you to integrate the MoonBit’s error handling easier.
For example, for the following interface:
package docs:adder@0.1.0;
interface add {
variant computation-error {
overflow
}
add: func(x: u32, y: u32) -> result<u32, computation-error>;
}
world adder {
import add;
}
Will generate the following type and function:
// Generated by `wit-bindgen` 0.45.0. DO NOT EDIT!
///|
pub(all) suberror ComputationError {
Overflow
} derive(Show, Eq)
///|
pub fn add(x : UInt, y : UInt) -> Result[UInt, ComputationError] {
...
}
which you may use it as:
// Generated by `wit-bindgen` 0.45.0.
///|
fn init {
let _ = @add.add(1, 2).unwrap_or_error() catch { Overflow => ... }
}
–ignore-stub#
It happens when you would like to regenerate the project due to the updated
interface, but you don’t want the stub
file to be touched. You may use
--ignore-stub
option to avoid such modifications.
–project-name#
By default, the project name is generated per the name defined in the MoonBit file. You may use this option to specify the name of the project. It can also be used if you are generating the project as part of a larger project.
–gen-dir#
By default, the exportation parts are generated under gen
. You may use this
option to specify another directory.