Skip to content

rustminded/xtask-wasm

Repository files navigation

xtask-wasm

actions status crate version documentation dependencies status licenses

Changelog

This crate aims to provide an easy and customizable way to help you build Wasm projects by extending them with custom subcommands, based on the xtask concept, instead of using external tooling like wasm-pack.

Changelog

Why xtask-wasm?

No external tools to install

wasm-pack and trunk are separate binaries that must be installed outside of Cargo — via cargo install, a shell script, or a system package manager. This means every contributor and every CI machine needs an extra installation step, and there is no built-in guarantee that everyone is running the same version.

With xtask-wasm, cargo xtask is all you need. The build tooling is a regular Cargo dependency, versioned in your Cargo.lock and reproduced exactly like every other dependency in your project.

wasm-bindgen version is always in sync

This is the most common source of pain with wasm-pack and trunk: the wasm-bindgen CLI tool version must exactly match the wasm-bindgen library version declared in your Cargo.toml. When they drift — after a cargo update, a fresh clone, or a CI cache invalidation — you get a cryptic error at runtime rather than a clear compile-time failure.

xtask-wasm uses wasm-bindgen-cli-support as a library dependency. The version is pinned in your Cargo.lock alongside your wasm-bindgen library dependency and kept in sync automatically — no manual version matching required.

Fully customizable

Because the build process is plain Rust code living inside your workspace, you can extend, replace or wrap any step. wasm-pack and trunk are opaque binaries driven by configuration files; xtask-wasm gives you the full build logic as code, under your control.

Setup

The best way to add xtask-wasm to your project is to create a workspace with two packages: your project's package and the xtask package.

Create a project using xtask

  • Create a new directory that will contains the two package of your project and the workspace's Cargo.toml:

    mkdir my-project
    cd my-project
    touch Cargo.toml
  • Create the project package and the xtask package using cargo new:

    cargo new my-project
    cargo new xtask
  • Open the workspace's Cargo.toml and add the following:

    [workspace]
    default-members = ["my-project"]
    members = [
        "my-project",
        "xtask",
    ]
    resolver = "2"
  • Create a .cargo/config.toml file and add the following content:

    [alias]
    xtask = "run --package xtask --"

The directory layout should look like this:

project
├── .cargo
│   └── config.toml
├── Cargo.toml
├── my-project
│   ├── Cargo.toml
│   └── src
│       └── ...
└── xtask
    ├── Cargo.toml
    └── src
        └── main.rs

And now you can run your xtask package using:

cargo xtask

You can find more informations about xtask here.

Use xtask-wasm as a dependency

Finally, add xtask-wasm to your dependencies:

cargo add -p xtask xtask-wasm

Usage

This library gives you three structs:

  • Dist - Generate a distributed package for Wasm.
  • Watch - Re-run a given command when changes are detected (using xtask-watch).
  • DevServer - Serve your project at a given IP address.

They all implement clap::Parser allowing them to be added easily to an existing CLI implementation and are flexible enough to be customized for most use-cases.

The pre and post hooks of DevServer accept any type implementing the Hook trait. This lets you construct a process::Command based on the server's final configuration — for example, to pass the resolved dist_dir or port as arguments to an external tool. A blanket implementation is provided for process::Command itself, so no changes are needed for simple use-cases.

Asset files copied by Dist can be processed by types implementing the Transformer trait. Transformers are tried in order for each file; the first to return Ok(true) claims the file, while unclaimed files are copied verbatim. When the sass feature is enabled, SassTransformer is available to compile SASS/SCSS files to CSS.

You can find further information for each type at their documentation level.

Examples

A basic implementation

use std::process::Command;
use xtask_wasm::{anyhow::Result, clap};

#[derive(clap::Parser)]
enum Opt {
    Dist(xtask_wasm::Dist),
    Watch(xtask_wasm::Watch),
    Start(xtask_wasm::DevServer),
}


fn main() -> Result<()> {
    env_logger::builder()
        .filter_level(log::LevelFilter::Info)
        .init();

    let opt: Opt = clap::Parser::parse();

    match opt {
        Opt::Dist(dist) => {
            log::info!("Generating package...");

            dist
                .assets_dir("my-project/assets")
                .app_name("my-project")
                .build("my-project")?;
        }
        Opt::Watch(watch) => {
            log::info!("Watching for changes and check...");

            let mut command = Command::new("cargo");
            command.arg("check");

            watch.run(command)?;
        }
        Opt::Start(dev_server) => {
            log::info!("Starting the development server...");

            dev_server
                .xtask("dist")
                .start()?;
        }
    }

    Ok(())
}

Note: this basic implementation uses env_logger and log. Add them to the Cargo.toml of your xtask (or use your preferred logger).

Provides a basic implementation of xtask-wasm to generate the web app package, a fractal clock screensaver using egui and egui-screensaver-fractal-clock. This example demonstrates a simple directory layout and a dist process that uses the wasm-opt feature via Dist::optimize_wasm.

The available subcommands are:

  • Build and optimize the web app package (downloads wasm-opt if not cached).

    cargo xtask dist
  • Build the web app package and watch for changes in the workspace root.

    cargo xtask watch
  • Serve an optimized web app dist on 127.0.0.1:8000 and watch for changes in the workspace root.

    cargo xtask start

Additional flags can be found using cargo xtask <subcommand> --help.

This example also demonstrates the use of the run-example feature with a Mystify screensaver using egui-screensaver-mystify:

cargo run --example run_example

A Mystify screensaver built with egui. Demonstrates how to use the run-example feature to run examples locally and how to set up GitHub Actions to release the Wasm app automatically.

A fractal clock screensaver built with egui. Also demonstrates the run-example feature and automated GitHub releases.

Features

  • wasm-opt: enable the WasmOpt struct and Dist::optimize_wasm for downloading and running wasm-opt automatically as part of the dist build. This is the recommended way to integrate wasm-opt — no custom wrapper struct or manual path computation needed:

    // requires the `wasm-opt` feature
    dist.optimize_wasm(WasmOpt::level(1).shrink(2))
        .build("my-project")?;
  • run-example: a helper to run examples from examples/ directory using a development server.

  • sass: enable SASS/SCSS compilation via SassTransformer. Add it to your Dist with .transformer(SassTransformer::default()).

Troubleshooting

When using the re-export of clap, you might encounter this error:

error[E0433]: failed to resolve: use of undeclared crate or module `clap`
 --> xtask/src/main.rs:4:10
  |
4 | #[derive(Parser)]
  |          ^^^^^^ use of undeclared crate or module `clap`
  |
  = note: this error originates in the derive macro `Parser` (in Nightly builds, run with -Z macro-backtrace for more info)

This occurs because you need to import clap in the scope too. This error can be resolved like this:

use xtask_wasm::clap;

#[derive(clap::Parser)]
struct MyStruct {}

Or like this:

use xtask_wasm::{clap, clap::Parser};

#[derive(Parser)]
struct MyStruct {}

About

Customizable subcommands to build your Wasm projects using xtask.

Resources

License

Stars

Watchers

Forks

Contributors

Languages