Home

The WebAssembly Component Model is a broad-reaching architecture for building interoperable Wasm libraries, applications, and environments.

Understanding componentsBuilding componentsUsing components
Why Components?JavascriptComposing
ComponentsPythonRunning
InterfacesRustDistributing
Worlds

ⓘ This documentation is aimed at users of the component model: developers of libraries and applications. Compiler and Wasm runtime developers can take a look at the Component Model specification to see how to add support for the component model to their project.

Status

The component model is a work in progress. Although the architecture is well-defined, details are still evolving. This version of the guide is current as of late August 2023.

Contributing

If you find a mistake, omission, ambiguity, or other problem, please let us know via GitHub issues.

If you'd like to contribute content to the guide, please see the contribution guide for information on how to contribute.

Why the Component Model?

If you've tried out WebAssembly, you'll be familiar with the concept of a module. Roughly speaking, a module corresponds to a single .wasm file, with functions, memory, imports and exports, and so on. These "core" modules can run in the browser, or via a separate runtime such as Wasmtime or WAMR. A module is defined by the WebAssembly Core Specification, and if you compile a program written in Rust, C, Go or whatever for the browser, then a core module is what you'll get.

Core modules are, however, limited to describing themselves in terms of a small number of core WebAssembly types such as integers and floating-point numbers. Just as in native assembly code, richer types, such as strings or records (structs), have to be represented in terms of integers and floating point numbers, for example by the use of pointers and offsets. And just as in native code, those representations are not interchangeable. A string in C might be represented entirely differently from a string in Rust, or a string in JavaScript.

For Wasm modules to interoperate, therefore, there needs to be an agreed-upon way for defining those richer types, and an agreed-upon way of expressing them at module boundaries.

In the component model, these type definitions are written in a language called WIT (Wasm Interface Type), and the way they translate into bits and bytes is called the Canonical ABI (Application Binary Interface). A Wasm component is thus a wrapper around a core module that specifies its imports and outputs using such Interfaces.

The agreement of an interface adds a new dimension to Wasm portability. Not only are components portable across architectures and operating systems, but they are now portable across languages. A Go component can communicate directly and safely with a C or Rust component. It need not even know which language another component was written in - it needs only the component interface, expressed in WIT. Additionally, components can be linked into larger graphs, with one component satisfying another's dependencies, and deployed as units.

Combined with Wasm's strong sandboxing, this opens the door to yet further benefits. By expressing higher-level semantics than integers and floats, it becomes possible to statically analyse and reason about a component's behaviour - to enforce and guarantee properties just by looking at the surface of the component. The relationships within a graph of components can be analysed, for example to verify that a component containing business logic has no access to a component containing personally identifiable information.

Moreover, components interact only through the Canonical ABI. Specifically, unlike core modules, components may not export Wasm memory. This not only reinforces sandboxing, but enables interoperation between languages that make different assumptions about memory - for example, allowing a component that relies on Wasm GC (garbage collected) memory to collaborate with one that uses conventional linear memory.

Now that you have a better idea about how the component model can help you, take a look at how to build components in your favorite language!

ⓘ For more background on why the component model was created, take a look at the specification's goals, use cases and design choices.

Components

  • Logically, components are containers for modules - or other components - which express their interfaces and dependencies via WIT and the Canonical ABI.
  • Conceptually, components are self-describing units of code that interact only through interfaces instead of shared memory.
  • Physically, a component is a specially-formatted WebAssembly file. Internally, the component could include multiple traditional ("core") WebAssembly modules, and sub-components, composed via their imports and exports.

The external interface of a component - its imports and exports - corresponds to a world. The component, however, internally defines how that world is implemented.

ⓘ For a more formal definition of what a component is, take a look at the Component Model specification.

Interfaces

An interface describes a single-focus, composable contract, through which components can interact with each other and with hosts. Interfaces describe the types and functions used to carry out that interaction. For example:

  • A "receive HTTP requests" interface might have only a single "handle request" function, but contain types representing incoming requests, outgoing responses, HTTP methods and headers, and so on.
  • A "wall clock" interface might have two functions, one to get the current time and one to get the granularity of the timer. It would also include a type to represent an instant in time.

Interfaces are defined using the WIT language.

ⓘ For a more formal definition of what an interface is, take a look at the WIT specification.

An Overview of WIT

The WIT (Wasm Interface Type) language is used to define Component Model interfaces and worlds. WIT isn't a general-purpose coding language and doesn't define behaviour; it defines only contracts between components. This topic provides an overview of key elements of the WIT language.

Structure of a WIT file

A WIT file contains one or more interfaces or worlds. An interface or world can define types and/or functions.

Types and functions can't be defined outside of interfaces or worlds.

A file may optionally start with a package declaration.

Comments

WIT comment syntax is similar to the one used by the C++ family of languages:

  • Everything from // to end of line is a comment.
  • Any text enclosed in /* ... */ is a comment.
    • Unlike the C++ family, block comments can be nested, e.g. /* blah /* rabbit */ rhubarb */.

Documentation

WIT defines special comment formats for documentation:

  • Everything from /// to end of line is documentation for the following item.
  • Any text enclosed in /** ... */ is documentation for the following item.

For example:

#![allow(unused)]
fn main() {
/// Prints "hello".
print-hello: func()

/**
Prints "hello".
*/
print-hello: func()
}

Identifiers

