Skip to main content
Version: 1.x

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.

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:

bash
rustup toolchain install nightly

wasmCloud 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.

shell
cargo install --locked wash-cli

If you have cargo-binstall installed, you can install even faster:

bash
cargo binstall wash-cli

Wasmtime

On Linux or macOS, you can install Wasmtime locally with an install script:

shell
curl https://wasmtime.dev/install.sh -sSf | bash

WebAssembly Compositions (WAC)

WAC requires Cargo. Once you've installed Cargo:

shell
cargo install wac-cli

WASI Virt

WASI Virt requires the nightly release channel for Rust:

bash
rustup toolchain install nightly

Install the wasi-virt command line tool with Rust's cargo package manager:

bash
cargo +nightly install --git https://github.com/bytecodealliance/wasi-virt

Example 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):

bash
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/composition

For 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:

shell
cd pong
wash build

We can use wash inspect to take a look at the WIT for this component:

shell
wash inspect --wit ./build/pong_s.wasm
wit
package 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.

shell
wasi-virt build/pong_s.wasm --allow-random -e PONG=demo -o virt.wasm

Now let's view the WIT for our virtualized component:

shell
wash inspect --wit virt.wasm 
wit
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.

shell
cd ../http-hello2
wash build

For context, let's try running wasmtime serve without composing pong and http-hello-world together.

shell
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 missing

As 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:

shell
wac plug --plug pong/virt.wasm ./http-hello2/build/http_hello_world.wasm -o output.wasm

The "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:

bash
wash inspect --wit output.wasm
wit
package 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:

shell
wasmtime serve -S cli=y output.wasm
shell
curl localhost:8080
Hello World! I got pong demo

We can run the composed component in wasmCloud as well:

shell
wash app deploy wadm.yaml

Congratulations! 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:

wit
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:

shell
wac encode --dep ping:pong=./pong/virt.wasm --dep hello:there=./http-hello2/build/http_hello_world_s.wasm -o output.wasm compose.wac

You 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.