Skip to content

Commit 25263d4

Browse files
tpmccallumbjorn3vados-cosmonic
authored
blog: Running Wasm Components From the Command Line (approved) (#126)
* Adding draft article re: wasmtime run --invoke * Adding draft article re: wasmtime run --invoke * Format tree command and add WAVE link * Update _posts/2025-04-10-invoking-component-functions-in-wasmtime-cli.md Great point, thanks so. much! Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com> * Respond to review feedback --invoke and single quote updates * Update 2025-04-10-invoking-component-functions-in-wasmtime-cli.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> * Update 2025-04-10-invoking-component-functions-in-wasmtime-cli.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> * Commit responding to some of the review points * Responding to all review comments * Update in readiness for final review/publishing * Some final adjustments made during markdown preview * Adjust headings * Update _posts/2025-04-10-invoking-component-functions-in-wasmtime-cli.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> * Be more specific re: module v component and use wasip2 target * Update based on further review comments * Update to use best practice of interface in the WIT file, as per review comment. * Final pass - couple of tweaks. Ready to publish. * Update publish date Just setting the `date` frontmatter to a future publishing date. * Update the article's publish date in the filename * Rename and update article for v33.0.0 release --------- Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com> Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com>
1 parent c3a8a57 commit 25263d4

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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

Comments
 (0)