WIT identifiers have a slightly different set of rules from what you might be familiar with from, say, C, Rust, or Java. These rules apply to all names - types, functions, interfaces, and worlds. (Package identifiers are a little more complex and will be covered in the Packages section.)

  • Identifiers are restricted to ASCII kebab-case - sequences of words, separated by single hyphens.
    • Double hyphens (--) are not allowed.
    • Hyphens aren't allowed at the beginning or end of the sequence, only between words.
  • An identifier may be preceded by a single % sign.
    • This is required if the identifier would otherwise be a WIT keyword. For example, interface is not a legal identifier, but %interface is legal.
  • Each word in the sequence must begin with an ASCII letter, and may contain only ASCII letters and digits.
    • A word cannot begin with a digit.
    • A word cannot contain a non-ASCII Unicode character.
    • A word cannot contain punctuation, underscores, etc.
  • Each word must be either all lowercase or all UPPERCASE.
    • Different words in the identifier may have different cases. For example, WIT-demo is allowed.
  • An identifier cannot be a WIT keyword such as interface (unless preceded by a % sign).

Built-in types

The types in this section are defined by the WIT language itself.

Primitive types

WIT defines the following primitive types:

IdentifierDescription
boolBoolean value - true or false.
s8, s16, s32, s64Signed integers of the appropriate width. For example, s32 is a 32-bit integer.
u8, u16, u32, u64Unsigned integers of the appropriate width. For example, u32 is a 32-bit integer.
float32, float64Floating-point numbers of the appropriate width. For example, float64 is a 64-bit (double precision) floating-point number.
charUnicode character. (Specifically, a Unicode scalar value.)
stringA Unicode string - that is, a finite sequence of characters.

Lists

list<T> for any type T denotes an ordered sequence of values of type T. T can be any type, built-in or user-defined:

list<u8>        // byte buffer
list<customer>  // a list of customers

This is similar to Rust Vec, or Java List.

Options

option<T> for any type T may contain a value of type T, or may contain no value. T can be any type, built-in or user-defined. For example, a lookup function might return an option, allowing for the possibility that the lookup key wasn't found:

option<customer>

This is similar to Rust Option, C++ std::optional, or Haskell Maybe.

This is a special case of a variant type. WIT defines it so that there is a common way of expressing it, so that you don't need to create a variant type for every value type, and to enable it to be mapped idiomatically into languages with option types.

Results

result<T, E> for any types T and E may contain a value of type T or a value of type E (but not both). This is typically used for "value or error" situations; for example, a HTTP request function might return a result, with the success case (the T type) representing a HTTP response, and the error case (the E type) representing the various kinds of error that might occur:

result<http-response, http-error>

This is similar to Rust Result, or Haskell Either.

This is a special case of a variant type. WIT defines it so that there is a common way of expressing it, so that you don't need to create a variant type for every combination of value and error types, and to enable it to be mapped idiomatically into languages with result or "either" types.

Sometimes there is no data associated with one or both of the cases. For example, a print function could return an error code if it fails, but has nothing to return if it succeeds. In this case, you can omit the corresponding type as follows:

result<u32>     // no data associated with the error case
result<_, u32>  // no data associated with the success case
result          // no data associated with either case

Tuples

A tuple type is an ordered fixed length sequence of values of specified types. It is similar to a record, except that the fields are identified by their order instead of by names.

tuple<u64, string>  // An integer and a string
tuple<u64, string, u64>  // An integer, then a string, then an integer

This is similar to tuples in Rust or OCaml.

User-defined types

You can define your own types within an interface or world. WIT offers several ways of defining new types.

Records

A record type declares a set of named fields, each of the form name: type, separated by commas. A record instance contains a value for every field. Field types can be built-in or user-defined. The syntax is as follows:

record customer {
    id: u64,
    name: string,
    picture: option<list<u8>>,
    account-manager: employee,
}

Records are similar to C or Rust structs.

User-defined records can't be generic (that is, parameterised by type). Only built-in types can be generic.

Variants

A variant type declares one or more cases. Each case has a name and, optionally, a type of data associated with that case. A variant instance contains exactly one case. Cases are separated by commas. The syntax is as follows:

variant allowed-destinations {
    none,
    any,
    restricted(list<address>),
}

Variants are similar to Rust enums or OCaml discriminated unions. The closest C equivalent is a tagged union, but WIT both takes care of the "tag" (the case) and enforces the correct data shape for each tag.

User-defined variants can't be generic (that is, parameterised by type). Only built-in types can be generic.

Enums

An enum type is a variant type where none of the cases have associated data:

enum color {
    hot-pink,
    lime-green,
    navy-blue,
}

This can provide a simpler representation in languages without discriminated unions. For example, a WIT enum can translate directly to a C++ enum.

Flags

A flags type is a set of named booleans. In an instance of the type, each flag will be either true or false.

flags allowed-methods {
    get,
    post,
    put,
    delete,
}

A flags type is logically equivalent to a record type where each field is of type bool, but it is represented more efficiently (as a bitfield) at the binary level.

Type aliases

You can define a new named type using type ... = .... This can be useful for giving shorter or more meaningful names to types:

type buffer = list<u8>
type http-result = result<http-response, http-error>

Functions

A function is defined by a name and a function type. Like in record fields, the name is separated from the type by a colon:

do-nothing: func()

The function type is the word func, followed by a parenthesised, comma-separated list of parameters (names and types). If the function returns a value, this is expressed as an arrow symbol (->) followed by the return type:

// This function does not return a value
print: func(message: string)

// These functions return values
add: func(a: u64, b: u64) -> u64
lookup: func(store: kv-store, key: string) -> option<string>

A function can have multiple return values. In this case the return values must be named, similar to the parameter list. All return values must be populated (in the same way as tuple or record fields).

get-customers-paged: func(cont: continuation-token) -> (customers: list<customer>, cont: continuation-token)

A function can be declared as part of an interface, or can be declared as an import or export in a world.

Interfaces

An interface is a named set of types and functions, enclosed in braces and introduced with the interface keyword:

