Files
dioxus/packages/cli/src/builder/web.rs
T
Evan Almloff e5e578d27b Add liveview support to the CLI and make fullstack runnable from dist (#2759)
* add liveview cli support
* Fix TUI fullstack deadlock
* look for fullstack assets in the public directory
* Fix fullstack with the CLI
* Fix static generation server
2024-08-01 17:46:35 -07:00

192 lines
8.3 KiB
Rust

use super::BuildRequest;
use super::BuildResult;
use crate::assets::pre_compress_folder;
use crate::builder::progress::Stage;
use crate::builder::progress::UpdateBuildProgress;
use crate::builder::progress::UpdateStage;
use crate::error::{Error, Result};
use futures_channel::mpsc::UnboundedSender;
use manganis_cli_support::AssetManifest;
use std::path::Path;
use tokio::process::Command;
use wasm_bindgen_cli_support::Bindgen;
// Attempt to automatically recover from a bindgen failure by updating the wasm-bindgen version
async fn update_wasm_bindgen_version() -> Result<()> {
let cli_bindgen_version = wasm_bindgen_shared::version();
tracing::info!("Attempting to recover from bindgen failure by setting the wasm-bindgen version to {cli_bindgen_version}...");
let output = Command::new("cargo")
.args([
"update",
"-p",
"wasm-bindgen",
"--precise",
&cli_bindgen_version,
])
.output()
.await;
let mut error_message = None;
if let Ok(output) = output {
if output.status.success() {
tracing::info!("Successfully updated wasm-bindgen to {cli_bindgen_version}");
return Ok(());
} else {
error_message = Some(output);
}
}
if let Some(output) = error_message {
tracing::error!("Failed to update wasm-bindgen: {:#?}", output);
}
Err(Error::BuildFailed(format!("WASM bindgen build failed!\nThis is probably due to the Bindgen version, dioxus-cli is using `{cli_bindgen_version}` which is not compatible with your crate.\nPlease reinstall the dioxus cli to fix this issue.\nYou can reinstall the dioxus cli by running `cargo install dioxus-cli --force` and then rebuild your project")))
}
/// Check if the wasm32-unknown-unknown target is installed and try to install it if not
pub(crate) async fn install_web_build_tooling(
progress: &mut UnboundedSender<UpdateBuildProgress>,
) -> Result<()> {
// If the user has rustup, we can check if the wasm32-unknown-unknown target is installed
// Otherwise we can just assume it is installed - which is not great...
// Eventually we can poke at the errors and let the user know they need to install the target
if let Ok(wasm_check_command) = Command::new("rustup").args(["show"]).output().await {
let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap();
if !wasm_check_output.contains("wasm32-unknown-unknown") {
_ = progress.start_send(UpdateBuildProgress {
stage: Stage::InstallingWasmTooling,
update: UpdateStage::Start,
});
tracing::info!("wasm32-unknown-unknown target not detected, installing..");
let _ = Command::new("rustup")
.args(["target", "add", "wasm32-unknown-unknown"])
.output()
.await?;
}
}
Ok(())
}
impl BuildRequest {
async fn run_wasm_bindgen(&self, input_path: &Path, bindgen_outdir: &Path) -> Result<()> {
tracing::info!("Running wasm-bindgen");
let input_path = input_path.to_path_buf();
let bindgen_outdir = bindgen_outdir.to_path_buf();
let keep_debug =
self.dioxus_crate.dioxus_config.web.wasm_opt.debug || (!self.build_arguments.release);
let name = self.dioxus_crate.dioxus_config.application.name.clone();
let run_wasm_bindgen = move || {
// [3] Bindgen the final binary for use easy linking
let mut bindgen_builder = Bindgen::new();
bindgen_builder
.input_path(&input_path)
.web(true)
.unwrap()
.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(&name)
.generate(&bindgen_outdir)
.unwrap();
};
let bindgen_result = tokio::task::spawn_blocking(run_wasm_bindgen.clone()).await;
// WASM bindgen requires the exact version of the bindgen schema to match the version the CLI was built with
// If we get an error, we can try to recover by pinning the user's wasm-bindgen version to the version we used
if let Err(err) = bindgen_result {
tracing::error!("Bindgen build failed: {:?}", err);
update_wasm_bindgen_version().await?;
run_wasm_bindgen();
}
Ok(())
}
/// Post process the WASM build artifacts
pub(crate) async fn post_process_web_build(
&self,
build_result: &BuildResult,
assets: Option<&AssetManifest>,
progress: &mut UnboundedSender<UpdateBuildProgress>,
) -> Result<()> {
_ = progress.start_send(UpdateBuildProgress {
stage: Stage::OptimizingWasm,
update: UpdateStage::Start,
});
// Find the wasm file
let output_location = build_result.executable.clone();
let input_path = output_location.with_extension("wasm");
// Create the directory where the bindgen output will be placed
let bindgen_outdir = self.target_out_dir().join("assets").join("dioxus");
// Run wasm-bindgen
self.run_wasm_bindgen(&input_path, &bindgen_outdir).await?;
// Only run wasm-opt if the feature is enabled
// Wasm-opt has an expensive build script that makes it annoying to keep enabled for iterative dev
#[cfg(feature = "wasm-opt")]
{
// Run wasm-opt if this is a release build
if self.build_arguments.release {
use dioxus_cli_config::WasmOptLevel;
tracing::info!("Running optimization with wasm-opt...");
let mut options = match self.dioxus_crate.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",
self.dioxus_crate.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(self.dioxus_crate.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
let pre_compress = self
.dioxus_crate
.should_pre_compress_web_assets(self.build_arguments.release);
tokio::task::spawn_blocking(move || pre_compress_folder(&bindgen_outdir, pre_compress))
.await
.unwrap()?;
// Create the index.html file
// Note that we do this last since the webserver will attempt to serve the index.html file
// If we do this too early, the wasm won't be ready but the index.html will be served, leading
// to test failures and broken pages.
let html = self.prepare_html(assets, progress)?;
let html_path = self.target_out_dir().join("index.html");
std::fs::write(html_path, html)?;
Ok(())
}
}