Skip to content

Repository files navigation

Wasmono

Buck2 rules for building WebAssembly components.

See also similar rules for bazel: https://github.com/pulseengine/rules_wasm_component

Quick Start

Probably the easiest way of getting buck2 is via dotslash (https://dotslash-cli.com). This will also ensure using the buck2 version that the rules were tested against.

cargo binstall dotslash

#Run hello world example
./buck2 run //examples/hello:run

# Run the validator example
./buck2 run //examples/validator/runner:validator -- "Hello World"

# Run the http example
./buck2 run //examples/http/runner:runner

Regenerate Rust third-party rules

Rust dependencies are resolved in third-party/Cargo.toml and translated to Buck targets in third-party/BUCK with a pinned Reindeer binary:

./reindeer buckify

Features

  • Component Building: Create WASM components from Rust, C, and C++
  • WIT Bindings: Generate language bindings from WIT definitions
  • Composition: Link, plug, and compose components together (via WAC)
  • Package Management: Fetch components from WASM registries
  • Optimization: Run wasm-opt (Binaryen) on modules
  • Partial Evaluation: Specialize WASM interpreters into compiled code (via weval)
  • Pre-initialization: Snapshot module initialization state (via wasmtime wizer)
  • JavaScript Components: Build components from JavaScript (via jco)

Build a Component

load("@toolchains//wasm:component.bzl", "wasm_component")

# Build the Rust binary with WASM target
rust_binary(
    name = "regex",
    crate = "regex",
    crate_root = "src/lib.rs",
    edition = "2024",
    srcs = glob(["src/**/*.rs"]) + glob(["wit/**/*.wit"]),
    deps = [
        "//third-party:regex",
        "//third-party:wit-bindgen",
    ],
)

# Promote output of regex to wasm component
wasm_component(
    name = "regex_component",
    module = ":regex",
    wit = "wit/regex.wit",
    visibility = ['PUBLIC'],
)

By default, wasm_component builds the module as WASI Preview 2 and runs wasm-tools component new without a Preview 1 adapter. Setting adapter selects a WASI Preview 1 adapter and configures the module as wasip1 unless you set wasi explicitly:

wasm_component(
    name = "cli_component",
    module = ":cli",
    adapter = "command",
)

If the dependency already produces a component, use component instead of module so downstream rules receive WasmInfo without re-running component new.

For WASI Preview 2 components, and newer component-shaped worlds such as WASI Preview 3 test components, omit adapter. The adapter attribute is only for adapting WASI Preview 1 core modules before componentization.

Available Rules

Component Rules

  • wasm_component - Create a component from a WASM module, or declare an existing component
  • wasm_component_link - Link multiple components
  • wasm_plug - Compose components using plug pattern
  • wasm_compose - Compose components using WAC composition files
  • wasm_validate - Validate WASM binaries
  • wasm_print - Convert WASM to text format
  • wasm_opt - Optimize WASM modules with Binaryen
  • wasm_weval - Partially evaluate WASM modules with weval
  • wasm_wizer - Pre-initialize WASM modules with wasmtime wizer

Binding Generation

  • wit_bindgen_rust - Generate Rust bindings
  • wit_bindgen_c - Generate C bindings
  • wit_bindgen_cxx - Generate C++ bindings
  • wit_to_markdown - Generate documentation

WIT inputs can be files, directories, or dependencies. Dependencies' WasmInfo.wit and WitBindingInfo.wit metadata takes precedence over their default outputs, so generated source files are not mistaken for WIT. Repeated inputs are deduplicated. Version-qualified worlds such as test:api/api@1.2.3 are supported. The C generator supports string_encoding = "utf8" or "utf16"; latin1 and compact-utf16 are not supported by the pinned generator. wit_to_markdown produces a directory containing the generated documentation.

JavaScript

  • wasm_componentize_js - Build component from JavaScript (via jco)
  • wasm_jco_run - Run a WASI command component with jco run

Package Management

  • wasm_package - Download packages from registries
  • wit_library - Define a WIT library with automatic dependency resolution (via wkg)

wasm_package requires an exact @version or expected_sha256 digest by default. Version ranges do not pin a package. allow_unpinned = True explicitly opts out of this reproducibility check.

wasm_package and wasm_compose require toolchains//:python_bootstrap, included in the external-cell setup below.

Composition requires explicit dependencies. Replace wasm_plug's old socket_registry and plugs_registry arguments with pinned wasm_package targets passed through socket and plugs. Every external package referenced by a wasm_compose WAC file must appear in deps; composition will not look up undeclared registry packages or workspace dependencies. Version-qualified keys such as "test:plug@1.2.3" take precedence over unversioned keys.

Example: Multi-Component App

# Regex component (Rust)
rust_binary(
    name = "regex_lib",
    srcs = glob(["src/**/*.rs"]),
)

wasm_component(
    name = "regex_component",
    module = ":regex_lib",
    wit = "wit/regex.wit",
)

# Validator component (C++)
wit_bindgen_cxx(
    name = "validator_bindings",
    world = "validator",
    wit = ["wit/validator.wit"],
)

cxx_binary(
    name = "validator",
    srcs = ["src/validator.cpp"],
    deps = [":validator_bindings"],
    _cxx_toolchain = "toolchains//:cxx_wasi_p1",
)

wasm_opt(
    name = "validator_opt",
    input = ":validator",
    optimization = "os",
)

wasm_component(
    name = "validator_component",
    module = ":validator_opt",
    adapter = "reactor",
    wit = "wit/validator.wit",
)

# Compose: plug regex into validator
wasm_plug(
    name = "app",
    socket = ":validator_component",
    plugs = [":regex_component"],
)

Toolchains

Most toolchains are downloaded and checksum-pinned rather than relying on system installations. The jco and AssemblyScript setup paths pin npm package versions and install them on the selected execution platform, which must have registry access. Their npm workspaces and native Node distribution must match that platform's OS and architecture. For jco, supply package_json and package_lock together to use npm ci instead of the default npm install. Explicit system/preinstalled setups are also supported.

Downloaded binary toolchains are composed of two parts:

  1. Distribution: Downloads and extracts the tool binary
  2. Toolchain: Provides commands and subcommands through RunInfo

Native subcommands compose directly with cmd_args; shell launchers are only needed where platform dispatch requires them, such as Windows .cmd tools.

This separation makes it easy to control tool sources and versions:

# Download the distribution
download_wasm_tools(
    name = "wasm_tools_dist",
    version = "1.259.0",
)

# Create toolchain from distribution
wasm_tools_toolchain(
    name = "wasm_tools",
    distribution = ":wasm_tools_dist",
    visibility = ["PUBLIC"],
)

Available toolchains:

Using as an External Cell

Wasmono can be used as a git external cell in other Buck2 projects.

1. Configure .buckconfig

[cells]
  root = .
  wasmono = wasmono
  toolchains = toolchains
  prelude = prelude
  none = none

[cell_aliases]
  config = prelude
  ovr_config = prelude
  fbcode = none
  fbsource = none
  fbcode_macros = none
  buck = none

[external_cells]
  prelude = bundled
  wasmono = git

[external_cell_wasmono]
  git_origin = https://github.com/andreiltd/wasmono.git
  commit_hash = <sha1>

[build]
  execution_platforms = prelude//platforms:default

[parser]
  target_platform_detector_spec = target:root//...->prelude//platforms:default

Create an empty none/BUCK file (required by cell aliases).

2. Set up toolchains/BUCK

The wasmono rules reference toolchains//:wasm_tools, toolchains//:wit_bindgen, etc. You must define these targets in your own toolchains/BUCK. The wasm_demo_toolchains() macro creates all WASM toolchain targets with sensible defaults:

load("@prelude//toolchains:cxx.bzl", "system_cxx_toolchain")
load("@prelude//toolchains:genrule.bzl", "system_genrule_toolchain")
load("@prelude//toolchains:python.bzl", "system_python_bootstrap_toolchain")
load("@prelude//toolchains:rust.bzl", "system_rust_toolchain")
load("@wasmono//:defs.bzl", "cxx_wasi_toolchain", "download_wasi_sdk", "wasm_demo_toolchains")

_DEFAULT_TRIPLE = select({
    "config//os:wasi": select({
        "config//cpu:wasm32": select({
            "wasmono//wasm/constraints:wasip1": "wasm32-wasip1",
            "DEFAULT": "wasm32-wasip2",
        }),
    }),
    "config//os:linux": select({
        "config//cpu:arm64": "aarch64-unknown-linux-gnu",
        "config//cpu:x86_64": "x86_64-unknown-linux-gnu",
    }),
    "config//os:macos": select({
        "config//cpu:arm64": "aarch64-apple-darwin",
        "config//cpu:x86_64": "x86_64-apple-darwin",
    }),
})

system_genrule_toolchain(name = "genrule", visibility = ["PUBLIC"])
system_cxx_toolchain(name = "cxx", visibility = ["PUBLIC"])
system_python_bootstrap_toolchain(name = "python_bootstrap", visibility = ["PUBLIC"])

system_rust_toolchain(
    name = "rust",
    default_edition = "2024",
    rustc_target_triple = _DEFAULT_TRIPLE,
    visibility = ["PUBLIC"],
)

# WASM toolchains
wasm_demo_toolchains()

download_wasi_sdk(name = "wasi_sdk", version = "34.0")
cxx_wasi_toolchain(name = "cxx_wasi", distribution = ":wasi_sdk", visibility = ["PUBLIC"])
cxx_wasi_toolchain(
    name = "cxx_wasi_p1",
    distribution = ":wasi_sdk",
    target = "wasm32-wasip1",
    visibility = ["PUBLIC"],
)

3. Add platforms/BUCK

platform(
    name = "wasm32_wasi",
    constraint_values = [
        "wasmono//wasm/constraints:wasm32",
        "config//os/constraints:wasi",
    ],
)

4. Use the rules

The Wasmono wasm32 constraint preserves the consuming Prelude's CPU identity, including newer releases that expose it as cpu[wasm32] rather than a standalone target.

load("@wasmono//:defs.bzl", "wasm_component", "wasm_compose")

You can still load implementation modules such as @wasmono//toolchains/wasm:component.bzl directly, but @wasmono//:defs.bzl is the stable public entrypoint for user-facing rules and setup macros. It also exports lower-level setup helpers such as download_node, download_wasm_tools, host_arch, host_os, and wasm_transition_p1 for repositories that need more granular toolchain wiring than wasm_demo_toolchains().

Custom Tool Releases

By default, download_* functions and wasm_demo_toolchains() look up tool versions from the built-in release dictionaries shipped with wasmono. If you need a version that isn't included yet — or want to use a dev/nightly build — you can supply your own release data via the releases parameter.

Custom releases overlay the built-in releases: entries you provide take precedence, while built-in versions remain available as fallback.

Example: Using a newer wasm-tools version

Create a single releases file in your repo:

# my_releases.bzl
my_releases = {
    "wasm_tools": {
        "1.250.0": {
            "x86_64-linux": {
                "url": "<url>/wasm-tools-1.250.0-x86_64-linux.tar.gz",
                "shasum": "<sha256>",
            },
            "aarch64-macos": {
                "url": "<url>/wasm-tools-1.250.0-aarch64-macos.tar.gz",
                "shasum": "<sha256>",
            },
            # ... add platforms you need
        },
    },
}

Then pass it to wasm_demo_toolchains():

load(":my_releases.bzl", "my_releases")
load("@wasmono//toolchains/wasm:demo.bzl", "wasm_demo_toolchains")

wasm_demo_toolchains(
    wasm_tools_version = "1.259.0",
    releases = my_releases,
)

Acknowledgments

The http example is port of great p3 demo from https://github.com/ejrgilbert/component-interposition

About

Buck2 rules for building WebAssembly components

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages