|
| 1 | +--- |
| 2 | +title: "Running WebAssembly (Wasm) Components From the Command Line" |
| 3 | +author: "Tim McCallum" |
| 4 | +date: "2025-05-21" |
| 5 | +github_name: "tpmccallum" |
| 6 | +excerpt_separator: <!--end_excerpt--> |
| 7 | +--- |
| 8 | +Wasmtime's 33.0.0 release supports invoking Wasm component exports directly from the command line with the new `--invoke` flag. |
| 9 | +This article walks through building a Wasm component in Rust and using `wasmtime run --invoke` to execute specific functions (enabling powerful workflows for scripting, testing, and integrating Wasm into modern development pipelines). |
| 10 | +<!--end_excerpt--> |
| 11 | + |
| 12 | +## The Evolution of Wasmtime's CLI |
| 13 | + |
| 14 | +Wasmtime's `run` subcommand has traditionally supported running Wasm modules as well as invoking that **module**'s exported function. However, with the evolution of the Wasm Component Model, this article focuses on a newer capability; creating a component that exports a function and then demonstrating how to invoke that **component**'s exported function. |
| 15 | + |
| 16 | +By the end of this article, you’ll be ready to create Wasm components and orchestrate their exported component functions to improve your workflow's efficiency and promote reuse. Potential examples include: |
| 17 | + |
| 18 | +* Shell Scripting: Embed Wasm logic directly into Bash or Python scripts for seamless automation. |
| 19 | +* CI/CD Pipelines: Validate components in GitHub Actions, GitLab CI, or other automation tools without embedding them in host applications. |
| 20 | +* Cross-Language Testing: Quickly verify that interfaces match across implementations in Rust, JavaScript, and Python. |
| 21 | +* Debugging: Inspect exported functions during development with ease. |
| 22 | +* Microservices: Chain components in serverless workflows, such as compress → encrypt → upload, leveraging Wasm's modularity. |
| 23 | + |
| 24 | +## Tooling & Dependencies |
| 25 | + |
| 26 | +If you want to follow along, please install: |
| 27 | + |
| 28 | +* [Rust](https://www.rust-lang.org/tools/install) (if you already have Rust installed, make sure you are on [the latest version](https://github.com/rust-lang/rust/releases)), |
| 29 | +* [`cargo`](https://crates.io/crates/cargo) (if already installed, please make sure you are on [the latest version](https://crates.io/crates/cargo)), |
| 30 | +* [`cargo component`](https://crates.io/crates/cargo-component) (if already installed, please make sure you are on [the latest version](https://crates.io/crates/cargo-component)), and |
| 31 | +* [`wasmtime` CLI](https://docs.wasmtime.dev/cli-install.html) (or use a [precompiled binary](https://docs.wasmtime.dev/cli-install.html#download-precompiled-binaries)). If already installed, ensure you are using [v33.0.0](https://github.com/bytecodealliance/wasmtime/releases) or newer. |
| 32 | + |
| 33 | +You can check versions using the following commands: |
| 34 | + |
| 35 | +```console |
| 36 | +$ rustc --version |
| 37 | +$ cargo --version |
| 38 | +$ cargo component --version |
| 39 | +$ wasmtime --version |
| 40 | +``` |
| 41 | + |
| 42 | +We must explicitly `add` the `wasm32-wasip2` target. This ensures that our component adheres to WASI’s system interface for non-browser environments (e.g., file system access, sockets, random etc.): |
| 43 | + |
| 44 | +```console |
| 45 | +$ rustup target add wasm32-wasip2 |
| 46 | +``` |
| 47 | + |
| 48 | +## Creating a New Wasm Component With Rust |
| 49 | + |
| 50 | +Let's start by creating a new Wasm library that we will later convert to a Wasm component using `cargo component` and the `wasm32-wasip2` target: |
| 51 | + |
| 52 | +```console |
| 53 | +$ cargo component new --lib wasm_answer |
| 54 | +$ cd wasm_answer |
| 55 | +``` |
| 56 | + |
| 57 | +If you open the `Cargo.toml` file, you will notice that the `cargo component` command has automatically added some essential configurations. |
| 58 | + |
| 59 | +The `wit-bindgen-rt` dependency (with the `["bitflags"]` feature) under `[dependencies]`, and the `crate-type = ["cdylib"]` setting under the `[lib]` section. |
| 60 | + |
| 61 | +Your `Cargo.toml` should now include these entries (as shown in the example below): |
| 62 | + |
| 63 | +```toml |
| 64 | +[package] |
| 65 | +name = "wasm_answer" |
| 66 | +version = "0.1.0" |
| 67 | +edition = "2024" |
| 68 | + |
| 69 | +[dependencies] |
| 70 | +wit-bindgen-rt = { version = "0.41.0", features = ["bitflags"] } |
| 71 | + |
| 72 | +[lib] |
| 73 | +crate-type = ["cdylib"] |
| 74 | + |
| 75 | +[package.metadata.component] |
| 76 | +package = "component:wasm-answer" |
| 77 | + |
| 78 | +[package.metadata.component.dependencies] |
| 79 | +``` |
| 80 | + |
| 81 | +The directory structure of the `wasm_answer` example is automatically scaffolded out for us by `cargo component`: |
| 82 | + |
| 83 | +```console |
| 84 | +$ tree wasm_answer |
| 85 | + |
| 86 | +wasm_answer |
| 87 | +├── Cargo.lock |
| 88 | +├── Cargo.toml |
| 89 | +├── src |
| 90 | +│ ├── bindings.rs |
| 91 | +│ └── lib.rs |
| 92 | +└── wit |
| 93 | + └── world.wit |
| 94 | +``` |
| 95 | + |
| 96 | +## WIT |
| 97 | + |
| 98 | +If we open the `wit/world.wit` file, that `cargo component` created for us, we can see that `cargo component` generates a minimal `world.wit` that exports a raw function: |
| 99 | + |
| 100 | +```wit |
| 101 | +package component:wasm-answer; |
| 102 | +
|
| 103 | +/// An example world for the component to target. |
| 104 | +world example { |
| 105 | + export hello-world: func() -> string; |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +We can simply adjust the `export` line (as shown below): |
| 110 | + |
| 111 | +```wit |
| 112 | +package component:wasm-answer; |
| 113 | +
|
| 114 | +/// An example world for the component to target. |
| 115 | +world example { |
| 116 | + export get-answer: func() -> u32; |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +> **But, instead, let's use an interface to export our function!** |
| 121 | +
|
| 122 | +While the above approach works, the [recommended best practice](https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/tutorial/wit/adder/world.wit) is to **wrap related functions inside an interface, which you then export from your world**. This is more modular, extensible, and aligns with how the Wasm Interface Type (WIT) format is used in multi-function or real-world components. Let's update the `wit/world.wit` file as follows: |
| 123 | + |
| 124 | +```wit |
| 125 | +package component:wasm-answer; |
| 126 | +
|
| 127 | +interface answer { |
| 128 | + get-answer: func() -> u32; |
| 129 | +} |
| 130 | +
|
| 131 | +world example { |
| 132 | + export answer; |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +Next, we update our `src/lib.rs` file accordingly, by pasting in the following Rust code: |
| 137 | + |
| 138 | +```rust |
| 139 | +#[allow(warnings)] |
| 140 | +mod bindings; |
| 141 | + |
| 142 | +use bindings::exports::component::wasm_answer::answer::Guest; |
| 143 | + |
| 144 | +struct Component; |
| 145 | + |
| 146 | +impl Guest for Component { |
| 147 | + fn get_answer() -> u32 { |
| 148 | + 42 |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +bindings::export!(Component with_types_in bindings); |
| 153 | +``` |
| 154 | + |
| 155 | +Now, let's create the Wasm component with our exported `get_answer()` function: |
| 156 | + |
| 157 | +```console |
| 158 | +$ cargo component build --target wasm32-wasip2 |
| 159 | +``` |
| 160 | + |
| 161 | +Our newly generated `.wasm` file now lives at the following location: |
| 162 | + |
| 163 | +```console |
| 164 | +$ file target/wasm32-wasip2/debug/wasm_answer.wasm |
| 165 | +target/wasm32-wasip2/debug/wasm_answer.wasm: WebAssembly (wasm) binary module version 0x1000d |
| 166 | +``` |
| 167 | + |
| 168 | +We can also use the `--release` option which optimises builds for production: |
| 169 | + |
| 170 | +```console |
| 171 | +$ cargo component build --target wasm32-wasip2 --release |
| 172 | +``` |
| 173 | + |
| 174 | +If we check the sizes of the `debug` and `release`, we see a difference of `2.1M` and `16K`, respectively. |
| 175 | + |
| 176 | +Debug: |
| 177 | + |
| 178 | +```console |
| 179 | +$ du -mh target/wasm32-wasip2/debug/wasm_answer.wasm |
| 180 | +2.1M target/wasm32-wasip2/debug/wasm_answer.wasm |
| 181 | +``` |
| 182 | + |
| 183 | +Release: |
| 184 | + |
| 185 | +```console |
| 186 | +$ du -mh target/wasm32-wasip2/release/wasm_answer.wasm |
| 187 | +16K target/wasm32-wasip2/release/wasm_answer.wasm |
| 188 | +``` |
| 189 | + |
| 190 | +## How Invoke Works: A Practical Example |
| 191 | + |
| 192 | +The `wasmtime run` command can take one positional argument and just run a `.wasm` or `.wat` file: |
| 193 | + |
| 194 | +```console |
| 195 | +$ wasmtime run foo.wasm |
| 196 | +$ wasmtime run foo.wat |
| 197 | +``` |
| 198 | + |
| 199 | +### Invoke: Wasm Modules |
| 200 | + |
| 201 | +In the case of a Wasm **module** that exports a raw function directly, the `run` command accepts an optional `--invoke` argument, which is the name of an exported raw function (of the **module**) to run: |
| 202 | + |
| 203 | +```console |
| 204 | +$ wasmtime run --invoke initialize foo.wasm |
| 205 | +``` |
| 206 | + |
| 207 | +### Invoke: Wasm Components |
| 208 | + |
| 209 | +In the case of a Wasm **component** that uses typed interfaces (defined [in WIT](https://component-model.bytecodealliance.org/design/wit.html), in concert with [the Component Model](https://component-model.bytecodealliance.org/design/components.html)), the `run` command now also accepts the optional `--invoke` argument for calling an exported function of a **component**. |
| 210 | + |
| 211 | +However, the calling of an exported function of a **component** uses [WAVE](https://github.com/bytecodealliance/wasm-tools/tree/a56e8d3d2a0b754e0465c668f8e4b68bad97590f/crates/wasm-wave#readme)(a human-oriented text encoding of Wasm Component Model values). For example: |
| 212 | + |
| 213 | +```console |
| 214 | +$ wasmtime run --invoke 'initialize()' foo.wasm |
| 215 | +``` |
| 216 | + |
| 217 | +> You will notice the different syntax of `initialize` versus `'initialize()'` when referring to a **module** versus a **component**, respectively. |
| 218 | +
|
| 219 | +Back to our `get-answer()` example: |
| 220 | + |
| 221 | +```console |
| 222 | +$ wasmtime run --invoke 'get-answer()' target/wasm32-wasip2/debug/wasm_answer.wasm |
| 223 | +42 |
| 224 | +``` |
| 225 | + |
| 226 | +You will notice that the above `get-answer()` function call does not pass in any arguments. Let's discuss how to represent the arguments passed into function calls in a structured way (using WAVE). |
| 227 | + |
| 228 | +#### Wasm Value Encoding (WAVE) |
| 229 | + |
| 230 | +Transferring and invoking complex argument data via the command line is challenging, especially with Wasm components that use diverse value types. To simplify this, Wasm Value Encoding ([WAVE](https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wasm-wave/README.md)) was introduced; offering a concise way to represent structured values directly in the CLI. |
| 231 | + |
| 232 | +WAVE provides a standard way to encode function calls and/or results. WAVE is a human-oriented text encoding of Wasm Component Model values; designed to be consistent with the [WIT IDL format](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md). |
| 233 | + |
| 234 | +Below are a few additional pointers for constructing your `wasmtime run --invoke` commands using WAVE. |
| 235 | + |
| 236 | +#### Quotes |
| 237 | + |
| 238 | +As shown above, the component's exported function name and mandatory parentheses are contained in one set of single quotes, i.e., `'get-answer()'`: |
| 239 | + |
| 240 | +```console |
| 241 | +$ wasmtime run --invoke 'get-answer()' target/wasm32-wasip2/release/wasm_answer.wasm |
| 242 | +``` |
| 243 | + |
| 244 | +The result from our correctly typed command above is as follows: |
| 245 | + |
| 246 | +```console |
| 247 | +42 |
| 248 | +``` |
| 249 | + |
| 250 | +#### Parentheses |
| 251 | + |
| 252 | +Parentheses after the exported function's name are mandatory. The presence of the parenthesis `()` signifies function invocation, as opposed to the function name just being referenced. If your function takes a string argument, ensure that you contain your string in double quotes (inside the parentheses). For example: |
| 253 | + |
| 254 | +```console |
| 255 | +$ wasmtime run --invoke 'initialize("hello")' foo.wasm |
| 256 | +``` |
| 257 | + |
| 258 | +If your exported function takes more than one argument, ensure that each argument is separated using a single comma `,` as shown below: |
| 259 | + |
| 260 | +```console |
| 261 | +$ wasmtime run --invoke 'initialize("Pi", 3.14)' foo.wasm |
| 262 | +$ wasmtime run --invoke 'add(1, 2)' foo.wasm |
| 263 | +``` |
| 264 | + |
| 265 | +## Recap: Wasm Modules versus Wasm Components |
| 266 | + |
| 267 | +Let's wrap this article up with a recap to crystallize your knowledge. |
| 268 | + |
| 269 | +### Earlier Wasmtime Run Support for Modules |
| 270 | + |
| 271 | +If we are not using the Component Model and just creating a module, we use a simple command like `wasmtime run foo.wasm` (**without** WAVE syntax). This approach typically applies to modules, which export a `_start` function, or reactor modules, which can optionally export the `wasi:cli/run` interface—standardized to enable consistent execution semantics. |
| 272 | + |
| 273 | +Example of running a Wasm **module** that exports a raw function directly: |
| 274 | + |
| 275 | +```console |
| 276 | +$ wasmtime run --invoke initialize foo.wasm |
| 277 | +``` |
| 278 | + |
| 279 | +### Wasmtime Run Support for Components |
| 280 | + |
| 281 | +As Wasm evolves with the Component Model, developers gain fine-grained control over component execution and composition. Components using WIT can now be run with `wasmtime run`, using the optional `--invoke` argument to call exported functions (**with** [WAVE](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-wave)). |
| 282 | + |
| 283 | +Example of running a Wasm **component** that exports a function: |
| 284 | + |
| 285 | +```console |
| 286 | +$ wasmtime run --invoke 'add(1, 2)' foo.wasm |
| 287 | +``` |
| 288 | + |
| 289 | +For more information, visit the [cli-options section](https://docs.wasmtime.dev/cli-options.html#run) of the Wasmtime documentation. |
| 290 | + |
| 291 | +## Benefits and Usefulness |
| 292 | + |
| 293 | +The addition of support for the run `--invoke` feature [for components](https://github.com/bytecodealliance/wasmtime/pull/10054) allows users to specify and execute exported functions from a Wasm component. This enables greater flexibility for testing, debugging, and integration. We now have the ability to perform the execution of arbitrary exported functions directly from the command line, this feature opens up a world of possibilities for integrating Wasm into modern development pipelines. |
| 294 | + |
| 295 | +**This evolution from monolithic Wasm modules to composable, CLI-friendly components exemplifies the versatility and power of Wasm in real-world scenarios.** |
0 commit comments