interface canvas {
    type canvas-id = u64

    record point {
        x: u32,
        y: u32,
    }

    draw-line: func(canvas: canvas-id, from: point, to: point)
}

Notice that items in an interface are not comma-separated.

Using definitions from elsewhere

An interface can reuse types declared in another interface via a use directive. The use directive must give the interface where the types are declared, then a dot, then a braced list of the types to be reused. The interface can then refer to the types named in the use.

interface types {
    type dimension = u32
    record point {
        x: dimension,
        y: dimension,
    }
}

interface canvas {
    use types.{dimension, point}
    type canvas-id = u64
    draw-line: func(canvas: canvas-id, from: point, to: point, thickness: dimension)
}

Even if you are only using one type, it must still be enclosed in braces. For example, use types.{dimension} is legal but use types.dimension is not.

This works across files as long as the files are in the same package (effectively, in the same directory). For information about using definitions from other packages, see the specification.

Worlds

A world describes a set of imports and exports, enclosed in braces and introduced with the world keyword. Roughly, a world describes the contract of a component. Exports are provided by the component, and define what consumers of the component may call; imports are things the component may call. The imports and exports may be interfaces or individual functions.

interface printer {
    print: func(text: string)
}

interface error-reporter {
    report-error: func(error-message: string)
}

world multi-function-device {
    // The component implements the `printer` interface
    export printer
    // The component implements the `scan` function
    export scan: func() -> list<u8>
    // The component needs to be supplied with an `error-reporter`
    import error-reporter
}

Interfaces from other packages

You can import and export interfaces defined in other packages. This can be done using package/name syntax:

world http-proxy {
    export wasi:http/incoming-handler
    import wasi:http/outgoing-handler
}

As this example shows, import and export apply at the interface level, not the package level. You can import one interface defined in a package, while exporting another interface defined in the same package. Packages group definitions; they don't represent behaviour.

WIT does not define how packages are resolved - different tools may resolve them in different ways.

Inline interfaces

Interfaces can be declared inline in a world:

world toy {
    export example: interface {
        do-nothing: func()
    }
}

Including other worlds

You can include another world. This causes your world to export all that world's exports, and import all that world's imports.

world glow-in-the-dark-multi-function-device {
    // The component provides all the same exports, and depends on all the same imports, as a `multi-function-device`...
    include multi-function-device
    // ...but also exports a function to make it glow in the dark
    export glow: func(brightness: u8)
}

As with use directives, you can include worlds from other packages.

Packages

A package is a set of interfaces and worlds, potentially defined across multiple files. To declare a package, use the package directive to specify the package ID. This must include a namespace and name, separated by a colon, and may optionally include a semver-compliant version:

package documentation:example
package documentation:example@1.0.1

If a package spans multiple files, only one file needs to contain a package declaration (but if multiple files contain declarations then they must all be the same). All files must have the .wit extension and must be in the same directory. For example, the following documentation:http package is spread across four files:

// types.wit
interface types {
    record request { /* ... */ }
    record response { /* ... */ }
}

// incoming.wit
interface incoming-handler {
    use types.{request, response}
    // ...
}

// outgoing.wit
interface outgoing-handler {
    use types.{request, response}
    // ...
}

// http.wit
package documentation:http@1.0.0

world proxy {
    export incoming-handler
    import outgoing-handler
}

ⓘ For a more formal definition of the WIT language, take a look at the WIT specification.

WIT Worlds

A WIT world is a higher-level contract that describes a component's capabilities and needs.

In one sense, a world defines a component - it says what interfaces the component exposes for other code to call, and what interfaces the component depends on - but it defines only the surface of a component, not the internal behaviour. This is a useful way to think about "business logic" components, like libraries that export APIs for other components to use. If you're an application or library developer, you'll define custom worlds to expose the functionality of each component you create - and to declare the functionality your component depends on.

In another sense, though, a world defines a hosting environment for components. For example, WASI (the WebAssembly System Interface) defines a "command line" world which imports interfaces such as file I/O, random number generation, clocks and so on - the sort of APIs a piece of code running in a POSIX or Win32 environment might want to call. Worlds like this are more akin to operating systems or server frameworks; unless you're building a Wasm host, you'll never define or implement one of these worlds, only use the worlds these environments define for you. The 'world' concept unifies the "library" and "host" senses at a technical level, which enables some powerful techniques, but can also be daunting when first encountering the component model!

A world is composed of interfaces, but each interface is directional - it indicates whether the interface is available for outside code to call (an "export"), or outside code must fulfill the interface for the component to call (an "import"). These interfaces strictly bound the component. A component cannot interact with anything outside itself except by having its exports called, or by calling its imports. This provides very strong sandboxing; for example, if a component does not have an import for a secret store, then it cannot access that secret store, even if the store is running in the same process.

For a component to run, its imports must be fulfilled, by a host or by other components. Connecting up one component's imports to another component's matching exports is called composition.

  • A (trivial) "HTTP proxy" world would export a "receive HTTP requests" interface, and import a "send HTTP requests" interface. A host, or another component, would call the exported "receive" interface, passing an HTTP request; the component would forward it on via the imported "send" interface. To be a useful proxy, the component may also need to import interfaces such as I/O and clock time - without those imports the component could not perform, for example, on-disk caching.
  • The "WASI (WebAssembly System Interface) command line" world is a classic example of a "hosting environment" use of the world concept. This world exports APIs such as file I/O, sockets, random number generation, and other POSIX-style functionality, so that application components that depend on this world - that is, command line applications - can use those familiar capabilities.
  • A "regex parser" world would export a "parse regex" function, and would import nothing. This declares not only that the component implementing this world can parse regular expressions, but also that it calls no other APIs. A user of such a parser could know, without looking at the implementation, that is does not access the file system, or send the user's regexes to a network service.

ⓘ For a more formal definition of what a WIT world is, take a look at the WIT world specification.

WIT Packages

A WIT package is a set of one or more WIT (Wasm Interface Type) files containing a related set of interfaces and worlds. WIT is an IDL (interface definition language) for the Component Model. Packages provide a way for worlds and interfaces to refer to each other, and thus for an ecosystem of components to share common definitions.

A WIT package is not a world. It's a way of grouping related interfaces and worlds together for ease of discovery and reference, more like a namespace.

  • The WebAssembly System Interface (WASI) defines a number of packages, including one named wasi:clocks. Our HTTP proxy world could import the wall-clock interface from the wasi:clocks package, rather than having to define a custom clock interface.

ⓘ For a more formal definition of what a WIT package is, take a look at the WIT specification.

Canonical ABI

An ABI is an application binary interface - an agreement on how to pass data around in a binary format. ABIs are specifically concerned with data layout at the bits-and-bytes level. For example, an ABI might define how integers are represented (big-endian or little-endian?), how strings are represented (pointer to null-terminated character sequence or length-prefixed? UTF-8 or UTF-16 encoded?), and how composite types are represented (the offsets of each field from the start of the structure).

The component model defines a canonical ABI - an ABI to which all components adhere. This guarantees that components can talk to each other without confusion, even if they are built in different languages. Internally, a C component might represent strings in a quite different way from a Rust component, but the canonical ABI provides a format for them to pass strings across the boundary between them.

ⓘ For a more formal definition of what the Canonical ABI is, take a look at the Canonical ABI explainer.

Wasm Language Support

WebAssembly can be targeted by the majority of top programming languages; however, the level of support varies. This document details the subset of languages that target WASI and support components. This is a living document, so if you are aware of advancements in a toolchain, please do not hesitate to contribute documentation. You can find more information about the development of support for specific languages here. Each section covers how to build and run components for a given toolchain.

One of the benefits of components is their portability across host runtimes. The runtime only needs to know what world the component is targeting in order to import or execute the component. This language guide hopes to demonstrate that with a prevailing example world defined in examples/example-host/add.wit. Furthermore, an example host that understands the example world has been provided in examples/example-host for running components. Each toolchain section walks through creating a component of this world, which can be run either in the example host or from an application of that toolchain. This aims to provide a full story for using components within and among toolchains.

Language Agnostic Tooling

Building a Component with wasm-tools

wasm-tools provides a suite of subcommands for working with WebAssembly modules and components.

wasm-tools can be used to create a component from WebAssembly Text (WAT). This walks through creating a component from WAT that implements the example world and simply adds two numbers.

  1. Install wasm-tools, a tool for low-level manipulation of Wasm modules and components.

  2. The add function is defined inside the following example world:

    package example:component
    
    world example {
        export add: func(x: s32, y: s32) -> s32
    }
    
  3. Define an add core module in WAT that exports an add function that adds two parameters:

    (module
      (func $add (param $lhs i32) (param $rhs i32) (result i32)
          local.get $lhs
          local.get $rhs
          i32.add)
      (export "add" (func $add))
    )
    
  4. Use wasm-tools to create a component from the core module, first embedding component metadata inside the core module and then encoding the WAT to a Wasm binary.

    $ wasm-tools component embed add.wit add.wat -o add.wasm
    $ wasm-tools component new add.wasm -o add.component.wasm
    

Running a Component with Wasmtime

You can "run" a component by calling one of its exports. Hosts and runtimes often only support running components with certain exports. The wasmtime CLI can only run "command" components, so in order to run the add function above, it first must be composed with a primary "command" component that calls it. See documentation on running components for more details.

Components in Rust

Rust has first-class support for the component model via the cargo component tool. It is a cargo subcommand for creating WebAssembly components using Rust as the component's implementation language.

Installing cargo component

To install cargo component, run:

cargo install cargo-component

You can find more details about cargo component in its crates.io page.

Building a Component with cargo component

Create a Rust program that implements the add function in the example world. Note that it imports the bindings that will be created by cargo-component. First scaffold a project:

$ cargo component new add --reactor && cd add

Update wit/world.wit to match add.wit and modify the component package reference to change the package name to example. The component section of Cargo.toml should look like the following:

[package.metadata.component]
package = "component:example"

cargo-component will generate bindings for the world specified in a package's Cargo.toml. In particular, it will create a Guest trait that a component should implement. Since our example world has no interfaces, the trait lives directly under the bindings module. Implement the Guest trait in add/src/lib.rs such that it satisfied the example world, adding an add function. It should look similar to the following:

cargo_component_bindings::generate!();
use bindings::Guest;

struct Component;

impl Guest for Component {
    fn add(x: i32, y: i32) -> i32 {
        x + y
    }
}

Now, build the component, being sure to optimize with a release build.

$ cargo component build --release

You can use wasm-tools component wit to output the WIT package of the component:

$ wasm-tools component wit add/target/wasm32-wasi/release/add.wasm
package root:component

world root {
  export add: func(x: s32, y: s32) -> s32
}

Running a Component from Rust Applications

To verify that our component works, lets run it from a Rust application that knows how to import a component of the example world.

The application uses wasmtime crates to generate Rust bindings, bring in WASI worlds, and execute the component.

$ cd examples/add-host
$ cargo run --release -- 1 2 ../add/target/wasm32-wasi/release/add.wasm
1 + 2 = 3

See the language guide.

Exporting an interface with cargo component

The sample add.wit file exports a function. However, to use your component from another component, it must export an interface. This results in slightly fiddlier bindings. For example, to implement the following world:

package docs:adder@0.1.0

