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:
1 parent
9dbdf74a1e
commit
e5e578d27b
14 files changed
+428
-272
No files matched your search
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()?))
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
Reference in new issue
Block a user