Linking at Build
Overview
At build-time, WebAssembly components may be composed, meaning they will be packaged together as a single component and linked internally. The composability of components is a core tenet of components' design. Open source CLI tools like wac and WASI Virt enable you to compose components before deployment.
It's important to note that build-time composition is a matter of link declaration. These links will be resolved upon component instantiation by the WebAssembly runtime—in our case, Wasmtime, the underlying runtime used by the wasmCloud host.
When to link at build
Linking at build makes sense when...
- Components' functionality is tightly coupled, and you expect to always update the components at the same time
 - You want to use a library from another language in your primary component
 - Services performed by multiple components should scale together, and you want to maximize network efficiency
 - Sensitive functionality such as filesystem operations need to be sandboxed
 
Example: Composition
In this example, we'll perform multiple compositions, using tools like WAC and WASI Virt to compose Wasm binaries that run in wasmCloud and Wasmtime, and ultimately arriving at a single composed component.
Prerequisites
This demo is designed to demonstrate multiple ecosystem tools, including:
Note that multiple tools require Cargo, and WASI Virt requires the nightly release channel for Rust, so you may wish to install that up front:
rustup toolchain install nightlywasmCloud Shell (wash)
Follow the instructions for your OS on the Installation page. Since several of the following tools use Cargo, you may wish to use wash through Cargo as well.
cargo install --locked washIf you have cargo-binstall installed, you can install even faster:
cargo binstall washWasmtime
On Linux or macOS, you can install Wasmtime locally with an install script:
curl https://wasmtime.dev/install.sh -sSf | bashWebAssembly Compositions (WAC)
WAC requires Cargo. Once you've installed Cargo:
cargo install wac-cliWASI Virt
WASI Virt requires the nightly release channel for Rust:
rustup toolchain install nightlyInstall the wasi-virt command line tool with Rust's cargo package manager:
cargo +nightly install --git https://github.com/bytecodealliance/wasi-virtExample files
The example files are included in the wasmCloud monorepo at github.com/wasmcloud/wasmcloud. You can perform a "sparse" checkout to download only the relevant example directory (and associated documentation and metadata):
git clone --depth 1 --no-checkout https://github.com/wasmCloud/wasmCloud.git
cd wasmcloud
git sparse-checkout set ./examples/rust/composition
git checkout
cd examples/rust/compositionFor this example, we will use the pong and http-hello2 directories as well as the compose.wac file at the root of the composition project directory.
Virtualizing a component
Navigate to the pong directory. Here we have a Rust-based component with a custom interface called pong that will return a string "ping" on its exported pingpong interface. Build the component:
cd pong
wash buildWe can use wash inspect to take a look at the WIT for this component:
wash inspect --wit ./build/pong_s.wasmpackage root:component;
world root {
  import wasi:cli/environment@0.2.0;
  import wasi:cli/exit@0.2.0;
  import wasi:io/error@0.2.0;
  import wasi:io/streams@0.2.0;
  import wasi:cli/stdin@0.2.0;
  import wasi:cli/stdout@0.2.0;
  import wasi:cli/stderr@0.2.0;
  import wasi:clocks/wall-clock@0.2.0;
  import wasi:filesystem/types@0.2.0;
  import wasi:filesystem/preopens@0.2.0;
  import wasi:random/random@0.2.0;
  export example:pong/pingpong;
}You can see the pingpong interface listed as an export and wasi:cli/environment as one of many imports.
Now it's time to perform our first composition in the form of a virtualization with WASI Virt. Using WASI Virt, we're going to compose pong_s.wasm into an encapsulating component with an environment variable PONG set to demo. The resulting component will be named virt.wasm.
wasi-virt build/pong_s.wasm --allow-random -e PONG=demo -o virt.wasmNow let's view the WIT for our virtualized component:
wash inspect --wit virt.wasm package root:component;
world root {
  import wasi:random/random@0.2.0;
  export example:pong/pingpong;
}The virtualized component still exports pingpong but no longer requires wasi:cli/environment—that import (and all of the others except the new wasi:random/random, which we added via a WASI Virt argument) is satisfied by the encapsulating component. If we run this component on wasmCloud or with Wasmtime, the host will be able to satisfy the random import. But we still need another component to invoke this one via the exposed pingpong interface.
Composing a component
In the http-hello2 directory, we have a modified http-hello-world component that imports the pingpong interface and calls for a string from pong to append to the hello world message. Navigate to that directory and build the component.
cd ../http-hello2
wash buildFor context, let's try running wasmtime serve without composing pong and http-hello-world together.
wasmtime serve -S cli=y build/http_hello_world.wasm
Error: component imports instance `example:pong/pingpong`, but a matching implementation was not found in the linker
Caused by:
    0: instance export `ping` has the wrong type
    1: function implementation is missingAs we might expect, it doesn't work—the hello world component doesn't have anything linked to fulfill the pong import. If we compose it with the pong component, however, we essentially wire those corresponding imports and exports together within the encapsulating component. Head back to the root of the project directory with cd .. and then we'll use WAC to perform the composition.
WAC is a tool for composing components at build-time. It has the ability to do complex compositions using the .wac file format (an example of which can be found below as an optional step). But for purposes of our example, we're going to use the wac plug command to perform the composition:
wac plug --plug pong/virt.wasm ./http-hello2/build/http_hello_world.wasm -o output.wasmThe "plug" in this example is the component exporting what the other component needs. So in our case, it is exporting example:pong/pingpong.
Now we have a new file: output.wasm. Taking a look at our composed component, we can see that we've ended up with a combination of both components:
wash inspect --wit output.wasmpackage root:component;
world root {
  import wasi:random/random@0.2.0;
  import wasi:io/error@0.2.0;
  import wasi:io/streams@0.2.0;
  import wasi:http/types@0.2.0;
  import wasi:cli/environment@0.2.0;
  import wasi:cli/exit@0.2.0;
  import wasi:cli/stdin@0.2.0;
  import wasi:cli/stdout@0.2.0;
  import wasi:cli/stderr@0.2.0;
  import wasi:clocks/wall-clock@0.2.0;
  import wasi:filesystem/types@0.2.0;
  import wasi:filesystem/preopens@0.2.0;
  export wasi:http/incoming-handler@0.2.0;
}Let's try running it with Wasmtime:
wasmtime serve -S cli=y output.wasmcurl localhost:8080
Hello World! I got pong demoWe can run the composed component in wasmCloud as well:
wash app deploy wadm.yamlCongratulations! You've composed a component that runs anywhere supporting WASI P2. For more information on linking components at runtime or at build via composition, including when you might want to use each approach, see Linking Components in the wasmCloud documentation.
Advanced WAC
WAC can also work by encoding a composed component based on the instructions defined in a .wac file. The .wac file uses a superset of WIT also called WAC. This section contains an example that does the exact same thing as the plug command above, but explicitly defining it with a WAC file. In this example we have a compose.wac file. Here are the contents of that file:
package demo:composition;
let pong = new ping:pong { ... };
let hello = new hello:there { "example:pong/pingpong": pong.pingpong, ... };
export hello...;This is a pretty simple composition, but the the design of WAC facilitates much more complex compositions of many components. You can learn more about WAC usage in the tool's readme and language guide.
For this demo's purposes, we simply need to run wac encode while indicating the components we wish to compose with the --dep argument, naming our output file with -o, and specifying our .wac instructions:
wac encode --dep ping:pong=./pong/virt.wasm --dep hello:there=./http-hello2/build/http_hello_world_s.wasm -o output.wasm compose.wacYou can then run the output.wasm in the same way as above!
Keep reading
You can learn more about composing components in the Component Model documentation.