interface add {
    add: func(a: u32, b: u32) -> u32
}

world adder {
    export add
}

you would write the following Rust code:

#![allow(unused)]
fn main() {
cargo_component_bindings::generate!();

// Separating out the interface puts it in a sub-module
use bindings::exports::docs::adder::add::Guest;

struct Component;

impl Guest for Component {
    fn add(a: u32, b: u32) -> u32 {
        a + b
    }
}
}

Importing an interface with cargo component

The world file (wit/world.wit) generated for you by cargo component new --reactor doesn't specify any imports.

cargo component build, by default, uses the Rust wasm32-wasi target, and therefore automatically imports any required WASI interfaces - no action is needed from you to import these. This section is about importing custom WIT interfaces from library components.

If your component consumes other components, you can edit the world.wit file to import their interfaces.

For example, suppose you have created and built an adder component as explained in the exporting an interface section and want to use that component in a calculator component. Here is a partial example world for a calculator that imports the add interface:

// in the 'calculator' project

// wit/world.wit
package docs:calculator

interface calculate {
    eval-expression: func(expr: string) -> u32
}

world calculator {
    export calculate
    import docs:adder/add@0.1.0
}

Referencing the package to import

Because the docs:adder package is in a different project, we must first tell cargo component how to find it. To do this, add the following to the Cargo.toml file:

[package.metadata.component.target.dependencies]
"docs:adder" = { path = "../adder/wit" }  # directory containing the WIT package

Note that the path is to the adder project's WIT directory, not to the world.wit file. A WIT package may be spread across multiple files in the same directory; cargo component will look at all the files.

Calling the import from Rust

Now the declaration of add in the adder's WIT file is visible to the calculator project. To invoke the imported add interface from the calculate implementation:

#![allow(unused)]
fn main() {
// src/lib.rs
cargo_component_bindings::generate!();

use bindings::exports::docs::calculator::calculate::Guest;

// Bring the imported add function into scope
use bindings::docs::adder::add::add;

struct Component;

impl Guest for Component {
    fn eval_expression(expr: String) -> u32 {
        // Cleverly parse `expr` into values and operations, and evaluate
        // them meticulously.
        add(123, 456)
    }
}
}

Fulfilling the import

When you build this using cargo component build, the add interface remains imported. The calculator has taken a dependency on the add interface, but has not linked the adder implementation of that interface - this is not like referencing the adder crate. (Indeed, calculator could import the add interface even if there was no Rust project implementing the WIT file.) You can see this by running wasm-tools component wit to view the calculator's world:

# Do a release build to prune unused imports (e.g. WASI)
$ cargo component build --release

$ wasm-tools component wit ./target/wasm32-wasi/release/calculator.wasm
package root:component

world root {
  import docs:adder/add@0.1.0

  export docs:calculator/calculate@0.1.0
}

As the import is unfulfilled, the calculator.wasm component could not run by itself in its current form. To fulfil the add import, so that the calculator can run, you would need to compose the calculator.wasm and adder.wasm files into a single, self-contained component.

Creating a command component with cargo component

A command is a component with a specific export that allows it to be executed directly by wasmtime (or other wasm:cli hosts). In Rust terms, it's the equivalent of an application (bin) package with a main function, instead of a library crate (lib) package.

To create a command with cargo component, run:

cargo component new <name>

Unlike library components, this does not have the --reactor flag. You will see that the created project is different too:

  • It doesn't contain a .wit file. cargo component build will automatically export the wasm:cli/run interface for Rust bin packages, and hook it up to main.
  • Because there's no .wit file, Cargo.toml doesn't contain a package.metadata.component.target section.
  • The Rust file is called main.rs instead of lib.rs, and contains a main function instead of an interface implementation.

You can write Rust in this project, just as you normally would, including importing your own or third-party crates.

All the crates that make up your project are linked together at build time, and compiled to a single Wasm component. In this case, all the linking is happening at the Rust level: no WITs or component composition is involved. Only if you import Wasm interfaces do WIT and composition come into play.

To run your command component:

cargo component build
wasmtime run --wasm component-model ./target/wasm32-wasi/debug/<name>.wasm

WARNING: If your program prints to standard out or error, you may not see the printed output! Some versions of wasmtime have a bug where they don't flush output streams before exiting. To work around this, add a std::thread::sleep() with a 10 millisecond delay before exiting main.

Importing an interface into a command component

As mentioned above, cargo component build doesn't generate a WIT file for a command component. If you want to import a Wasm interface, though, you'll need to create a WIT file and a world, plus reference the packages containing your imports:

  1. Add a wit/world.wit to your project, and write a WIT world that imports the interface(s) you want to use. For example:
package docs:app

world app {
    import docs:calculator/calculate@0.1.0
}

cargo component sometimes fails to find packages if versions are not set explicitly. For example, if the calculator WIT declares package docs:calculator rather than docs:calculator@0.1.0, then you may get an error even though cargo component build automatically versions the binary export.

  1. Edit Cargo.toml to tell cargo component about the new WIT file:
[package.metadata.component.target]
path = "wit"

(This entry is created automatically for library components but not for command components.)

  1. Edit Cargo.toml to tell cargo component where to find external package WITs:
[package.metadata.component.target.dependencies]
"docs:calculator" = { path = "../calculator/wit" }
"docs:adder" = { path = "../adder/wit" }

If the external package refers to other packages, you need to provide the paths to them as well.

  1. Use the imported interface in your Rust code:
use bindings::docs::calculator::calculate::eval_expression;

fn main() {
    let result = eval_expression("1 + 1");
    println!("1 + 1 = {result}");
    std::thread::sleep(Duration::from_millis(10));
}
  1. Compose the command component with the .wasm components that implement the imports.

  2. Run the composed component:

