wasm_bundle/
lib.rs

1mod pack;
2#[cfg(test)]
3mod test;
4
5use pack::pack;
6use std::{
7    env::var,
8    ffi::OsStr,
9    fs::create_dir_all,
10    io::ErrorKind,
11    path::{Path, PathBuf},
12    process::{exit, Command, ExitStatus},
13};
14
15use clap::{Args, Parser};
16
17#[derive(Args, Debug, Default)]
18pub struct Opt {
19    /// Build with the dev profile
20    #[clap(long)]
21    pub release: bool,
22    #[clap(long)]
23    pub example: Option<String>,
24}
25
26#[derive(Parser, Debug)]
27#[clap(bin_name = "cargo")]
28pub enum Cli {
29    /// compile to self-contained `.js` and `.html` files using WASM
30    #[clap(version)]
31    WasmBundle(Opt),
32}
33
34pub fn exec(base_dir: &Path, opt: &Opt) {
35    let base_dir = std::fs::canonicalize(base_dir).unwrap();
36    let target_profile = match opt.release {
37        true => "release",
38        false => "debug",
39    };
40    let cargo_meta = cargo_metadata::MetadataCommand::new()
41        .current_dir(base_dir.clone())
42        .no_deps()
43        .exec()
44        .unwrap();
45    let mut cargo_build_target_dir = cargo_meta
46        .target_directory
47        .as_std_path()
48        .join("wasm32-unknown-unknown")
49        .join(target_profile);
50    let mut wasm_bindgen_target_dir = cargo_meta
51        .target_directory
52        .as_std_path()
53        .join("wasm-bindgen")
54        .join(target_profile);
55    let mut html_target_dir = cargo_meta
56        .target_directory
57        .as_std_path()
58        .join("wasm-bundle")
59        .join(target_profile);
60
61    let mut name = None;
62    for package in cargo_meta.workspace_packages() {
63        let manifest_path = package.manifest_path.clone().into_std_path_buf();
64        let manifest_path = std::fs::canonicalize(manifest_path).unwrap();
65        let manifest_dir = manifest_path.parent().unwrap();
66        if manifest_dir == base_dir {
67            name = Some(&package.name);
68        }
69    }
70
71    let mut name = name.expect("cargo wasm-bundle must be executed in a package root");
72
73    if let Some(example) = &opt.example {
74        name = example;
75        cargo_build_target_dir = cargo_build_target_dir.join("examples");
76        wasm_bindgen_target_dir = wasm_bindgen_target_dir.join("examples");
77        html_target_dir = html_target_dir.join("examples");
78    }
79
80    create_dir_all(&html_target_dir).unwrap();
81
82    cargo_build(&base_dir, &opt);
83    check_wasm_bindgen(&base_dir);
84    wasm_bindgen(
85        &base_dir,
86        &cargo_build_target_dir,
87        &name,
88        &wasm_bindgen_target_dir,
89    );
90    pack(name, &wasm_bindgen_target_dir, &html_target_dir);
91}
92
93fn cargo_build(base_dir: &Path, opt: &Opt) {
94    let mut cmd = Command::new(var("CARGO").unwrap_or("cargo".into()));
95    cmd.arg("build")
96        .args(["--target", "wasm32-unknown-unknown"]);
97    if opt.release {
98        cmd.arg("--release");
99    }
100    if let Some(example) = &opt.example {
101        cmd.args(["--example", example]);
102    }
103    run(base_dir, cmd)
104}
105
106fn check_wasm_bindgen(base_dir: &Path) {
107    let mut cmd = Command::new("wasm-bindgen");
108    cmd.arg("--version");
109    eprintln!("running {:?}", cmd);
110    match cmd.status() {
111        Ok(status) => check_status(status),
112        Err(err) => match err.kind() {
113            ErrorKind::NotFound => {
114                let mut cmd = Command::new(var("CARGO").unwrap());
115                cmd.args(["install", "wasm-bindgen-cli"]);
116                run(base_dir, cmd);
117            }
118            _ => Err(err).unwrap(),
119        },
120    }
121}
122
123fn wasm_bindgen(
124    base_dir: &Path,
125    cargo_build_target_dir: &PathBuf,
126    name: &str,
127    wasm_bindgen_target_dir: &PathBuf,
128) {
129    let mut cmd = Command::new("wasm-bindgen");
130    let infile = cargo_build_target_dir.join(format!("{name}.wasm"));
131    cmd.args(["--target", "web"])
132        .arg(infile.into_os_string())
133        .args([
134            &OsStr::new("--out-dir"),
135            wasm_bindgen_target_dir.as_os_str(),
136        ]);
137    run(base_dir, cmd)
138}
139
140pub(crate) fn run(base_dir: &Path, mut cmd: Command) {
141    cmd.current_dir(base_dir);
142    eprintln!("running {:?}", cmd);
143    check_status(cmd.status().unwrap());
144}
145
146fn check_status(status: ExitStatus) {
147    let code = status.code().unwrap();
148    if code != 0 {
149        exit(code)
150    }
151}