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
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-08-01 17:46:35 -07:00
1 parent 9dbdf74a1e
commit e5e578d27b
14 files changed
+428 -272

No files matched your search

+69 -56
View File
@@ -1,6 +1,7 @@
use super::web::install_web_build_tooling;
use super::BuildRequest;
use super::BuildResult;
use super::TargetPlatform;
use crate::assets::copy_dir_to;
use crate::assets::create_assets_head;
use crate::assets::{asset_manifest, process_assets, AssetConfigDropGuard};
@@ -12,9 +13,12 @@ use crate::builder::progress::UpdateStage;
use crate::link::LinkCommand;
use crate::Result;
use anyhow::Context;
use dioxus_cli_config::Platform;
use futures_channel::mpsc::UnboundedSender;
use manganis_cli_support::AssetManifest;
use manganis_cli_support::ManganisSupportGuard;
use std::fs::create_dir_all;
use std::path::PathBuf;
impl BuildRequest {
/// Create a list of arguments for cargo builds
@@ -41,11 +45,10 @@ impl BuildRequest {
cargo_args.push(features_str);
}
if let Some(target) = self.web.then_some("wasm32-unknown-unknown").or(self
.build_arguments
.target_args
.target
.as_deref())
if let Some(target) = self
.targeting_web()
.then_some("wasm32-unknown-unknown")
.or(self.build_arguments.target_args.target.as_deref())
{
cargo_args.push("--target".to_string());
cargo_args.push(target.to_string());
@@ -94,7 +97,7 @@ impl BuildRequest {
Ok((cmd, cargo_args))
}
pub async fn build(
pub(crate) async fn build(
&self,
mut progress: UnboundedSender<UpdateBuildProgress>,
) -> Result<BuildResult> {
@@ -115,7 +118,7 @@ impl BuildRequest {
AssetConfigDropGuard::new(self.dioxus_crate.dioxus_config.web.app.base_path.as_deref());
// If this is a web, build make sure we have the web build tooling set up
if self.web {
if self.targeting_web() {
install_web_build_tooling(&mut progress).await?;
}
@@ -133,13 +136,8 @@ impl BuildRequest {
.context("Failed to post process build")?;
tracing::info!(
"🚩 Build completed: [./{}]",
self.dioxus_crate
.dioxus_config
.application
.out_dir
.clone()
.display()
"🚩 Build completed: [{}]",
self.dioxus_crate.out_dir().display()
);
_ = progress.start_send(UpdateBuildProgress {
@@ -161,30 +159,17 @@ impl BuildRequest {
update: UpdateStage::Start,
});
// Start Manganis linker intercept.
let linker_args = vec![format!("{}", self.dioxus_crate.out_dir().display())];
// Don't block the main thread - manganis should not be running its own std process but it's
// fine to wrap it here at the top
tokio::task::spawn_blocking(move || {
manganis_cli_support::start_linker_intercept(
&LinkCommand::command_name(),
cargo_args,
Some(linker_args),
)
})
.await
.unwrap()?;
let assets = self.collect_assets(cargo_args, progress).await?;
let file_name = self.dioxus_crate.executable_name();
// Move the final output executable into the dist folder
let out_dir = self.dioxus_crate.out_dir();
let out_dir = self.target_out_dir();
if !out_dir.is_dir() {
create_dir_all(&out_dir)?;
}
let mut output_path = out_dir.join(file_name);
if self.web {
if self.targeting_web() {
output_path.set_extension("wasm");
} else if cfg!(windows) {
output_path.set_extension("exe");
@@ -195,37 +180,14 @@ impl BuildRequest {
self.copy_assets_dir()?;
let assets = if !self.build_arguments.skip_assets {
let assets = asset_manifest(&self.dioxus_crate);
let dioxus_crate = self.dioxus_crate.clone();
let mut progress = progress.clone();
tokio::task::spawn_blocking(
move || -> Result<Option<manganis_cli_support::AssetManifest>> {
// Collect assets
process_assets(&dioxus_crate, &assets, &mut progress)?;
// Create the __assets_head.html file for bundling
create_assets_head(&dioxus_crate, &assets)?;
Ok(Some(assets))
},
)
.await
.unwrap()?
} else {
None
};
// Create the build result
let build_result = BuildResult {
executable: output_path,
web: self.web,
platform: self
.build_arguments
.platform
.expect("To be resolved by now"),
target_platform: self.target_platform,
};
// If this is a web build, run web post processing steps
if self.web {
if self.targeting_web() {
self.post_process_web_build(&build_result, assets.as_ref(), progress)
.await?;
}
@@ -233,6 +195,45 @@ impl BuildRequest {
Ok(build_result)
}
async fn collect_assets(
&self,
cargo_args: Vec<String>,
progress: &mut UnboundedSender<UpdateBuildProgress>,
) -> anyhow::Result<Option<AssetManifest>> {
// If this is the server build, the client build already copied any assets we need
if self.target_platform == TargetPlatform::Server {
return Ok(None);
}
// If assets are skipped, we don't need to collect them
if self.build_arguments.skip_assets {
return Ok(None);
}
// Start Manganis linker intercept.
let linker_args = vec![format!("{}", self.target_out_dir().display())];
// Don't block the main thread - manganis should not be running its own std process but it's
// fine to wrap it here at the top
let build = self.clone();
let mut progress = progress.clone();
tokio::task::spawn_blocking(move || {
manganis_cli_support::start_linker_intercept(
&LinkCommand::command_name(),
cargo_args,
Some(linker_args),
)?;
let assets = asset_manifest(&build);
// Collect assets from the asset manifest the linker intercept created
process_assets(&build, &assets, &mut progress)?;
// Create the __assets_head.html file for bundling
create_assets_head(&build, &assets)?;
Ok(Some(assets))
})
.await
.unwrap()
}
pub fn copy_assets_dir(&self) -> anyhow::Result<()> {
tracing::info!("Copying public assets to the output directory...");
let out_dir = self.dioxus_crate.out_dir();
@@ -240,7 +241,7 @@ impl BuildRequest {
if asset_dir.is_dir() {
// Only pre-compress the assets from the web build. Desktop assets are not served, so they don't need to be pre_compressed
let pre_compress = self.web
let pre_compress = self.targeting_web()
&& self
.dioxus_crate
.should_pre_compress_web_assets(self.build_arguments.release);
@@ -249,4 +250,16 @@ impl BuildRequest {
}
Ok(())
}
/// Get the output directory for a specific built target
pub fn target_out_dir(&self) -> PathBuf {
let out_dir = self.dioxus_crate.out_dir();
match self.build_arguments.platform {
Some(Platform::Fullstack | Platform::StaticGeneration) => match self.target_platform {
TargetPlatform::Web => out_dir.join("public"),
_ => out_dir,
},
_ => out_dir,
}
}
}
+6 -10
View File
@@ -1,10 +1,11 @@
use crate::builder::Build;
use crate::dioxus_crate::DioxusCrate;
use dioxus_cli_config::Platform;
use crate::builder::BuildRequest;
use std::path::PathBuf;
use super::TargetPlatform;
static CLIENT_RUST_FLAGS: &[&str] = &["-Cdebuginfo=none", "-Cstrip=debuginfo"];
// The `opt-level=2` increases build times, but can noticeably decrease time
// between saving changes and being able to interact with an app. The "overall"
@@ -56,15 +57,10 @@ impl BuildRequest {
target_directory: PathBuf,
rust_flags: &[&str],
feature: String,
web: bool,
target_platform: TargetPlatform,
) -> Self {
let config = config.clone();
let mut build = build.clone();
build.platform = Some(if web {
Platform::Web
} else {
Platform::Desktop
});
// Set the target directory we are building the server in
let target_dir = get_target_directory(&build, target_directory);
// Add the server feature to the features we pass to the build
@@ -74,12 +70,12 @@ impl BuildRequest {
let rust_flags = fullstack_rust_flags(&build, rust_flags);
Self {
web,
serve,
build_arguments: build.clone(),
dioxus_crate: config,
rust_flags,
target_dir,
target_platform,
}
}
@@ -91,7 +87,7 @@ impl BuildRequest {
config.server_target_dir(),
SERVER_RUST_FLAGS,
build.target_args.server_feature.clone(),
false,
TargetPlatform::Server,
)
}
@@ -103,7 +99,7 @@ impl BuildRequest {
config.client_target_dir(),
CLIENT_RUST_FLAGS,
build.target_args.client_feature.clone(),
true,
TargetPlatform::Web,
)
}
}
+65 -32
View File
@@ -18,14 +18,37 @@ pub use progress::{
BuildMessage, MessageSource, MessageType, Stage, UpdateBuildProgress, UpdateStage,
};
/// The target platform for the build
/// This is very similar to the Platform enum, but we need to be able to differentiate between the
/// server and web targets for the fullstack platform
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TargetPlatform {
Web,
Desktop,
Server,
Liveview,
}
impl std::fmt::Display for TargetPlatform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TargetPlatform::Web => write!(f, "web"),
TargetPlatform::Desktop => write!(f, "desktop"),
TargetPlatform::Server => write!(f, "server"),
TargetPlatform::Liveview => write!(f, "liveview"),
}
}
}
/// A request for a project to be built
#[derive(Clone)]
pub struct BuildRequest {
/// Whether the build is for serving the application
pub serve: bool,
/// Whether this is a web build
pub web: bool,
/// The configuration for the crate we are building
pub dioxus_crate: DioxusCrate,
/// The target platform for the build
pub target_platform: TargetPlatform,
/// The arguments for the build
pub build_arguments: Build,
/// The rustc flags to pass to the build
@@ -41,28 +64,32 @@ impl BuildRequest {
build_arguments: impl Into<Build>,
) -> Vec<Self> {
let build_arguments = build_arguments.into();
let dioxus_crate = dioxus_crate.clone();
let platform = build_arguments.platform();
let single_platform = |platform| {
let dioxus_crate = dioxus_crate.clone();
vec![Self {
serve,
dioxus_crate,
build_arguments: build_arguments.clone(),
target_platform: platform,
rust_flags: Default::default(),
target_dir: Default::default(),
}]
};
match platform {
Platform::Web | Platform::Desktop => {
let web = platform == Platform::Web;
vec![Self {
serve,
web,
dioxus_crate,
build_arguments,
rust_flags: Default::default(),
target_dir: Default::default(),
}]
}
Platform::Web => single_platform(TargetPlatform::Web),
Platform::Liveview => single_platform(TargetPlatform::Liveview),
Platform::Desktop => single_platform(TargetPlatform::Desktop),
Platform::StaticGeneration | Platform::Fullstack => {
Self::new_fullstack(dioxus_crate, build_arguments, serve)
Self::new_fullstack(dioxus_crate.clone(), build_arguments, serve)
}
_ => unimplemented!("Unknown platform: {platform:?}"),
}
}
pub async fn build_all_parallel(build_requests: Vec<BuildRequest>) -> Result<Vec<BuildResult>> {
pub(crate) async fn build_all_parallel(
build_requests: Vec<BuildRequest>,
) -> Result<Vec<BuildResult>> {
let multi_platform_build = build_requests.len() > 1;
let mut build_progress = Vec::new();
let mut set = tokio::task::JoinSet::new();
@@ -104,13 +131,17 @@ impl BuildRequest {
Ok(all_results)
}
/// Check if the build is targeting the web platform
pub fn targeting_web(&self) -> bool {
self.target_platform == TargetPlatform::Web
}
}
#[derive(Debug, Clone)]
pub(crate) struct BuildResult {
pub executable: PathBuf,
pub web: bool,
pub platform: Platform,
pub target_platform: TargetPlatform,
}
impl BuildResult {
@@ -121,24 +152,26 @@ impl BuildResult {
fullstack_address: Option<SocketAddr>,
workspace: &std::path::Path,
) -> std::io::Result<Option<Child>> {
if self.web {
if self.target_platform == TargetPlatform::Web {
return Ok(None);
}
if self.target_platform == TargetPlatform::Server {
tracing::trace!("Proxying fullstack server from port {fullstack_address:?}");
}
let arguments = RuntimeCLIArguments::new(serve.address.address(), fullstack_address);
let executable = self.executable.canonicalize()?;
Ok(Some(
Command::new(executable)
// When building the fullstack server, we need to forward the serve arguments (like port) to the fullstack server through env vars
.env(
dioxus_cli_config::__private::SERVE_ENV,
serde_json::to_string(&arguments).unwrap(),
)
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.current_dir(workspace)
.spawn()?,
))
let mut cmd = Command::new(executable);
cmd
// When building the fullstack server, we need to forward the serve arguments (like port) to the fullstack server through env vars
.env(
dioxus_cli_config::__private::SERVE_ENV,
serde_json::to_string(&arguments).unwrap(),
)
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.current_dir(workspace);
Ok(Some(cmd.spawn()?))
}
}
+2 -2
View File
@@ -124,7 +124,7 @@ impl BuildRequest {
let input_path = output_location.with_extension("wasm");
// Create the directory where the bindgen output will be placed
let bindgen_outdir = self.dioxus_crate.out_dir().join("assets").join("dioxus");
let bindgen_outdir = self.target_out_dir().join("assets").join("dioxus");
// Run wasm-bindgen
self.run_wasm_bindgen(&input_path, &bindgen_outdir).await?;
@@ -183,7 +183,7 @@ impl BuildRequest {
// 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.dioxus_crate.out_dir().join("index.html");
let html_path = self.target_out_dir().join("index.html");
std::fs::write(html_path, html)?;
Ok(())