From 9d55bfc1dcef4ee749474bb00648b6379091d346 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 11 Jun 2024 05:48:04 +0200 Subject: [PATCH] Integrate wasm-opt into the CLI (#2434) * integrate wasm-opt * include wasm opt configuration in the default dioxus.toml --- Cargo.lock | 129 ++++++++++++++++++++++++++++ packages/cli-config/src/config.rs | 54 ++++++++++++ packages/cli/Cargo.toml | 1 + packages/cli/src/assets/dioxus.toml | 4 + packages/cli/src/builder.rs | 80 ++++++++--------- packages/cli/src/tools.rs | 36 -------- 6 files changed, 225 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9721cf49..875c60d72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1435,6 +1435,16 @@ dependencies = [ "objc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -1933,6 +1943,50 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cxx" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb497fad022245b29c2a0351df572e2d67c1046bcef2260ebc022aec81efea82" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9327c7f9fbd6329a200a5d4aa6f674c60ab256525ff0084b52a889d4e4c60cee" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.60", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c799a4a846f1c0acb9f36bb9c6272d9b3d9457f3633c7753c6057270df13c" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bc249a7e3cd554fd2e8e08a426e9670c50bbfc9a621653cfa9accc9641783" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "darling" version = "0.20.8" @@ -2194,6 +2248,7 @@ dependencies = [ "walkdir", "wasm-bindgen-cli-support", "wasm-bindgen-shared", + "wasm-opt", "zip", ] @@ -5521,6 +5576,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -7863,6 +7927,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "sct" version = "0.7.1" @@ -8735,6 +8805,25 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "subprocess" version = "0.2.9" @@ -10010,6 +10099,46 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-opt" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" +dependencies = [ + "anyhow", + "libc", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-cxx-sys" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c57b28207aa724318fcec6575fe74803c23f6f266fce10cbc9f3f116762f12e" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-sys" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a1cce564dc768dacbdb718fc29df2dba80bd21cb47d8f77ae7e3d95ceb98cbe" +dependencies = [ + "anyhow", + "cc", + "cxx", + "cxx-build", +] + [[package]] name = "wasm-streams" version = "0.4.0" diff --git a/packages/cli-config/src/config.rs b/packages/cli-config/src/config.rs index e119a90d5..8c2f10e26 100644 --- a/packages/cli-config/src/config.rs +++ b/packages/cli-config/src/config.rs @@ -214,6 +214,7 @@ impl Default for DioxusConfig { cert_path: None, }, pre_compress: true, + wasm_opt: Default::default(), }, bundle: BundleConfig { identifier: Some(format!("io.github.{name}")), @@ -286,6 +287,55 @@ pub struct WebConfig { /// Whether to enable pre-compression of assets and wasm during a web build in release mode #[serde(default = "true_bool")] pub pre_compress: bool, + /// The wasm-opt configuration + #[serde(default)] + pub wasm_opt: WasmOptConfig, +} + +/// The wasm-opt configuration +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct WasmOptConfig { + /// The wasm-opt level to use for release builds [default: s] + /// Options: + /// - z: optimize aggressively for size + /// - s: optimize for size + /// - 1: optimize for speed + /// - 2: optimize for more for speed + /// - 3: optimize for even more for speed + /// - 4: optimize aggressively for speed + #[serde(default)] + pub level: WasmOptLevel, + + /// Keep debug symbols in the wasm file + #[serde(default = "false_bool")] + pub debug: bool, +} + +/// The wasm-opt level to use for release web builds [default: 4] +#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] +pub enum WasmOptLevel { + /// Optimize aggressively for size + #[serde(rename = "z")] + Z, + /// Optimize for size + #[serde(rename = "s")] + S, + /// Don't optimize + #[serde(rename = "0")] + Zero, + /// Optimize for speed + #[serde(rename = "1")] + One, + /// Optimize for more for speed + #[serde(rename = "2")] + Two, + /// Optimize for even more for speed + #[serde(rename = "3")] + Three, + /// Optimize aggressively for speed + #[serde(rename = "4")] + #[default] + Four, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -587,3 +637,7 @@ impl CrateConfig { fn true_bool() -> bool { true } + +fn false_bool() -> bool { + false +} diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index cf3646902..6e52634ce 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -101,6 +101,7 @@ env_logger = "0.11.3" tracing-subscriber = { version = "0.3.18", features = ["std", "env-filter"] } tracing = { workspace = true } +wasm-opt = "0.116.1" # on maco, we need to specify the vendored feature on ssl when cross compiling [target.'cfg(target_os = "macos")'.dependencies] diff --git a/packages/cli/src/assets/dioxus.toml b/packages/cli/src/assets/dioxus.toml index ef66e47c4..486e65a13 100644 --- a/packages/cli/src/assets/dioxus.toml +++ b/packages/cli/src/assets/dioxus.toml @@ -17,6 +17,10 @@ asset_dir = "public" # hot reload by default hot_reload = true +[web.wasm_opt] +# The level wasm-opt should target. z is the smallest. 4 is the fastest. +level = "4" + [web.app] # HTML title tag content diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index f21f17341..2d36429ab 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -8,7 +8,7 @@ use crate::{ }; use anyhow::Context; use cargo_metadata::{diagnostic::Diagnostic, Message}; -use dioxus_cli_config::{crate_root, CrateConfig, ExecutableType}; +use dioxus_cli_config::{crate_root, CrateConfig, ExecutableType, WasmOptLevel}; use indicatif::{ProgressBar, ProgressStyle}; use lazy_static::lazy_static; use manganis_cli_support::{AssetManifest, ManganisSupportGuard}; @@ -174,15 +174,18 @@ pub fn build_web( // [3] Bindgen the final binary for use easy linking let mut bindgen_builder = Bindgen::new(); + let keep_debug = dioxus_config.web.wasm_opt.debug || (!config.release); + bindgen_builder .input_path(&input_path) .web(true) .unwrap() - .debug(true) - .demangle(true) - .keep_debug(true) - .remove_name_section(false) - .remove_producers_section(false) + .debug(keep_debug) + .demangle(keep_debug) + .keep_debug(keep_debug) + .reference_types(true) + .remove_name_section(!keep_debug) + .remove_producers_section(!keep_debug) .out_name(&dioxus_config.application.name) .generate(&bindgen_outdir) .unwrap(); @@ -197,43 +200,33 @@ pub fn build_web( run_wasm_bindgen(); } - // check binaryen:wasm-opt tool - tracing::info!("Running optimization with wasm-opt..."); - let dioxus_tools = dioxus_config.application.tools.clone(); - if dioxus_tools.contains_key("binaryen") { - let info = dioxus_tools.get("binaryen").unwrap(); - let binaryen = crate::tools::Tool::Binaryen; - - if binaryen.is_installed() { - if let Some(sub) = info.as_table() { - if sub.contains_key("wasm_opt") - && sub.get("wasm_opt").unwrap().as_bool().unwrap_or(false) - { - tracing::info!("Optimizing WASM size with wasm-opt..."); - let target_file = out_dir - .join("assets") - .join("dioxus") - .join(format!("{}_bg.wasm", dioxus_config.application.name)); - if target_file.is_file() { - let mut args = vec![ - target_file.to_str().unwrap(), - "-o", - target_file.to_str().unwrap(), - ]; - if config.release { - args.push("-Oz"); - } - binaryen.call("wasm-opt", args)?; - } - } - } - } else { - tracing::warn!( - "Binaryen tool not found, you can use `dx tool add binaryen` to install it." - ); - } - } else { - tracing::info!("Skipping optimization with wasm-opt, binaryen tool not found."); + // Run wasm-opt if this is a release build + if config.release { + tracing::info!("Running optimization with wasm-opt..."); + let mut options = match dioxus_config.web.wasm_opt.level { + WasmOptLevel::Z => wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively(), + WasmOptLevel::S => wasm_opt::OptimizationOptions::new_optimize_for_size(), + WasmOptLevel::Zero => wasm_opt::OptimizationOptions::new_opt_level_0(), + WasmOptLevel::One => wasm_opt::OptimizationOptions::new_opt_level_1(), + WasmOptLevel::Two => wasm_opt::OptimizationOptions::new_opt_level_2(), + WasmOptLevel::Three => wasm_opt::OptimizationOptions::new_opt_level_3(), + WasmOptLevel::Four => wasm_opt::OptimizationOptions::new_opt_level_4(), + }; + let wasm_file = bindgen_outdir.join(format!("{}_bg.wasm", dioxus_config.application.name)); + let old_size = wasm_file.metadata()?.len(); + options + // WASM bindgen relies on reference types + .enable_feature(wasm_opt::Feature::ReferenceTypes) + .debug_info(dioxus_config.web.wasm_opt.debug) + .run(&wasm_file, &wasm_file) + .map_err(|err| Error::Other(anyhow::anyhow!(err)))?; + let new_size = wasm_file.metadata()?.len(); + tracing::info!( + "wasm-opt reduced WASM size from {} to {} ({:2}%)", + old_size, + new_size, + (new_size as f64 - old_size as f64) / old_size as f64 * 100.0 + ); } // If pre-compressing is enabled, we can pre_compress the wasm-bindgen output @@ -242,6 +235,7 @@ pub fn build_web( } // [5][OPTIONAL] If tailwind is enabled and installed we run it to generate the CSS + let dioxus_tools = dioxus_config.application.tools.clone(); if dioxus_tools.contains_key("tailwindcss") { let info = dioxus_tools.get("tailwindcss").unwrap(); let tailwind = crate::tools::Tool::Tailwind; diff --git a/packages/cli/src/tools.rs b/packages/cli/src/tools.rs index 284e0d36b..ec4502c3d 100644 --- a/packages/cli/src/tools.rs +++ b/packages/cli/src/tools.rs @@ -13,15 +13,10 @@ use tokio::io::AsyncWriteExt; #[derive(Debug, PartialEq, Eq)] pub enum Tool { - Binaryen, Sass, Tailwind, } -// pub fn tool_list() -> Vec<&'static str> { -// vec!["binaryen", "sass", "tailwindcss"] -// } - pub fn app_path() -> PathBuf { let data_local = dirs::data_local_dir().unwrap(); let dioxus_dir = data_local.join("dioxus"); @@ -70,7 +65,6 @@ impl Tool { /// from str to tool enum pub fn from_str(name: &str) -> Option { match name { - "binaryen" => Some(Self::Binaryen), "sass" => Some(Self::Sass), "tailwindcss" => Some(Self::Tailwind), _ => None, @@ -80,7 +74,6 @@ impl Tool { /// get current tool name str pub fn name(&self) -> &str { match self { - Self::Binaryen => "binaryen", Self::Sass => "sass", Self::Tailwind => "tailwindcss", } @@ -89,7 +82,6 @@ impl Tool { /// get tool bin dir path pub fn bin_path(&self) -> &str { match self { - Self::Binaryen => "bin", Self::Sass => ".", Self::Tailwind => ".", } @@ -98,17 +90,6 @@ impl Tool { /// get target platform pub fn target_platform(&self) -> &str { match self { - Self::Binaryen => { - if cfg!(target_os = "windows") { - "windows" - } else if cfg!(target_os = "macos") { - "macos" - } else if cfg!(target_os = "linux") { - "linux" - } else { - panic!("unsupported platformm"); - } - } Self::Sass => { if cfg!(target_os = "windows") { "windows" @@ -137,7 +118,6 @@ impl Tool { /// get tool version pub fn tool_version(&self) -> &str { match self { - Self::Binaryen => "version_105", Self::Sass => "1.51.0", Self::Tailwind => "v3.1.6", } @@ -146,13 +126,6 @@ impl Tool { /// get tool package download url pub fn download_url(&self) -> String { match self { - Self::Binaryen => { - format!( - "https://github.com/WebAssembly/binaryen/releases/download/{version}/binaryen-{version}-x86_64-{target}.tar.gz", - version = self.tool_version(), - target = self.target_platform() - ) - } Self::Sass => { format!( "https://github.com/sass/dart-sass/releases/download/{version}/dart-sass-{version}-{target}-x64.{extension}", @@ -179,7 +152,6 @@ impl Tool { /// get package extension name pub fn extension(&self) -> &str { match self { - Self::Binaryen => "tar.gz", Self::Sass => { if cfg!(target_os = "windows") { "zip" @@ -226,7 +198,6 @@ impl Tool { let tool_path = tools_path(); let dir_name = match self { - Self::Binaryen => format!("binaryen-{}", self.tool_version()), Self::Sass => "dart-sass".to_string(), Self::Tailwind => self.name().to_string(), }; @@ -281,13 +252,6 @@ impl Tool { let bin_path = tools_path().join(self.name()).join(self.bin_path()); let command_file = match self { - Tool::Binaryen => { - if cfg!(target_os = "windows") { - format!("{}.exe", command) - } else { - command.to_string() - } - } Tool::Sass => { if cfg!(target_os = "windows") { format!("{}.bat", command)