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
@@ -30,6 +30,11 @@ pub enum Platform {
|
|||||||
#[cfg_attr(feature = "cli", clap(name = "static-generation"))]
|
#[cfg_attr(feature = "cli", clap(name = "static-generation"))]
|
||||||
#[serde(rename = "static-generation")]
|
#[serde(rename = "static-generation")]
|
||||||
StaticGeneration,
|
StaticGeneration,
|
||||||
|
|
||||||
|
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
|
||||||
|
#[cfg_attr(feature = "cli", clap(name = "liveview"))]
|
||||||
|
#[serde(rename = "liveview")]
|
||||||
|
Liveview,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurs when a platform is not recognized
|
/// An error that occurs when a platform is not recognized
|
||||||
@@ -50,6 +55,7 @@ impl FromStr for Platform {
|
|||||||
"desktop" => Ok(Self::Desktop),
|
"desktop" => Ok(Self::Desktop),
|
||||||
"fullstack" => Ok(Self::Fullstack),
|
"fullstack" => Ok(Self::Fullstack),
|
||||||
"static-generation" => Ok(Self::StaticGeneration),
|
"static-generation" => Ok(Self::StaticGeneration),
|
||||||
|
"liveview" => Ok(Self::Liveview),
|
||||||
_ => Err(UnknownPlatformError),
|
_ => Err(UnknownPlatformError),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,6 +84,7 @@ impl Platform {
|
|||||||
Platform::Desktop => "desktop",
|
Platform::Desktop => "desktop",
|
||||||
Platform::Fullstack => "fullstack",
|
Platform::Fullstack => "fullstack",
|
||||||
Platform::StaticGeneration => "static-generation",
|
Platform::StaticGeneration => "static-generation",
|
||||||
|
Platform::Liveview => "liveview",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-39
@@ -1,14 +1,16 @@
|
|||||||
use crate::builder::{
|
use crate::builder::{
|
||||||
BuildMessage, MessageSource, MessageType, Stage, UpdateBuildProgress, UpdateStage,
|
BuildMessage, BuildRequest, MessageSource, MessageType, Stage, UpdateBuildProgress, UpdateStage,
|
||||||
};
|
};
|
||||||
use crate::dioxus_crate::DioxusCrate;
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use brotli::enc::BrotliEncoderParams;
|
use brotli::enc::BrotliEncoderParams;
|
||||||
use futures_channel::mpsc::UnboundedSender;
|
use futures_channel::mpsc::UnboundedSender;
|
||||||
use manganis_cli_support::{process_file, AssetManifest, AssetManifestExt, AssetType};
|
use manganis_cli_support::{process_file, AssetManifest, AssetManifestExt, AssetType};
|
||||||
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::atomic::AtomicUsize;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{ffi::OsString, path::PathBuf};
|
use std::{ffi::OsString, path::PathBuf};
|
||||||
use std::{fs::File, io::Write};
|
use std::{fs::File, io::Write};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
@@ -17,8 +19,8 @@ use walkdir::WalkDir;
|
|||||||
/// The temp file name for passing manganis json from linker to current exec.
|
/// The temp file name for passing manganis json from linker to current exec.
|
||||||
pub const MG_JSON_OUT: &str = "mg-out";
|
pub const MG_JSON_OUT: &str = "mg-out";
|
||||||
|
|
||||||
pub fn asset_manifest(config: &DioxusCrate) -> AssetManifest {
|
pub fn asset_manifest(build: &BuildRequest) -> AssetManifest {
|
||||||
let file_path = config.out_dir().join(MG_JSON_OUT);
|
let file_path = build.target_out_dir().join(MG_JSON_OUT);
|
||||||
let read = fs::read_to_string(&file_path).unwrap();
|
let read = fs::read_to_string(&file_path).unwrap();
|
||||||
_ = fs::remove_file(file_path);
|
_ = fs::remove_file(file_path);
|
||||||
let json: Vec<String> = serde_json::from_str(&read).unwrap();
|
let json: Vec<String> = serde_json::from_str(&read).unwrap();
|
||||||
@@ -27,58 +29,64 @@ pub fn asset_manifest(config: &DioxusCrate) -> AssetManifest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a head file that contains all of the imports for assets that the user project uses
|
/// Create a head file that contains all of the imports for assets that the user project uses
|
||||||
pub fn create_assets_head(config: &DioxusCrate, manifest: &AssetManifest) -> Result<()> {
|
pub fn create_assets_head(build: &BuildRequest, manifest: &AssetManifest) -> Result<()> {
|
||||||
let mut file = File::create(config.out_dir().join("__assets_head.html"))?;
|
let out_dir = build.target_out_dir();
|
||||||
|
std::fs::create_dir_all(&out_dir)?;
|
||||||
|
let mut file = File::create(out_dir.join("__assets_head.html"))?;
|
||||||
file.write_all(manifest.head().as_bytes())?;
|
file.write_all(manifest.head().as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process any assets collected from the binary
|
/// Process any assets collected from the binary
|
||||||
pub(crate) fn process_assets(
|
pub(crate) fn process_assets(
|
||||||
config: &DioxusCrate,
|
build: &BuildRequest,
|
||||||
manifest: &AssetManifest,
|
manifest: &AssetManifest,
|
||||||
progress: &mut UnboundedSender<UpdateBuildProgress>,
|
progress: &mut UnboundedSender<UpdateBuildProgress>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let static_asset_output_dir = config.out_dir();
|
let static_asset_output_dir = build.target_out_dir();
|
||||||
|
|
||||||
std::fs::create_dir_all(&static_asset_output_dir)
|
std::fs::create_dir_all(&static_asset_output_dir)
|
||||||
.context("Failed to create static asset output directory")?;
|
.context("Failed to create static asset output directory")?;
|
||||||
|
|
||||||
let mut assets_finished: usize = 0;
|
let assets_finished = Arc::new(AtomicUsize::new(0));
|
||||||
let assets = manifest.assets();
|
let assets = manifest.assets();
|
||||||
let asset_count = assets.len();
|
let asset_count = assets.len();
|
||||||
assets.iter().try_for_each(move |asset| {
|
assets.par_iter().try_for_each_init(
|
||||||
if let AssetType::File(file_asset) = asset {
|
|| progress.clone(),
|
||||||
match process_file(file_asset, &static_asset_output_dir) {
|
move |progress, asset| {
|
||||||
Ok(_) => {
|
if let AssetType::File(file_asset) = asset {
|
||||||
// Update the progress
|
match process_file(file_asset, &static_asset_output_dir) {
|
||||||
_ = progress.start_send(UpdateBuildProgress {
|
Ok(_) => {
|
||||||
stage: Stage::OptimizingAssets,
|
// Update the progress
|
||||||
update: UpdateStage::AddMessage(BuildMessage {
|
_ = progress.start_send(UpdateBuildProgress {
|
||||||
level: Level::INFO,
|
stage: Stage::OptimizingAssets,
|
||||||
message: MessageType::Text(format!(
|
update: UpdateStage::AddMessage(BuildMessage {
|
||||||
"Optimized static asset {}",
|
level: Level::INFO,
|
||||||
file_asset
|
message: MessageType::Text(format!(
|
||||||
)),
|
"Optimized static asset {}",
|
||||||
source: MessageSource::Build,
|
file_asset
|
||||||
}),
|
)),
|
||||||
});
|
source: MessageSource::Build,
|
||||||
assets_finished += 1;
|
}),
|
||||||
_ = progress.start_send(UpdateBuildProgress {
|
});
|
||||||
stage: Stage::OptimizingAssets,
|
let assets_finished =
|
||||||
update: UpdateStage::SetProgress(
|
assets_finished.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
assets_finished as f64 / asset_count as f64,
|
_ = progress.start_send(UpdateBuildProgress {
|
||||||
),
|
stage: Stage::OptimizingAssets,
|
||||||
});
|
update: UpdateStage::SetProgress(
|
||||||
}
|
assets_finished as f64 / asset_count as f64,
|
||||||
Err(err) => {
|
),
|
||||||
tracing::error!("Failed to copy static asset: {}", err);
|
});
|
||||||
return Err(err);
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Failed to copy static asset: {}", err);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok::<(), anyhow::Error>(())
|
||||||
Ok::<(), anyhow::Error>(())
|
},
|
||||||
})?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::web::install_web_build_tooling;
|
use super::web::install_web_build_tooling;
|
||||||
use super::BuildRequest;
|
use super::BuildRequest;
|
||||||
use super::BuildResult;
|
use super::BuildResult;
|
||||||
|
use super::TargetPlatform;
|
||||||
use crate::assets::copy_dir_to;
|
use crate::assets::copy_dir_to;
|
||||||
use crate::assets::create_assets_head;
|
use crate::assets::create_assets_head;
|
||||||
use crate::assets::{asset_manifest, process_assets, AssetConfigDropGuard};
|
use crate::assets::{asset_manifest, process_assets, AssetConfigDropGuard};
|
||||||
@@ -12,9 +13,12 @@ use crate::builder::progress::UpdateStage;
|
|||||||
use crate::link::LinkCommand;
|
use crate::link::LinkCommand;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use dioxus_cli_config::Platform;
|
||||||
use futures_channel::mpsc::UnboundedSender;
|
use futures_channel::mpsc::UnboundedSender;
|
||||||
|
use manganis_cli_support::AssetManifest;
|
||||||
use manganis_cli_support::ManganisSupportGuard;
|
use manganis_cli_support::ManganisSupportGuard;
|
||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
impl BuildRequest {
|
impl BuildRequest {
|
||||||
/// Create a list of arguments for cargo builds
|
/// Create a list of arguments for cargo builds
|
||||||
@@ -41,11 +45,10 @@ impl BuildRequest {
|
|||||||
cargo_args.push(features_str);
|
cargo_args.push(features_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(target) = self.web.then_some("wasm32-unknown-unknown").or(self
|
if let Some(target) = self
|
||||||
.build_arguments
|
.targeting_web()
|
||||||
.target_args
|
.then_some("wasm32-unknown-unknown")
|
||||||
.target
|
.or(self.build_arguments.target_args.target.as_deref())
|
||||||
.as_deref())
|
|
||||||
{
|
{
|
||||||
cargo_args.push("--target".to_string());
|
cargo_args.push("--target".to_string());
|
||||||
cargo_args.push(target.to_string());
|
cargo_args.push(target.to_string());
|
||||||
@@ -94,7 +97,7 @@ impl BuildRequest {
|
|||||||
Ok((cmd, cargo_args))
|
Ok((cmd, cargo_args))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build(
|
pub(crate) async fn build(
|
||||||
&self,
|
&self,
|
||||||
mut progress: UnboundedSender<UpdateBuildProgress>,
|
mut progress: UnboundedSender<UpdateBuildProgress>,
|
||||||
) -> Result<BuildResult> {
|
) -> Result<BuildResult> {
|
||||||
@@ -115,7 +118,7 @@ impl BuildRequest {
|
|||||||
AssetConfigDropGuard::new(self.dioxus_crate.dioxus_config.web.app.base_path.as_deref());
|
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 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?;
|
install_web_build_tooling(&mut progress).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,13 +136,8 @@ impl BuildRequest {
|
|||||||
.context("Failed to post process build")?;
|
.context("Failed to post process build")?;
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"🚩 Build completed: [./{}]",
|
"🚩 Build completed: [{}]",
|
||||||
self.dioxus_crate
|
self.dioxus_crate.out_dir().display()
|
||||||
.dioxus_config
|
|
||||||
.application
|
|
||||||
.out_dir
|
|
||||||
.clone()
|
|
||||||
.display()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
_ = progress.start_send(UpdateBuildProgress {
|
_ = progress.start_send(UpdateBuildProgress {
|
||||||
@@ -161,30 +159,17 @@ impl BuildRequest {
|
|||||||
update: UpdateStage::Start,
|
update: UpdateStage::Start,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start Manganis linker intercept.
|
let assets = self.collect_assets(cargo_args, progress).await?;
|
||||||
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 file_name = self.dioxus_crate.executable_name();
|
let file_name = self.dioxus_crate.executable_name();
|
||||||
|
|
||||||
// Move the final output executable into the dist folder
|
// 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() {
|
if !out_dir.is_dir() {
|
||||||
create_dir_all(&out_dir)?;
|
create_dir_all(&out_dir)?;
|
||||||
}
|
}
|
||||||
let mut output_path = out_dir.join(file_name);
|
let mut output_path = out_dir.join(file_name);
|
||||||
if self.web {
|
if self.targeting_web() {
|
||||||
output_path.set_extension("wasm");
|
output_path.set_extension("wasm");
|
||||||
} else if cfg!(windows) {
|
} else if cfg!(windows) {
|
||||||
output_path.set_extension("exe");
|
output_path.set_extension("exe");
|
||||||
@@ -195,37 +180,14 @@ impl BuildRequest {
|
|||||||
|
|
||||||
self.copy_assets_dir()?;
|
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
|
// Create the build result
|
||||||
let build_result = BuildResult {
|
let build_result = BuildResult {
|
||||||
executable: output_path,
|
executable: output_path,
|
||||||
web: self.web,
|
target_platform: self.target_platform,
|
||||||
platform: self
|
|
||||||
.build_arguments
|
|
||||||
.platform
|
|
||||||
.expect("To be resolved by now"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// If this is a web build, run web post processing steps
|
// 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)
|
self.post_process_web_build(&build_result, assets.as_ref(), progress)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -233,6 +195,45 @@ impl BuildRequest {
|
|||||||
Ok(build_result)
|
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<()> {
|
pub fn copy_assets_dir(&self) -> anyhow::Result<()> {
|
||||||
tracing::info!("Copying public assets to the output directory...");
|
tracing::info!("Copying public assets to the output directory...");
|
||||||
let out_dir = self.dioxus_crate.out_dir();
|
let out_dir = self.dioxus_crate.out_dir();
|
||||||
@@ -240,7 +241,7 @@ impl BuildRequest {
|
|||||||
|
|
||||||
if asset_dir.is_dir() {
|
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
|
// 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
|
&& self
|
||||||
.dioxus_crate
|
.dioxus_crate
|
||||||
.should_pre_compress_web_assets(self.build_arguments.release);
|
.should_pre_compress_web_assets(self.build_arguments.release);
|
||||||
@@ -249,4 +250,16 @@ impl BuildRequest {
|
|||||||
}
|
}
|
||||||
Ok(())
|
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::builder::Build;
|
||||||
use crate::dioxus_crate::DioxusCrate;
|
use crate::dioxus_crate::DioxusCrate;
|
||||||
use dioxus_cli_config::Platform;
|
|
||||||
|
|
||||||
use crate::builder::BuildRequest;
|
use crate::builder::BuildRequest;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use super::TargetPlatform;
|
||||||
|
|
||||||
static CLIENT_RUST_FLAGS: &[&str] = &["-Cdebuginfo=none", "-Cstrip=debuginfo"];
|
static CLIENT_RUST_FLAGS: &[&str] = &["-Cdebuginfo=none", "-Cstrip=debuginfo"];
|
||||||
// The `opt-level=2` increases build times, but can noticeably decrease time
|
// 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"
|
// between saving changes and being able to interact with an app. The "overall"
|
||||||
@@ -56,15 +57,10 @@ impl BuildRequest {
|
|||||||
target_directory: PathBuf,
|
target_directory: PathBuf,
|
||||||
rust_flags: &[&str],
|
rust_flags: &[&str],
|
||||||
feature: String,
|
feature: String,
|
||||||
web: bool,
|
target_platform: TargetPlatform,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
let mut build = build.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
|
// Set the target directory we are building the server in
|
||||||
let target_dir = get_target_directory(&build, target_directory);
|
let target_dir = get_target_directory(&build, target_directory);
|
||||||
// Add the server feature to the features we pass to the build
|
// 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);
|
let rust_flags = fullstack_rust_flags(&build, rust_flags);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
web,
|
|
||||||
serve,
|
serve,
|
||||||
build_arguments: build.clone(),
|
build_arguments: build.clone(),
|
||||||
dioxus_crate: config,
|
dioxus_crate: config,
|
||||||
rust_flags,
|
rust_flags,
|
||||||
target_dir,
|
target_dir,
|
||||||
|
target_platform,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +87,7 @@ impl BuildRequest {
|
|||||||
config.server_target_dir(),
|
config.server_target_dir(),
|
||||||
SERVER_RUST_FLAGS,
|
SERVER_RUST_FLAGS,
|
||||||
build.target_args.server_feature.clone(),
|
build.target_args.server_feature.clone(),
|
||||||
false,
|
TargetPlatform::Server,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +99,7 @@ impl BuildRequest {
|
|||||||
config.client_target_dir(),
|
config.client_target_dir(),
|
||||||
CLIENT_RUST_FLAGS,
|
CLIENT_RUST_FLAGS,
|
||||||
build.target_args.client_feature.clone(),
|
build.target_args.client_feature.clone(),
|
||||||
true,
|
TargetPlatform::Web,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,14 +18,37 @@ pub use progress::{
|
|||||||
BuildMessage, MessageSource, MessageType, Stage, UpdateBuildProgress, UpdateStage,
|
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
|
/// A request for a project to be built
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BuildRequest {
|
pub struct BuildRequest {
|
||||||
/// Whether the build is for serving the application
|
/// Whether the build is for serving the application
|
||||||
pub serve: bool,
|
pub serve: bool,
|
||||||
/// Whether this is a web build
|
|
||||||
pub web: bool,
|
|
||||||
/// The configuration for the crate we are building
|
/// The configuration for the crate we are building
|
||||||
pub dioxus_crate: DioxusCrate,
|
pub dioxus_crate: DioxusCrate,
|
||||||
|
/// The target platform for the build
|
||||||
|
pub target_platform: TargetPlatform,
|
||||||
/// The arguments for the build
|
/// The arguments for the build
|
||||||
pub build_arguments: Build,
|
pub build_arguments: Build,
|
||||||
/// The rustc flags to pass to the build
|
/// The rustc flags to pass to the build
|
||||||
@@ -41,28 +64,32 @@ impl BuildRequest {
|
|||||||
build_arguments: impl Into<Build>,
|
build_arguments: impl Into<Build>,
|
||||||
) -> Vec<Self> {
|
) -> Vec<Self> {
|
||||||
let build_arguments = build_arguments.into();
|
let build_arguments = build_arguments.into();
|
||||||
let dioxus_crate = dioxus_crate.clone();
|
|
||||||
let platform = build_arguments.platform();
|
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 {
|
match platform {
|
||||||
Platform::Web | Platform::Desktop => {
|
Platform::Web => single_platform(TargetPlatform::Web),
|
||||||
let web = platform == Platform::Web;
|
Platform::Liveview => single_platform(TargetPlatform::Liveview),
|
||||||
vec![Self {
|
Platform::Desktop => single_platform(TargetPlatform::Desktop),
|
||||||
serve,
|
|
||||||
web,
|
|
||||||
dioxus_crate,
|
|
||||||
build_arguments,
|
|
||||||
rust_flags: Default::default(),
|
|
||||||
target_dir: Default::default(),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
Platform::StaticGeneration | Platform::Fullstack => {
|
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:?}"),
|
_ => 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 multi_platform_build = build_requests.len() > 1;
|
||||||
let mut build_progress = Vec::new();
|
let mut build_progress = Vec::new();
|
||||||
let mut set = tokio::task::JoinSet::new();
|
let mut set = tokio::task::JoinSet::new();
|
||||||
@@ -104,13 +131,17 @@ impl BuildRequest {
|
|||||||
|
|
||||||
Ok(all_results)
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct BuildResult {
|
pub(crate) struct BuildResult {
|
||||||
pub executable: PathBuf,
|
pub executable: PathBuf,
|
||||||
pub web: bool,
|
pub target_platform: TargetPlatform,
|
||||||
pub platform: Platform,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildResult {
|
impl BuildResult {
|
||||||
@@ -121,24 +152,26 @@ impl BuildResult {
|
|||||||
fullstack_address: Option<SocketAddr>,
|
fullstack_address: Option<SocketAddr>,
|
||||||
workspace: &std::path::Path,
|
workspace: &std::path::Path,
|
||||||
) -> std::io::Result<Option<Child>> {
|
) -> std::io::Result<Option<Child>> {
|
||||||
if self.web {
|
if self.target_platform == TargetPlatform::Web {
|
||||||
return Ok(None);
|
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 arguments = RuntimeCLIArguments::new(serve.address.address(), fullstack_address);
|
||||||
let executable = self.executable.canonicalize()?;
|
let executable = self.executable.canonicalize()?;
|
||||||
Ok(Some(
|
let mut cmd = Command::new(executable);
|
||||||
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
|
// When building the fullstack server, we need to forward the serve arguments (like port) to the fullstack server through env vars
|
||||||
.env(
|
.env(
|
||||||
dioxus_cli_config::__private::SERVE_ENV,
|
dioxus_cli_config::__private::SERVE_ENV,
|
||||||
serde_json::to_string(&arguments).unwrap(),
|
serde_json::to_string(&arguments).unwrap(),
|
||||||
)
|
)
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.kill_on_drop(true)
|
.kill_on_drop(true)
|
||||||
.current_dir(workspace)
|
.current_dir(workspace);
|
||||||
.spawn()?,
|
Ok(Some(cmd.spawn()?))
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ impl BuildRequest {
|
|||||||
let input_path = output_location.with_extension("wasm");
|
let input_path = output_location.with_extension("wasm");
|
||||||
|
|
||||||
// Create the directory where the bindgen output will be placed
|
// 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
|
// Run wasm-bindgen
|
||||||
self.run_wasm_bindgen(&input_path, &bindgen_outdir).await?;
|
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
|
// 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.
|
// to test failures and broken pages.
|
||||||
let html = self.prepare_html(assets, progress)?;
|
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)?;
|
std::fs::write(html_path, html)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
use crate::builder::BuildRequest;
|
use crate::builder::BuildRequest;
|
||||||
use crate::builder::BuildResult;
|
use crate::builder::BuildResult;
|
||||||
|
use crate::builder::TargetPlatform;
|
||||||
use crate::builder::UpdateBuildProgress;
|
use crate::builder::UpdateBuildProgress;
|
||||||
use crate::dioxus_crate::DioxusCrate;
|
use crate::dioxus_crate::DioxusCrate;
|
||||||
|
use crate::serve::next_or_pending;
|
||||||
use crate::serve::Serve;
|
use crate::serve::Serve;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use dioxus_cli_config::Platform;
|
|
||||||
use futures_channel::mpsc::UnboundedReceiver;
|
use futures_channel::mpsc::UnboundedReceiver;
|
||||||
|
use futures_util::future::OptionFuture;
|
||||||
use futures_util::stream::select_all;
|
use futures_util::stream::select_all;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use futures_util::{future::OptionFuture, stream::FuturesUnordered};
|
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
process::{Child, Command},
|
process::{Child, Command},
|
||||||
@@ -21,7 +22,7 @@ pub struct Builder {
|
|||||||
build_results: Option<JoinHandle<Result<Vec<BuildResult>>>>,
|
build_results: Option<JoinHandle<Result<Vec<BuildResult>>>>,
|
||||||
|
|
||||||
/// The progress of the builds
|
/// The progress of the builds
|
||||||
build_progress: Vec<(Platform, UnboundedReceiver<UpdateBuildProgress>)>,
|
build_progress: Vec<(TargetPlatform, UnboundedReceiver<UpdateBuildProgress>)>,
|
||||||
|
|
||||||
/// The application we are building
|
/// The application we are building
|
||||||
config: DioxusCrate,
|
config: DioxusCrate,
|
||||||
@@ -30,7 +31,7 @@ pub struct Builder {
|
|||||||
serve: Serve,
|
serve: Serve,
|
||||||
|
|
||||||
/// The children of the build process
|
/// The children of the build process
|
||||||
pub children: Vec<(Platform, Child)>,
|
pub children: Vec<(TargetPlatform, Child)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
@@ -58,7 +59,7 @@ impl Builder {
|
|||||||
for build_request in build_requests {
|
for build_request in build_requests {
|
||||||
let (mut tx, rx) = futures_channel::mpsc::unbounded();
|
let (mut tx, rx) = futures_channel::mpsc::unbounded();
|
||||||
self.build_progress
|
self.build_progress
|
||||||
.push((build_request.build_arguments.platform(), rx));
|
.push((build_request.target_platform, rx));
|
||||||
set.spawn(async move {
|
set.spawn(async move {
|
||||||
let res = build_request.build(tx.clone()).await;
|
let res = build_request.build(tx.clone()).await;
|
||||||
|
|
||||||
@@ -94,24 +95,28 @@ impl Builder {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|(platform, rx)| rx.map(move |update| (*platform, update))),
|
.map(|(platform, rx)| rx.map(move |update| (*platform, update))),
|
||||||
);
|
);
|
||||||
|
let next = next_or_pending(next.next());
|
||||||
|
|
||||||
// The ongoing builds directly
|
// The ongoing builds directly
|
||||||
let results: OptionFuture<_> = self.build_results.as_mut().into();
|
let results: OptionFuture<_> = self.build_results.as_mut().into();
|
||||||
|
let results = next_or_pending(results);
|
||||||
|
|
||||||
// The process exits
|
// The process exits
|
||||||
let mut process_exited = self
|
let children_empty = self.children.is_empty();
|
||||||
|
let process_exited = self
|
||||||
.children
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|(_, child)| async move {
|
.map(|(target, child)| Box::pin(async move { (*target, child.wait().await) }));
|
||||||
let status = child.wait().await.ok();
|
let process_exited = async move {
|
||||||
|
if children_empty {
|
||||||
BuilderUpdate::ProcessExited { status }
|
return futures_util::future::pending().await;
|
||||||
})
|
}
|
||||||
.collect::<FuturesUnordered<_>>();
|
futures_util::future::select_all(process_exited).await
|
||||||
|
};
|
||||||
|
|
||||||
// Wait for the next build result
|
// Wait for the next build result
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Some(build_results) = results => {
|
build_results = results => {
|
||||||
self.build_results = None;
|
self.build_results = None;
|
||||||
|
|
||||||
// If we have a build result, bubble it up to the main loop
|
// If we have a build result, bubble it up to the main loop
|
||||||
@@ -119,17 +124,13 @@ impl Builder {
|
|||||||
|
|
||||||
Ok(BuilderUpdate::Ready { results: build_results })
|
Ok(BuilderUpdate::Ready { results: build_results })
|
||||||
}
|
}
|
||||||
Some((platform, update)) = next.next() => {
|
(platform, update) = next => {
|
||||||
// If we have a build progress, send it to the screen
|
// If we have a build progress, send it to the screen
|
||||||
Ok(BuilderUpdate::Progress { platform, update })
|
Ok(BuilderUpdate::Progress { platform, update })
|
||||||
}
|
}
|
||||||
Some(exit_status) = process_exited.next() => {
|
((target, exit_status), _, _) = process_exited => {
|
||||||
Ok(exit_status)
|
Ok(BuilderUpdate::ProcessExited { status: exit_status, target_platform: target })
|
||||||
}
|
}
|
||||||
else => {
|
|
||||||
std::future::pending::<()>().await;
|
|
||||||
unreachable!("Pending cannot resolve")
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +174,14 @@ impl Builder {
|
|||||||
|
|
||||||
pub enum BuilderUpdate {
|
pub enum BuilderUpdate {
|
||||||
Progress {
|
Progress {
|
||||||
platform: Platform,
|
platform: TargetPlatform,
|
||||||
update: UpdateBuildProgress,
|
update: UpdateBuildProgress,
|
||||||
},
|
},
|
||||||
Ready {
|
Ready {
|
||||||
results: Vec<BuildResult>,
|
results: Vec<BuildResult>,
|
||||||
},
|
},
|
||||||
ProcessExited {
|
ProcessExited {
|
||||||
status: Option<std::process::ExitStatus>,
|
target_platform: TargetPlatform,
|
||||||
|
status: Result<std::process::ExitStatus, std::io::Error>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
use crate::builder::{Stage, UpdateBuildProgress, UpdateStage};
|
use std::future::{poll_fn, Future, IntoFuture};
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
|
use crate::builder::{Stage, TargetPlatform, UpdateBuildProgress, UpdateStage};
|
||||||
use crate::cli::serve::Serve;
|
use crate::cli::serve::Serve;
|
||||||
use crate::dioxus_crate::DioxusCrate;
|
use crate::dioxus_crate::DioxusCrate;
|
||||||
use crate::tracer::CLILogControl;
|
use crate::tracer::CLILogControl;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use dioxus_cli_config::Platform;
|
use futures_util::FutureExt;
|
||||||
use tokio::task::yield_now;
|
use tokio::task::yield_now;
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
@@ -103,7 +106,7 @@ pub async fn serve_all(
|
|||||||
// Run the server in the background
|
// Run the server in the background
|
||||||
// Waiting for updates here lets us tap into when clients are added/removed
|
// Waiting for updates here lets us tap into when clients are added/removed
|
||||||
if let Some(msg) = msg {
|
if let Some(msg) = msg {
|
||||||
screen.new_ws_message(Platform::Web, msg);
|
screen.new_ws_message(TargetPlatform::Web, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +138,11 @@ pub async fn serve_all(
|
|||||||
for build_result in results.iter() {
|
for build_result in results.iter() {
|
||||||
let child = build_result.open(&serve.server_arguments, server.fullstack_address(), &dioxus_crate.workspace_dir());
|
let child = build_result.open(&serve.server_arguments, server.fullstack_address(), &dioxus_crate.workspace_dir());
|
||||||
match child {
|
match child {
|
||||||
Ok(Some(child_proc)) => builder.children.push((build_result.platform,child_proc)),
|
Ok(Some(child_proc)) => builder.children.push((build_result.target_platform, child_proc)),
|
||||||
Err(_e) => break,
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to open build result: {e}");
|
||||||
|
break;
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,10 +156,20 @@ pub async fn serve_all(
|
|||||||
},
|
},
|
||||||
|
|
||||||
// If the process exited *cleanly*, we can exit
|
// If the process exited *cleanly*, we can exit
|
||||||
Ok(BuilderUpdate::ProcessExited { status, ..}) => {
|
Ok(BuilderUpdate::ProcessExited { status, target_platform }) => {
|
||||||
if let Some(status) = status {
|
// Then remove the child process
|
||||||
if status.success() {
|
builder.children.retain(|(platform, _)| *platform != target_platform);
|
||||||
break;
|
match status {
|
||||||
|
Ok(status) => {
|
||||||
|
if status.success() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tracing::error!("Application exited with status: {status}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Application exited with error: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,3 +203,20 @@ pub async fn serve_all(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grab the output of a future that returns an option or wait forever
|
||||||
|
pub(crate) fn next_or_pending<F, T>(f: F) -> impl Future<Output = T>
|
||||||
|
where
|
||||||
|
F: IntoFuture<Output = Option<T>>,
|
||||||
|
{
|
||||||
|
let pinned = f.into_future().fuse();
|
||||||
|
let mut pinned = Box::pin(pinned);
|
||||||
|
poll_fn(move |cx| {
|
||||||
|
let next = pinned.as_mut().poll(cx);
|
||||||
|
match next {
|
||||||
|
Poll::Ready(Some(next)) => Poll::Ready(next),
|
||||||
|
_ => Poll::Pending,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fuse()
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
builder::{BuildMessage, MessageSource, MessageType, Stage, UpdateBuildProgress},
|
builder::{
|
||||||
|
BuildMessage, MessageSource, MessageType, Stage, TargetPlatform, UpdateBuildProgress,
|
||||||
|
},
|
||||||
dioxus_crate::DioxusCrate,
|
dioxus_crate::DioxusCrate,
|
||||||
|
serve::next_or_pending,
|
||||||
tracer::CLILogControl,
|
tracer::CLILogControl,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -16,13 +19,13 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
use dioxus_cli_config::{AddressArguments, Platform};
|
use dioxus_cli_config::{AddressArguments, Platform};
|
||||||
use dioxus_hot_reload::ClientMsg;
|
use dioxus_hot_reload::ClientMsg;
|
||||||
use futures_util::{future::select_all, Future, StreamExt};
|
use futures_util::{future::select_all, Future, FutureExt, StreamExt};
|
||||||
use ratatui::{prelude::*, widgets::*, TerminalOptions, Viewport};
|
use ratatui::{prelude::*, widgets::*, TerminalOptions, Viewport};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
fmt::Display,
|
||||||
io::{self, stdout},
|
io::{self, stdout},
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::atomic::Ordering,
|
sync::atomic::Ordering,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
@@ -35,9 +38,30 @@ use tracing::Level;
|
|||||||
|
|
||||||
use super::{Builder, Server, Watcher};
|
use super::{Builder, Server, Watcher};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum LogSource {
|
||||||
|
Internal,
|
||||||
|
Target(TargetPlatform),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LogSource {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
LogSource::Internal => write!(f, "CLI"),
|
||||||
|
LogSource::Target(platform) => write!(f, "{platform}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TargetPlatform> for LogSource {
|
||||||
|
fn from(platform: TargetPlatform) -> Self {
|
||||||
|
LogSource::Target(platform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BuildProgress {
|
pub struct BuildProgress {
|
||||||
build_logs: HashMap<Platform, ActiveBuild>,
|
build_logs: HashMap<LogSource, ActiveBuild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildProgress {
|
impl BuildProgress {
|
||||||
@@ -67,7 +91,7 @@ pub struct Output {
|
|||||||
_dx_version: String,
|
_dx_version: String,
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
pub(crate) build_progress: BuildProgress,
|
pub(crate) build_progress: BuildProgress,
|
||||||
running_apps: HashMap<Platform, RunningApp>,
|
running_apps: HashMap<TargetPlatform, RunningApp>,
|
||||||
is_cli_release: bool,
|
is_cli_release: bool,
|
||||||
platform: Platform,
|
platform: Platform,
|
||||||
|
|
||||||
@@ -162,34 +186,87 @@ impl Output {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a message from stderr to the logs
|
||||||
|
fn push_stderr(&mut self, platform: TargetPlatform, stderr: String) {
|
||||||
|
self.set_tab(Tab::BuildLog);
|
||||||
|
let source = platform.into();
|
||||||
|
|
||||||
|
self.running_apps
|
||||||
|
.get_mut(&platform)
|
||||||
|
.unwrap()
|
||||||
|
.output
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.stderr_line
|
||||||
|
.push_str(&stderr);
|
||||||
|
self.build_progress
|
||||||
|
.build_logs
|
||||||
|
.get_mut(&source)
|
||||||
|
.unwrap()
|
||||||
|
.messages
|
||||||
|
.push(BuildMessage {
|
||||||
|
level: Level::ERROR,
|
||||||
|
message: MessageType::Text(stderr),
|
||||||
|
source: MessageSource::App,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a message from stdout to the logs
|
||||||
|
fn push_stdout(&mut self, platform: TargetPlatform, stdout: String) {
|
||||||
|
let source = platform.into();
|
||||||
|
|
||||||
|
self.running_apps
|
||||||
|
.get_mut(&platform)
|
||||||
|
.unwrap()
|
||||||
|
.output
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.stdout_line
|
||||||
|
.push_str(&stdout);
|
||||||
|
self.build_progress
|
||||||
|
.build_logs
|
||||||
|
.get_mut(&source)
|
||||||
|
.unwrap()
|
||||||
|
.messages
|
||||||
|
.push(BuildMessage {
|
||||||
|
level: Level::INFO,
|
||||||
|
message: MessageType::Text(stdout),
|
||||||
|
source: MessageSource::App,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Wait for either the ctrl_c handler or the next event
|
/// Wait for either the ctrl_c handler or the next event
|
||||||
///
|
///
|
||||||
/// Why is the ctrl_c handler here?
|
/// Why is the ctrl_c handler here?
|
||||||
///
|
///
|
||||||
/// Also tick animations every few ms
|
/// Also tick animations every few ms
|
||||||
pub async fn wait(&mut self) -> io::Result<bool> {
|
pub async fn wait(&mut self) -> io::Result<bool> {
|
||||||
// sorry lord
|
fn ok_and_some<F, T, E>(f: F) -> impl Future<Output = T>
|
||||||
let user_input = match self.events.as_mut() {
|
where
|
||||||
Some(events) => {
|
F: Future<Output = Result<Option<T>, E>>,
|
||||||
let pinned: Pin<Box<dyn Future<Output = Option<Result<Event, _>>>>> =
|
{
|
||||||
Box::pin(events.next());
|
next_or_pending(async move { f.await.ok().flatten() })
|
||||||
pinned
|
}
|
||||||
}
|
let user_input = async {
|
||||||
None => Box::pin(futures_util::future::pending()) as Pin<Box<dyn Future<Output = _>>>,
|
let events = self.events.as_mut()?;
|
||||||
|
events.next().await
|
||||||
};
|
};
|
||||||
|
let user_input = ok_and_some(user_input.map(|e| e.transpose()));
|
||||||
|
|
||||||
let has_running_apps = !self.running_apps.is_empty();
|
let has_running_apps = !self.running_apps.is_empty();
|
||||||
let next_stdout = self.running_apps.values_mut().map(|app| {
|
let next_stdout = self.running_apps.values_mut().map(|app| {
|
||||||
let future = async move {
|
let future = async move {
|
||||||
let (stdout, stderr) = match &mut app.output {
|
let (stdout, stderr) = match &mut app.output {
|
||||||
Some(out) => (out.stdout.next_line(), out.stderr.next_line()),
|
Some(out) => (
|
||||||
|
ok_and_some(out.stdout.next_line()),
|
||||||
|
ok_and_some(out.stderr.next_line()),
|
||||||
|
),
|
||||||
None => return futures_util::future::pending().await,
|
None => return futures_util::future::pending().await,
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Ok(Some(line)) = stdout => (app.result.platform, Some(line), None),
|
line = stdout => (app.result.target_platform, Some(line), None),
|
||||||
Ok(Some(line)) = stderr => (app.result.platform, None, Some(line)),
|
line = stderr => (app.result.target_platform, None, Some(line)),
|
||||||
else => futures_util::future::pending().await,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Box::pin(future)
|
Box::pin(future)
|
||||||
@@ -203,34 +280,22 @@ impl Output {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let animation_timeout = tokio::time::sleep(Duration::from_millis(300));
|
|
||||||
let tui_log_rx = &mut self.log_control.tui_rx;
|
let tui_log_rx = &mut self.log_control.tui_rx;
|
||||||
|
let next_tui_log = next_or_pending(tui_log_rx.next());
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
(platform, stdout, stderr) = next_stdout => {
|
(platform, stdout, stderr) = next_stdout => {
|
||||||
if let Some(stdout) = stdout {
|
if let Some(stdout) = stdout {
|
||||||
self.running_apps.get_mut(&platform).unwrap().output.as_mut().unwrap().stdout_line.push_str(&stdout);
|
self.push_stdout(platform, stdout);
|
||||||
self.push_log(platform, BuildMessage {
|
|
||||||
level: Level::INFO,
|
|
||||||
message: MessageType::Text(stdout),
|
|
||||||
source: MessageSource::App,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if let Some(stderr) = stderr {
|
if let Some(stderr) = stderr {
|
||||||
self.set_tab(Tab::BuildLog);
|
self.push_stderr(platform, stderr);
|
||||||
|
|
||||||
self.running_apps.get_mut(&platform).unwrap().output.as_mut().unwrap().stderr_line.push_str(&stderr);
|
|
||||||
self.build_progress.build_logs.get_mut(&platform).unwrap().messages.push(BuildMessage {
|
|
||||||
level: Level::ERROR,
|
|
||||||
message: MessageType::Text(stderr),
|
|
||||||
source: MessageSource::App,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handle internal CLI tracing logs.
|
// Handle internal CLI tracing logs.
|
||||||
Some(log) = tui_log_rx.next() => {
|
log = next_tui_log => {
|
||||||
self.push_log(self.platform, BuildMessage {
|
self.push_log(LogSource::Internal, BuildMessage {
|
||||||
level: Level::INFO,
|
level: Level::INFO,
|
||||||
message: MessageType::Text(log),
|
message: MessageType::Text(log),
|
||||||
source: MessageSource::Dev,
|
source: MessageSource::Dev,
|
||||||
@@ -238,13 +303,10 @@ impl Output {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event = user_input => {
|
event = user_input => {
|
||||||
if self.handle_events(event.unwrap().unwrap()).await? {
|
if self.handle_events(event).await? {
|
||||||
return Ok(true)
|
return Ok(true)
|
||||||
}
|
}
|
||||||
// self.handle_input(event.unwrap().unwrap())?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = animation_timeout => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@@ -330,16 +392,13 @@ impl Output {
|
|||||||
}
|
}
|
||||||
Event::Key(key) if key.code == KeyCode::Char('c') => {
|
Event::Key(key) if key.code == KeyCode::Char('c') => {
|
||||||
// Clear the currently selected build logs.
|
// Clear the currently selected build logs.
|
||||||
let build = self
|
for build in self.build_progress.build_logs.values_mut() {
|
||||||
.build_progress
|
let msgs = match self.tab {
|
||||||
.build_logs
|
Tab::Console => &mut build.stdout_logs,
|
||||||
.get_mut(&self.platform)
|
Tab::BuildLog => &mut build.messages,
|
||||||
.unwrap();
|
};
|
||||||
let msgs = match self.tab {
|
msgs.clear();
|
||||||
Tab::Console => &mut build.stdout_logs,
|
}
|
||||||
Tab::BuildLog => &mut build.messages,
|
|
||||||
};
|
|
||||||
msgs.clear();
|
|
||||||
}
|
}
|
||||||
Event::Key(key) if key.code == KeyCode::Char('1') => self.set_tab(Tab::Console),
|
Event::Key(key) if key.code == KeyCode::Char('1') => self.set_tab(Tab::Console),
|
||||||
Event::Key(key) if key.code == KeyCode::Char('2') => self.set_tab(Tab::BuildLog),
|
Event::Key(key) if key.code == KeyCode::Char('2') => self.set_tab(Tab::BuildLog),
|
||||||
@@ -362,7 +421,11 @@ impl Output {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ws_message(&mut self, platform: Platform, message: axum::extract::ws::Message) {
|
pub fn new_ws_message(
|
||||||
|
&mut self,
|
||||||
|
platform: TargetPlatform,
|
||||||
|
message: axum::extract::ws::Message,
|
||||||
|
) {
|
||||||
if let axum::extract::ws::Message::Text(text) = message {
|
if let axum::extract::ws::Message::Text(text) = message {
|
||||||
let msg = serde_json::from_str::<ClientMsg>(text.as_str());
|
let msg = serde_json::from_str::<ClientMsg>(text.as_str());
|
||||||
match msg {
|
match msg {
|
||||||
@@ -402,7 +465,7 @@ impl Output {
|
|||||||
|
|
||||||
// todo: re-enable
|
// todo: re-enable
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn is_snapped(&self, _platform: Platform) -> bool {
|
fn is_snapped(&self, _platform: LogSource) -> bool {
|
||||||
true
|
true
|
||||||
// let prev_scrol = self
|
// let prev_scrol = self
|
||||||
// .num_lines_with_wrapping
|
// .num_lines_with_wrapping
|
||||||
@@ -414,12 +477,13 @@ impl Output {
|
|||||||
self.scroll = (self.num_lines_with_wrapping).saturating_sub(self.term_height);
|
self.scroll = (self.num_lines_with_wrapping).saturating_sub(self.term_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_log(&mut self, platform: Platform, message: BuildMessage) {
|
pub fn push_log(&mut self, platform: impl Into<LogSource>, message: BuildMessage) {
|
||||||
let snapped = self.is_snapped(platform);
|
let source = platform.into();
|
||||||
|
let snapped = self.is_snapped(source);
|
||||||
|
|
||||||
self.build_progress
|
self.build_progress
|
||||||
.build_logs
|
.build_logs
|
||||||
.entry(platform)
|
.entry(source)
|
||||||
.or_default()
|
.or_default()
|
||||||
.stdout_logs
|
.stdout_logs
|
||||||
.push(message);
|
.push(message);
|
||||||
@@ -429,8 +493,9 @@ impl Output {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_build_logs(&mut self, platform: Platform, update: UpdateBuildProgress) {
|
pub fn new_build_logs(&mut self, platform: impl Into<LogSource>, update: UpdateBuildProgress) {
|
||||||
let snapped = self.is_snapped(platform);
|
let source = platform.into();
|
||||||
|
let snapped = self.is_snapped(source);
|
||||||
|
|
||||||
// when the build is finished, switch to the console
|
// when the build is finished, switch to the console
|
||||||
if update.stage == Stage::Finished {
|
if update.stage == Stage::Finished {
|
||||||
@@ -439,7 +504,7 @@ impl Output {
|
|||||||
|
|
||||||
self.build_progress
|
self.build_progress
|
||||||
.build_logs
|
.build_logs
|
||||||
.entry(platform)
|
.entry(source)
|
||||||
.or_default()
|
.or_default()
|
||||||
.update(update);
|
.update(update);
|
||||||
|
|
||||||
@@ -454,7 +519,7 @@ impl Output {
|
|||||||
.children
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find_map(|(platform, child)| {
|
.find_map(|(platform, child)| {
|
||||||
if platform == &result.platform {
|
if platform == &result.target_platform {
|
||||||
let stdout = child.stdout.take().unwrap();
|
let stdout = child.stdout.take().unwrap();
|
||||||
let stderr = child.stderr.take().unwrap();
|
let stderr = child.stderr.take().unwrap();
|
||||||
Some((stdout, stderr))
|
Some((stdout, stderr))
|
||||||
@@ -463,7 +528,7 @@ impl Output {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let platform = result.platform;
|
let platform = result.target_platform;
|
||||||
|
|
||||||
let stdout = out.map(|(stdout, stderr)| RunningAppOutput {
|
let stdout = out.map(|(stdout, stderr)| RunningAppOutput {
|
||||||
stdout: BufReader::new(stdout).lines(),
|
stdout: BufReader::new(stdout).lines(),
|
||||||
@@ -480,7 +545,8 @@ impl Output {
|
|||||||
self.running_apps.insert(platform, app);
|
self.running_apps.insert(platform, app);
|
||||||
|
|
||||||
// Finish the build progress for the platform that just finished building
|
// Finish the build progress for the platform that just finished building
|
||||||
if let Some(build) = self.build_progress.build_logs.get_mut(&platform) {
|
let source = platform.into();
|
||||||
|
if let Some(build) = self.build_progress.build_logs.get_mut(&source) {
|
||||||
build.stage = Stage::Finished;
|
build.stage = Stage::Finished;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -735,12 +801,17 @@ impl Output {
|
|||||||
let mut events = vec![event];
|
let mut events = vec![event];
|
||||||
|
|
||||||
// Collect all the events within the next 10ms in one stream
|
// Collect all the events within the next 10ms in one stream
|
||||||
loop {
|
let collect_events = async {
|
||||||
let next = self.events.as_mut().unwrap().next();
|
loop {
|
||||||
tokio::select! {
|
let Some(Ok(next)) = self.events.as_mut().unwrap().next().await else {
|
||||||
msg = next => events.push(msg.unwrap().unwrap()),
|
break;
|
||||||
_ = tokio::time::sleep(Duration::from_millis(1)) => break
|
};
|
||||||
|
events.push(next);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
tokio::select! {
|
||||||
|
_ = collect_events => {},
|
||||||
|
_ = tokio::time::sleep(Duration::from_millis(10)) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounce events within the same frame
|
// Debounce events within the same frame
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::dioxus_crate::DioxusCrate;
|
use crate::dioxus_crate::DioxusCrate;
|
||||||
use crate::serve::Serve;
|
use crate::serve::{next_or_pending, Serve};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use axum::extract::{Request, State};
|
use axum::extract::{Request, State};
|
||||||
use axum::middleware::{self, Next};
|
use axum::middleware::{self, Next};
|
||||||
@@ -240,6 +240,7 @@ impl Server {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, socket)| async move { (idx, socket.next().await) })
|
.map(|(idx, socket)| async move { (idx, socket.next().await) })
|
||||||
.collect::<FuturesUnordered<_>>();
|
.collect::<FuturesUnordered<_>>();
|
||||||
|
let next_new_message = next_or_pending(new_message.next());
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
new_hot_reload_socket = &mut new_hot_reload_socket => {
|
new_hot_reload_socket = &mut new_hot_reload_socket => {
|
||||||
@@ -266,7 +267,7 @@ impl Server {
|
|||||||
panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use");
|
panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((idx, message)) = new_message.next() => {
|
(idx, message) = next_new_message => {
|
||||||
match message {
|
match message {
|
||||||
Some(Ok(message)) => return Some(message),
|
Some(Ok(message)) => return Some(message),
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ pub trait DioxusRouterExt<S> {
|
|||||||
/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
|
/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||||
/// let router = axum::Router::new()
|
/// let router = axum::Router::new()
|
||||||
/// // Server side render the application, serve static assets, and register server functions
|
/// // Server side render the application, serve static assets, and register server functions
|
||||||
/// .serve_static_assets("dist")
|
/// .serve_static_assets()
|
||||||
/// // Server render the application
|
/// // Server render the application
|
||||||
/// // ...
|
/// // ...
|
||||||
/// .into_make_service();
|
/// .into_make_service();
|
||||||
@@ -133,7 +133,7 @@ pub trait DioxusRouterExt<S> {
|
|||||||
/// axum::serve(listener, router).await.unwrap();
|
/// axum::serve(listener, router).await.unwrap();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn serve_static_assets(self, assets_path: impl Into<std::path::PathBuf>) -> Self
|
fn serve_static_assets(self) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
@@ -203,18 +203,16 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is a breaking change, but we should probably serve static assets from a different directory than dist where the server executable is located
|
fn serve_static_assets(mut self) -> Self {
|
||||||
// This would prevent issues like https://github.com/DioxusLabs/dioxus/issues/2327
|
|
||||||
fn serve_static_assets(mut self, assets_path: impl Into<std::path::PathBuf>) -> Self {
|
|
||||||
use tower_http::services::{ServeDir, ServeFile};
|
use tower_http::services::{ServeDir, ServeFile};
|
||||||
|
|
||||||
let assets_path = assets_path.into();
|
let public_path = crate::public_path();
|
||||||
|
|
||||||
// Serve all files in dist folder except index.html
|
// Serve all files in public folder except index.html
|
||||||
let dir = std::fs::read_dir(&assets_path).unwrap_or_else(|e| {
|
let dir = std::fs::read_dir(&public_path).unwrap_or_else(|e| {
|
||||||
panic!(
|
panic!(
|
||||||
"Couldn't read assets directory at {:?}: {}",
|
"Couldn't read public directory at {:?}: {}",
|
||||||
&assets_path, e
|
&public_path, e
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -224,7 +222,7 @@ where
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let route = path
|
let route = path
|
||||||
.strip_prefix(&assets_path)
|
.strip_prefix(&public_path)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|segment| {
|
.map(|segment| {
|
||||||
@@ -251,10 +249,7 @@ where
|
|||||||
let ssr_state = SSRState::new(&cfg);
|
let ssr_state = SSRState::new(&cfg);
|
||||||
|
|
||||||
// Add server functions and render index.html
|
// Add server functions and render index.html
|
||||||
#[allow(unused_mut)]
|
let server = self.serve_static_assets().register_server_functions();
|
||||||
let mut server = self
|
|
||||||
.serve_static_assets(cfg.assets_path.clone())
|
|
||||||
.register_server_functions();
|
|
||||||
|
|
||||||
server.fallback(
|
server.fallback(
|
||||||
get(render_handler).with_state(
|
get(render_handler).with_state(
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ async fn launch_server(
|
|||||||
|
|
||||||
let cfg = platform_config.server_cfg.build();
|
let cfg = platform_config.server_cfg.build();
|
||||||
|
|
||||||
let mut router = router.serve_static_assets(cfg.assets_path.clone());
|
let mut router = router.serve_static_assets();
|
||||||
|
|
||||||
router.fallback(
|
router.fallback(
|
||||||
axum::routing::get(crate::axum_adapter::render_handler).with_state(
|
axum::routing::get(crate::axum_adapter::render_handler).with_state(
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ pub struct ServeConfigBuilder {
|
|||||||
pub(crate) root_id: Option<&'static str>,
|
pub(crate) root_id: Option<&'static str>,
|
||||||
pub(crate) index_html: Option<String>,
|
pub(crate) index_html: Option<String>,
|
||||||
pub(crate) index_path: Option<PathBuf>,
|
pub(crate) index_path: Option<PathBuf>,
|
||||||
pub(crate) assets_path: Option<PathBuf>,
|
|
||||||
pub(crate) incremental: Option<dioxus_ssr::incremental::IncrementalRendererConfig>,
|
pub(crate) incremental: Option<dioxus_ssr::incremental::IncrementalRendererConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +21,6 @@ impl ServeConfigBuilder {
|
|||||||
root_id: None,
|
root_id: None,
|
||||||
index_html: None,
|
index_html: None,
|
||||||
index_path: None,
|
index_path: None,
|
||||||
assets_path: None,
|
|
||||||
incremental: None,
|
incremental: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,25 +49,15 @@ impl ServeConfigBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the path of the assets folder generated by the Dioxus CLI. (defaults to dist)
|
|
||||||
pub fn assets_path(mut self, assets_path: PathBuf) -> Self {
|
|
||||||
self.assets_path = Some(assets_path);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build the ServeConfig
|
/// Build the ServeConfig
|
||||||
pub fn build(self) -> ServeConfig {
|
pub fn build(self) -> ServeConfig {
|
||||||
let assets_path = self.assets_path.unwrap_or(
|
// The CLI always bundles static assets into the exe/public directory
|
||||||
dioxus_cli_config::CURRENT_CONFIG
|
let public_path = public_path();
|
||||||
.as_ref()
|
|
||||||
.map(|c| c.application.out_dir.clone())
|
|
||||||
.unwrap_or("dist".into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let index_path = self
|
let index_path = self
|
||||||
.index_path
|
.index_path
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.unwrap_or_else(|| assets_path.join("index.html"));
|
.unwrap_or_else(|| public_path.join("index.html"));
|
||||||
|
|
||||||
let root_id = self.root_id.unwrap_or("main");
|
let root_id = self.root_id.unwrap_or("main");
|
||||||
|
|
||||||
@@ -81,14 +69,23 @@ impl ServeConfigBuilder {
|
|||||||
|
|
||||||
ServeConfig {
|
ServeConfig {
|
||||||
index,
|
index,
|
||||||
assets_path,
|
|
||||||
incremental: self.incremental,
|
incremental: self.incremental,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the path to the public assets directory to serve static files from
|
||||||
|
pub(crate) fn public_path() -> PathBuf {
|
||||||
|
// The CLI always bundles static assets into the exe/public directory
|
||||||
|
std::env::current_exe()
|
||||||
|
.expect("Failed to get current executable path")
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("public")
|
||||||
|
}
|
||||||
|
|
||||||
fn load_index_path(path: PathBuf) -> String {
|
fn load_index_path(path: PathBuf) -> String {
|
||||||
let mut file = File::open(path).expect("Failed to find index.html. Make sure the index_path is set correctly and the WASM application has been built.");
|
let mut file = File::open(&path).unwrap_or_else(|_| panic!("Failed to find index.html. Make sure the index_path is set correctly and the WASM application has been built. Tried to open file at path: {path:?}"));
|
||||||
|
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)
|
file.read_to_string(&mut contents)
|
||||||
@@ -156,8 +153,6 @@ pub(crate) struct IndexHtml {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServeConfig {
|
pub struct ServeConfig {
|
||||||
pub(crate) index: IndexHtml,
|
pub(crate) index: IndexHtml,
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) assets_path: PathBuf,
|
|
||||||
pub(crate) incremental: Option<dioxus_ssr::incremental::IncrementalRendererConfig>,
|
pub(crate) incremental: Option<dioxus_ssr::incremental::IncrementalRendererConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ pub async fn generate_static_site(
|
|||||||
.map(|c| c.application.out_dir.clone())
|
.map(|c| c.application.out_dir.clone())
|
||||||
.unwrap_or("./dist".into());
|
.unwrap_or("./dist".into());
|
||||||
|
|
||||||
|
let assets_path = assets_path.join("public");
|
||||||
|
|
||||||
copy_static_files(&assets_path, &config.output_dir)?;
|
copy_static_files(&assets_path, &config.output_dir)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in new issue
Block a user