$ wasmtime run --wasm component-model ./my-composed-command.wasm
1 + 1 = 579  # might need to go back and do some work on the calculator implementation

JavaScript Tooling

jco is a fully native JS tool for working with the emerging WebAssembly Components specification in JavaScript.

Building a Component with jco

A component can be created from a JS module using jco componentize. First, install jco and componentize-js:

$ npm install @bytecodealliance/jco
$ npm install @bytecodealliance/componentize-js

Create a JavaScript module that implements the add function in add.wit:

export function add(x, y) {
  return x + y;
}

Now, use jco to create a component from the JS module:

$ jco componentize add.js --wit add.wit -n example -o add.wasm
OK Successfully written add.wasm with imports ().

Now, run the component using the Rust add host:

$ cd component-model/examples/add-host
$ cargo run --release -- 1 2 ../path/to/add.wasm
1 + 2 = 3

Running a Component from JavaScript Applications

As the JavaScript runtime cannot yet execute Wasm components, a component must be transpiled into JavaScript and a core module and then executed. jco automates this transpilation:

$ jco transpile add.wasm -o out-dir

Transpiled JS Component Files:

 - out-dir/add.core.wasm  6.72 MiB
 - out-dir/add.d.ts       0.05 KiB
 - out-dir/add.js          0.8 KiB

A core module and JavaScript bindings have been outputted to the out-dir.

Now, you can import the resultant add.js file and run it from a JavaScript application. This example renames it and imports it as an ECMAScript module for ease of running locally with node:

// app.mjs
import { add } from "./out-dir/add.mjs";

console.log("1 + 2 = " + add(1, 2));

The above example :

$ mv out-dir/add.js out-dir/add.mjs
$ node app.mjs
1 + 2 = 3

Python Tooling

Building a Component with componentize-py

componentize-py is a tool that converts a Python application to a WebAssembly component.

Create a Python program that implements the add function in the example world. Note that it imports the bindings that will be created by componentize-py:

$ cat<<EOT >> guest.py
import example

class Example(example.Example):
    def add(x: int, y: int) -> int:
        return x + y
EOT

Install componentize-py and generate a component from guest.py.

$ pip install componentize-py
$ componentize-py -d /path/to/examples/example-host/add.wit -w example componentize guest -o add.wasm
Component built successfully

To test the component, run it using the Rust add host:

$ cd component-model/examples/add-host
$ cargo run --release -- 1 2 ../path/to/add.wasm
1 + 2 = 3

Running components from Python Applications

Wasm components can also be invoked from Python applications. This walks through the tooling needed to call the app.wasm component from the previous section from a Python application. First, install wasmtime-py, being sure to use a version this PR has merged or working off that branch.

Note: be sure to use at least Python 3.11

$ git clone https://github.com/dicej/wasmtime-py
$ (cd wasmtime-py && python ci/download-wasmtime.py && python ci/build-rust.py && pip install .)

Now, generate the bindings to be able to call the component from a Python host application.

$ python3 -m wasmtime.bindgen add.wasm --out-dir add

The generated package add has all of the requisite exports/imports for the component and is annotated with types to assist with type-checking and self-documentation as much as possible.

Now, create a Python program to run the component. Note that imports for WASI preview 2 are explicitly set to null. This is because when creating a component from a Python module, componentize-py pulls in extra WASI Preview 2 imports, even if they are not used by the component. Currently, language toolchains are likely to pull in more than a component declares in WAT.

from add import Root, RootImports
from wasmtime import Store

def main():
    store = Store()
    component = Root(store, RootImports(poll=None, monotonic_clock=None, wall_clock=None, streams=None, filesystem=None, random=None, environment=None, preopens=None, exit=None, stdin=None, stdout=None, stderr=None))
    print("1 + 2 = ", component.add(store, 1, 2))

if __name__ == '__main__':
    main()

Run the Python host program:

$ python3 host.py
1 + 2 = 3

Creating and Consuming Components

The component model defines how components interface to each other and to hosts. This section describes how to work with components - from authoring them in custom code or by composing existing components, through to using them in applications and distributing them via registries.

Authoring Components

You can write WebAssembly core modules in a wide variety of languages, and the set of languages that can directly create components is growing. See the Language Support section for information on building components directly from source code.

If your preferred language supports WebAssembly but not components, you can still create components using the wasm-tools component tool. (A future version of this page will cover this in more detail.)

Composing Components

Because the WebAssembly component model packages code in a portable binary format, and provides machine-readable interfaces in WIT with a standardised ABI (Application Binary Interface), it enables applications and components to work together, no matter what languages they were originally written in. In the same way that, for example, a Rust package (crate) can be compiled together with other Rust code to create a higher-level library or an application, a Wasm component can be linked with other components.

Component model interoperation is more convenient and expressive than language-specific foreign function interfaces. A typical C FFI involves language-specific types, so it is not possible to link between arbitrary languages without at least some C-language wrapping or conversion. The component model, by contrast, provides a common way of expressing interfaces, and a standard binary representation of those interfaces. So if an import and an export have the same shape, they fit together directly.

What is composition?

When you compose components, you wire up the imports of one "primary" component to the exports of one or more other "dependency" components, creating a new component. The new component, like the original components, is a .wasm file, and its interface is defined as:

  • The new component exports the same exports as the primary component
  • The new component does not export the exports of the dependencies
  • The new component imports all the imports of the dependency components
  • The new component imports any imports of the primary component imports that the dependencies didn't satisfy
  • If several components import the same interface, the new component imports that interface - it doesn't "remember" that the import was declared in several different places

For example, consider two components with the following worlds:

// component `validator`
package docs:validator@0.1.0

interface validator {
    validate-text: func(text: string) -> string
}

world {
    export validator
    import docs:regex/match@0.1.0
}

// component 'regex'
package docs:regex@0.1.0

interface match {
    first-match: func(regex: string, text: string) -> string
}

world {
    export match
}

If we compose validator with regex, validator's import of docs:regex/match@0.1.0 is wired up to regex's export of match. The net result is that the composed component exports docs:validator/validator@0.1.0 and has no imports. The composed component does not export docs:regex/match@0.1.0 - that has become an internal implementation detail of the composed component.

Component composition tools are in their early stages right now. Here are some tips to avoid or diagnose errors:

  • Composition happens at the level of interfaces. If the initial component directly imports functions, then composition will fail. If composition reports an error such as "component path/to/component has a non-instance import named <name>" then check that all imports and exports are defined by interfaces.
  • Composition is asymmetrical. It is not just "gluing components together" - it takes a primary component which has imports, and satisfies its imports using dependency components. For example, composing an implementation of validator with an implementation of regex makes sense because validator has a dependency that regex can satisfy; doing it the other way round doesn't work, because regex doesn't have any dependencies, let alone ones that validator can satisfy.
  • Composition cares about interface versions, and current tools are inconsistent about when they infer or inject versions. For example, if a Rust component exports test:mypackage, cargo component build will decorate this with the crate version, e.g. test:mypackage@0.1.0. If another Rust component imports an interface from test:mypackage, that won't match test:mypackage@0.1.0. You can use wasm-tools component wit to view the imports and exports embedded in the .wasm files and check whether they match up.

Composing components with wasm-tools

The wasm-tools suite includes a compose command which can be used to compose components at the command line.

To compose a component with the components it directly depends on, run:

wasm-tools compose path/to/component.wasm -d path/to/dep1.wasm -d path/to/dep2.wasm -o composed.wasm

Here component.wasm is the component that imports interfaces from dep1.wasm and dep2.wasm, which export them. The composed component, with those dependencies satisfied and tucked away inside it, is saved to composed.wasm.

This syntax doesn't cover transitive dependencies. If, for example, dep1.wasm has unsatisfied imports that you want to satisfy from dep3.wasm, you'll need to use a configuration file. (Or you can compose dep1.wasm with dep3.wasm first, then refer to that composed component instead of dep1.wasm. This doesn't scale to lots of transitive dependencies though!)

For full information about wasm-tools compose including how to configure more advanced scenarios, see the wasm-tools compose documentation.

Composing components with a visual interface

You can compose components visually using the builder app at https://wasmbuilder.app/.

  1. Use the Add Component Button to upload the .wasm component files you want to compose. The components appear in the sidebar.

  2. Drag the components onto the canvas. You'll see imports listed on the left of each component, and exports on the right.

  3. Click the box in the top left to choose the 'primary' component, that is, the one whose exports will be preserved. (The clickable area is quite small - wait for the cursor to change from a hand to a pointer.)

  4. To fulfil one of the primary component's imports with a dependency's export, drag from the "I" icon next to the export to the "I" item next to the import. (Again, the clickable area is quite small - wait for the cursor to change from a hand to a cross.)

  5. When you have connected all the imports and exports that you want, click the Download Component button to download the composed component as a .wasm file.

Running Components

You can "run" a component by calling one of its exports. In some cases, this requires a custom host. For "command" components, though, you can use the wasmtime command line. This can be a convenient tool for testing components and exploring the component model. Other runtimes are also available - see the "Runtimes" section of the sidebar for more info.

A "command" component is one that exports the wasi:cli/run interface, and imports only interfaces listed in the wasi:cli/command world.

You must use a recent version of wasmtime (v14.0.0 or greater), as earlier releases of the wasmtime command line do not include component model support.

If you build the wasmtime CLI from source, you must pass --features component-model to the build command.

To run your component, run:

wasmtime run --wasm component-model <path-to-wasm-file>

Running components with custom exports

If you're writing a library-style component - that is, one that exports a custom API - then you can run it in wasmtime by writing a "command" component that imports and invokes your custom API. By composing the command and the library, you can exercise the library in wasmtime.

  1. Write your library component. The component's world (.wit file) must export an interface. (Do not export functions directly, only interfaces.) See the language support guide for how to implement an export.

  2. Build your library component to a .wasm file.

  3. Write your command component. The component's world (.wit file) must import the interface exported from the library. Write the command to call the library's API. See the language support guide for how to call an imported interface.

  4. Build your command component to a .wasm file. You will not be able to run this in wasmtime yet, as its imports are not yet satisfied.

  5. Compose your command component with your library component by running wasm-tools compose <path/to/command.wasm> -d <path/to/library.wasm> -o main.wasm.

  6. Run the composed component using wasmtime run --wasm component-model main.wasm

See Composing Components for more details.

Distributing Components

Modern applications rely extensively on third-party packages - so extensively that distributing packages is almost an industry in itself. Traditionally, these have been specific to a language. For example, JavaScript developers are used to using packages from NPM, and Rust developers use crates.io. Some runtimes support binary distribution and linking, enabling limited cross-language interop; for example, Maven packages can be written in any language that targets the Java runtime. Services like this are variously referred to as "package managers" or "registries."

Publishing and distribution are not defined by the core component model, but will form an important part of the component ecosystem. For example, if you're writing JavaScript, and want to pull in a highly optimised machine learning algorithm written in C and compiled to Wasm, you should be able to invoke it from a registry, just as easily as you would add a NPM package from the NPM registry.

Publishing and distribution is a work in progress. The proposed registry protocol is warg, but this is still in development, and there are no public warg registries as yet. You can find more information about the development of the registry protocol here.

Tutorial

If you like to learn by doing, this tutorial will walk through how to build, compose, and run components through a calculator example. Calculators can conduct many operations: add, subtract, multiply, and so on. In this example, each operation will be a component, that will be composed with an eval-expression component that will evaluate the expression using the expected operator. With one operation per component, this calculator is exaggeratedly granular to show how independent logic of an application can be contained in a component. In production, components will likely have a larger scope than a simple mathematical operation.

Our eventual solution will involve three components: one for the calculator engine, one for the addition operation, and one for the command-line interface. Once we have built these as separate Wasm components, we will compose them into a single runnable component, and test it using the wasmtime CLI.

The calculator interface

For tutorial purposes, we are going to define all our interfaces in one WIT package (in fact, one .wit file). This file defines:

  • An interface for the calculator itself. We'll use this later to carry out calculations. It contains an evaluate function, and an enum that delineates the operations that can be involved in a calculation. In this tutorial, the only operation is add.
  • Interfaces for the various operations the calculator might need to carry out as part of a calculation. For the tutorial, again, the only interface we define is for the "add" operation.
  • A world describing the calculator component. This world exports the calculator interface, meaning that other components can call it to perform calculations. It imports the operation interfaces (such as "add"), meaning it relies on other components to perform those operations.
  • A world describing each operator component. Again, there's just the "adder" world right now, and this exports the "add" interface, meaning that components such as the calculator can call it when they need to add numbers.
  • A world describing the "primary" app component, which imports the "calculate" interface. This is the component will take in command line arguments and pass them to the "eval-expression" function of the calculator component.
// calculator.wit
package docs:calculator@0.1.0

interface calculate {
    enum op {
        add,
    }
    eval-expression: func(op: op, x: u32, y: u32) -> u32
}

interface add {
    add: func(a: u32, b: u32) -> u32
}

world adder {
    export add
}

world calculator {
    export calculate
    import add
}

world app {
    import calculate
}

Create an add component

Reference the language guide and authoring components documentation to create a component that implements the adder world of calculator.wit. For reference, see the completed example.

Create a calculator component

Reference the language guide and authoring components documentation to create a component that implements the calculator world of calculator.wit. For reference, see the completed example. The component should import the add function from the adder world and call it if the op enum matches add.

Crate a command component

A command is a component with a specific export that allows it to be executed directly by wasmtime (or other wasm:cli hosts). The host expects it to export the wasi:cli/run interface, which is the equivalent of the main function to WASI. cargo-component will automatically resolve a Rust bin package with a main function to a component with wasi:cli/run exported. Scaffold a new Wasm application with a command component:

cargo component new command --command

This component will implement the app world, which imports the calculate interface. In Cargo.toml, point cargo-component to the WIT file and specify that it should pull in bindings for the app world:

[package.metadata.component.target]
path = "../path/to/calculator.wit"
world = "app"

Now, implement a command line application that:

  1. takes in three arguments: two operands and the name of an operator ("1 2 add")
  2. parses the operator name and ensures it is supported in the op enum
  3. calls the calculate interface's eval_expression, passing in the arguments.

For reference, see a completed example.

Composing the calculator

Now, we are ready to bring our components together into one runnable calculator component, using wasm-tools. We will first compose the calculator component with the add component to satisfy it's imports. We then compose that resolved calculator component with the command component to satisfy its calculate imports. The result is a command component that has all its imports satisfied and exports the wasi:cli/run function, which can be executed by wasmtime.

wasm-tools compose calculator.wasm -d adder.wasm -o calculator.wasm
wasm-tools compose command.wasm -d composed.wasm -o command.wasm

If you'd prefer to take a more visual approach to composing components, see the documentation on composing components with wasmbuilder.app.

Running the calculator

Now it all adds up! Run the command component with the wasmtime CLI, ensuring you are using a [v14.0.0 or greater release](https://github.com/bytecodealliance/wasmtime/releases), as earlier releases of the wasmtime` command line do not include component model support.

wasmtime run --wasm component-model command.wasm 1 2 add
1 + 2 = 3

To infinity and beyond!

To expand the exercise to add more components, modify calculator.wit to add another operator world and expand the op enum. Then, modify the command and calculator components to support the expanded enum.

Another extension of this tutorial could be to remove the op enum and instead modify eval-expression to take in a string that can then be parsed to determine which operator component to call. Maybe this parser is a component of its own?!

Wasmtime

Wasmtime is the reference implementation of the Component Model. It supports running components that implement the wasi:cli/command world and serving components the implement the wasi:http/proxy world.

Running command components with Wasmtime

To run a command component with wasmtime, execute:

wasmtime run --wasm component-model <path-to-wasm-file>

By default, Wasmtime denies the component access to all system resources. For example, the component cannot access the file system or environment variables. See the Wasmtime guide for information on granting access, and for other Wasmtime features.

Running HTTP components with Wasmtime

You can now execute components that implement the HTTP proxy world with the wasmtime serve subcommand. The Wasmtime CLI supports serving these components as of v14.0.3.

To run a HTTP component with Wasmtime, execute:

wasmtime serve <path-to-wasm-file>

Try out building and running HTTP components with one of these tutorials

  1. Hello WASI HTTP tutorial - build and serve a simple Rust-based HTTP component

  2. HTTP Auth Middleware tutorial - compose a HTTP authentication middleware component with a business logic component

jco

jco is a fully native JavaScript tool for working with components in JavaScript. It supports the wasi:cli/command world. jco also provides features for transpiling Wasm components to ES modules, and for building Wasm components from JavaScript and WIT.

To run a component with jco, run:

jco run <path-to-wasm-file> <command-args...>

jco's WASI implementation grants the component full access to the underlying system resources. For example, the component can read all environment variables of the jco process, or read and write files anywhere in the file system.