Skip to main content

xtask_wasm/
lib.rs

1#![deny(missing_docs)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! This crate aims to provide an easy and customizable way to help you build
5//! Wasm projects by extending them with custom subcommands, based on the
6//! [`xtask` concept](https://github.com/matklad/cargo-xtask/), instead of using
7//! external tooling like [`wasm-pack`](https://github.com/rustwasm/wasm-pack).
8//!
9//! **[Changelog](https://github.com/rustminded/xtask-wasm/blob/main/CHANGELOG.md)**
10//!
11//! # Why xtask-wasm?
12//!
13//! ## No external tools to install
14//!
15//! `wasm-pack` and `trunk` are separate binaries that must be installed outside
16//! of Cargo — via `cargo install`, a shell script, or a system package manager.
17//! This means every contributor and every CI machine needs an extra installation
18//! step, and there is no built-in guarantee that everyone is running the same
19//! version.
20//!
21//! With xtask-wasm, `cargo xtask` is all you need. The build tooling is a
22//! regular Cargo dependency, versioned in your `Cargo.lock` and reproduced
23//! exactly like every other dependency in your project.
24//!
25//! ## `wasm-bindgen` version is always in sync
26//!
27//! This is the most common source of pain with `wasm-pack` and `trunk`: the
28//! `wasm-bindgen` CLI tool version must exactly match the `wasm-bindgen` library
29//! version declared in your `Cargo.toml`. When they drift — after a `cargo
30//! update`, a fresh clone, or a CI cache invalidation — you get a cryptic error
31//! at runtime rather than a clear compile-time failure.
32//!
33//! xtask-wasm uses [`wasm-bindgen-cli-support`](https://crates.io/crates/wasm-bindgen-cli-support)
34//! as a library dependency. The version is pinned in your `Cargo.lock` alongside
35//! your `wasm-bindgen` library dependency and kept in sync automatically — no
36//! manual version matching required.
37//!
38//! ## Fully customizable
39//!
40//! Because the build process is plain Rust code living inside your workspace,
41//! you can extend, replace or wrap any step. `wasm-pack` and `trunk` are
42//! opaque binaries driven by configuration files; xtask-wasm gives you the full
43//! build logic as code, under your control.
44//!
45//! # Setup
46//!
47//! The best way to add xtask-wasm to your project is to create a workspace
48//! with two packages: your project's package and the xtask package.
49//!
50//! ## Create a project using xtask
51//!
52//! * Create a new directory that will contains the two package of your project
53//!   and the workspace's `Cargo.toml`:
54//!
55//!   ```console
56//!   mkdir my-project
57//!   cd my-project
58//!   touch Cargo.toml
59//!   ```
60//!
61//! * Create the project package and the xtask package using `cargo new`:
62//!
63//!   ```console
64//!   cargo new my-project
65//!   cargo new xtask
66//!   ```
67//!
68//! * Open the workspace's `Cargo.toml` and add the following:
69//!
70//!   ```toml
71//!   [workspace]
72//!   default-members = ["my-project"]
73//!   members = [
74//!       "my-project",
75//!       "xtask",
76//!   ]
77//!   resolver = "2"
78//!   ```
79//!
80//! * Create a `.cargo/config.toml` file and add the following content:
81//!
82//!   ```toml
83//!   [alias]
84//!   xtask = "run --package xtask --"
85//!   ```
86//!
87//! The directory layout should look like this:
88//!
89//! ```console
90//! project
91//! ├── .cargo
92//! │   └── config.toml
93//! ├── Cargo.toml
94//! ├── my-project
95//! │   ├── Cargo.toml
96//! │   └── src
97//! │       └── ...
98//! └── xtask
99//!     ├── Cargo.toml
100//!     └── src
101//!         └── main.rs
102//! ```
103//!
104//! And now you can run your xtask package using:
105//!
106//! ```console
107//! cargo xtask
108//! ```
109//!
110//! You can find more informations about xtask
111//! [here](https://github.com/matklad/cargo-xtask/).
112//!
113//! ## Use xtask-wasm as a dependency
114//!
115//! Finally, add `xtask-wasm` to your dependencies:
116//!
117//! ```console
118//! cargo add -p xtask xtask-wasm
119//! ```
120//!
121//! # Usage
122//!
123//! This library gives you three structs:
124//!
125//! * [`Dist`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.Dist.html) - Generate a distributed package for Wasm.
126//! * [`Watch`](https://docs.rs/xtask-watch/latest/xtask_watch/struct.Watch.html) -
127//!   Re-run a given command when changes are detected
128//!   (using [xtask-watch](https://github.com/rustminded/xtask-watch)).
129//! * [`DevServer`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.DevServer.html) - Serve your project at a given IP address.
130//!
131//! They all implement [`clap::Parser`](https://docs.rs/clap/latest/clap/trait.Parser.html)
132//! allowing them to be added easily to an existing CLI implementation and are
133//! flexible enough to be customized for most use-cases.
134//!
135//! The pre and post hooks of [`DevServer`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.DevServer.html)
136//! accept any type implementing the
137//! [`Hook`](https://docs.rs/xtask-wasm/latest/xtask_wasm/trait.Hook.html) trait.
138//! This lets you construct a [`std::process::Command`] based on the server's final configuration
139//! — for example, to pass the resolved `dist_dir` or `port` as arguments to an external tool.
140//! A blanket implementation is provided for [`std::process::Command`] itself, so no changes are
141//! needed for simple use-cases.
142//!
143//! Asset files copied by [`Dist`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.Dist.html)
144//! can be processed by types implementing the
145//! [`Transformer`](https://docs.rs/xtask-wasm/latest/xtask_wasm/trait.Transformer.html) trait.
146//! Transformers are tried in order for each file; the first to return `Ok(true)` claims the file,
147//! while unclaimed files are copied verbatim. When the `sass` feature is enabled,
148//! [`SassTransformer`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.SassTransformer.html)
149//! is available to compile SASS/SCSS files to CSS.
150//!
151//! You can find further information for each type at their documentation level.
152//!
153//! # Examples
154//!
155//! ## A basic implementation
156//!
157//! ```rust,no_run
158//! use std::process::Command;
159//! use xtask_wasm::{anyhow::Result, clap};
160//!
161//! #[derive(clap::Parser)]
162//! enum Opt {
163//!     Dist(xtask_wasm::Dist),
164//!     Watch(xtask_wasm::Watch),
165//!     Start(xtask_wasm::DevServer),
166//! }
167//!
168//!
169//! fn main() -> Result<()> {
170//!     env_logger::builder()
171//!         .filter_level(log::LevelFilter::Info)
172//!         .init();
173//!
174//!     let opt: Opt = clap::Parser::parse();
175//!
176//!     match opt {
177//!         Opt::Dist(dist) => {
178//!             log::info!("Generating package...");
179//!
180//!             dist
181//!                 .assets_dir("my-project/assets")
182//!                 .app_name("my-project")
183//!                 .build("my-project")?;
184//!         }
185//!         Opt::Watch(watch) => {
186//!             log::info!("Watching for changes and check...");
187//!
188//!             let mut command = Command::new("cargo");
189//!             command.arg("check");
190//!
191//!             watch.run(command)?;
192//!         }
193//!         Opt::Start(dev_server) => {
194//!             log::info!("Starting the development server...");
195//!
196//!             dev_server
197//!                 .xtask("dist")
198//!                 .start()?;
199//!         }
200//!     }
201//!
202//!     Ok(())
203//! }
204//! ```
205//!
206//! Note: this basic implementation uses `env_logger` and `log`. Add them to the `Cargo.toml` of
207//! your `xtask` (or use your preferred logger).
208//!
209//! ## [`examples/demo`](https://github.com/rustminded/xtask-wasm/tree/main/examples/demo)
210//!
211//! Provides a basic implementation of xtask-wasm to generate the web app
212//! package, an "hello world" app using [Yew](https://yew.rs/). This example
213//! demonstrates a simple directory layout and a dist process that uses the
214//! `wasm-opt` feature via [`Dist::optimize_wasm`].
215//!
216//! The available subcommands are:
217//!
218//! * Build and optimize the web app package (downloads
219//!   [`wasm-opt`](https://github.com/WebAssembly/binaryen#tools) if not cached).
220//!
221//!   ```console
222//!   cargo xtask dist
223//!   ```
224//!
225//! * Build the web app package and watch for changes in the workspace root.
226//!
227//!   ```console
228//!   cargo xtask watch
229//!   ```
230//!
231//! * Serve an optimized web app dist on `127.0.0.1:8000` and watch for
232//!   changes in the workspace root.
233//!
234//!   ```console
235//!   cargo xtask start
236//!   ```
237//!
238//! Additional flags can be found using `cargo xtask <subcommand> --help`.
239//!
240//! This example also demonstrates the use of the `run-example` feature that allows you to use the
241//! following:
242//!
243//! ```console
244//! cargo run --example run_example
245//! ```
246//!
247//! This command will run the code in `examples/run_example` using the development server.
248//!
249//! # Features
250//!
251//! * `wasm-opt`: enable the
252//!   [`WasmOpt`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.WasmOpt.html) struct and
253//!   [`Dist::optimize_wasm`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.Dist.html#method.optimize_wasm)
254//!   for downloading and running [`wasm-opt`](https://github.com/WebAssembly/binaryen#tools)
255//!   automatically as part of the dist build. This is the recommended way to integrate wasm-opt —
256//!   no custom wrapper struct or manual path computation needed:
257//!
258//!   ```rust,ignore
259//!   // requires the `wasm-opt` feature
260//!   dist.optimize_wasm(WasmOpt::level(1).shrink(2))
261//!       .build("my-project")?;
262//!   ```
263//!
264//! * `run-example`: a helper to run examples from `examples/` directory using a development
265//!   server.
266//! * `sass`: enable SASS/SCSS compilation via [`SassTransformer`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.SassTransformer.html).
267//!   Add it to your [`Dist`](https://docs.rs/xtask-wasm/latest/xtask_wasm/struct.Dist.html) with `.transformer(SassTransformer::default())`.
268//!
269//! # Troubleshooting
270//!
271//! When using the re-export of [`clap`](https://docs.rs/clap/latest/clap), you
272//! might encounter this error:
273//!
274//! ```console
275//! error[E0433]: failed to resolve: use of undeclared crate or module `clap`
276//!  --> xtask/src/main.rs:4:10
277//!   |
278//! 4 | #[derive(Parser)]
279//!   |          ^^^^^^ use of undeclared crate or module `clap`
280//!   |
281//!   = note: this error originates in the derive macro `Parser` (in Nightly builds, run with -Z macro-backtrace for more info)
282//! ```
283//!
284//! This occurs because you need to import clap in the scope too. This error can
285//! be resolved like this:
286//!
287//! ```rust
288//! use xtask_wasm::clap;
289//!
290//! #[derive(clap::Parser)]
291//! struct MyStruct {}
292//! ```
293//!
294//! Or like this:
295//!
296//! ```rust
297//! use xtask_wasm::{clap, clap::Parser};
298//!
299//! #[derive(Parser)]
300//! struct MyStruct {}
301//! ```
302
303#[cfg(not(target_arch = "wasm32"))]
304use std::process::Command;
305
306#[cfg(not(target_arch = "wasm32"))]
307pub use xtask_watch::{
308    anyhow, cargo_metadata, cargo_metadata::camino, clap, metadata, package, xtask_command, Watch,
309};
310
311#[cfg(not(target_arch = "wasm32"))]
312mod dev_server;
313#[cfg(not(target_arch = "wasm32"))]
314mod dist;
315#[cfg(all(not(target_arch = "wasm32"), feature = "sass"))]
316mod sass;
317#[cfg(all(not(target_arch = "wasm32"), feature = "wasm-opt"))]
318mod wasm_opt;
319
320#[cfg(not(target_arch = "wasm32"))]
321pub use dev_server::*;
322#[cfg(not(target_arch = "wasm32"))]
323pub use dist::*;
324#[cfg(all(not(target_arch = "wasm32"), feature = "sass"))]
325#[cfg_attr(docsrs, doc(cfg(feature = "sass")))]
326pub use sass::*;
327
328#[cfg(all(not(target_arch = "wasm32"), feature = "wasm-opt"))]
329#[cfg_attr(docsrs, doc(cfg(feature = "wasm-opt")))]
330pub use wasm_opt::*;
331
332#[cfg(all(not(target_arch = "wasm32"), feature = "sass"))]
333#[cfg_attr(docsrs, doc(cfg(feature = "sass")))]
334pub use sass_rs;
335
336#[cfg(all(not(target_arch = "wasm32"), feature = "run-example"))]
337#[cfg_attr(docsrs, doc(cfg(feature = "run-example")))]
338pub use env_logger;
339
340#[cfg(all(not(target_arch = "wasm32"), feature = "run-example"))]
341#[cfg_attr(docsrs, doc(cfg(feature = "run-example")))]
342pub use log;
343
344/// Get the default command for the build in the dist process.
345///
346/// This is `cargo build --target wasm32-unknown-unknown`.
347#[cfg(not(target_arch = "wasm32"))]
348pub fn default_build_command() -> Command {
349    let mut command = Command::new("cargo");
350    command.args(["build", "--target", "wasm32-unknown-unknown"]);
351    command
352}
353
354#[cfg(all(target_arch = "wasm32", feature = "run-example"))]
355pub use console_error_panic_hook;
356
357#[cfg(all(target_arch = "wasm32", feature = "run-example"))]
358pub use wasm_bindgen;
359
360#[cfg(feature = "run-example")]
361pub use xtask_wasm_run_example::*;