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::*;