Restore SSG serve logic (#4111)
* Build ssg routes when both the client and server builds are finished * Fix default server fn encoding * fix tokio console * fix ssg * add prerendering message * render under the same environment variables as normal * fix check * use should_bundle_to_asset * enable ssg playwright test * fix clippy
This commit is contained in:
1 parent
f610c6b8c1
commit
fdd77d79c1
17 files changed
+357
-236
No files matched your search
@@ -150,7 +150,7 @@ built = { version = "0.7.5", features = ["git2"] }
|
||||
[features]
|
||||
default = []
|
||||
plugin = []
|
||||
tokio-console = ["dep:console-subscriber"]
|
||||
tokio-console = ["dep:console-subscriber", "tokio/tracing"]
|
||||
bundle = []
|
||||
no-downloads = []
|
||||
|
||||
|
||||
@@ -417,31 +417,20 @@ impl AppBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn open(
|
||||
/// Create a list of environment variables that the child process will use
|
||||
pub(crate) fn child_environment_variables(
|
||||
&mut self,
|
||||
devserver_ip: SocketAddr,
|
||||
open_address: Option<SocketAddr>,
|
||||
devserver_ip: Option<SocketAddr>,
|
||||
start_fullstack_on_address: Option<SocketAddr>,
|
||||
open_browser: bool,
|
||||
always_on_top: bool,
|
||||
build_id: BuildId,
|
||||
args: &[String],
|
||||
) -> Result<()> {
|
||||
) -> Vec<(&'static str, String)> {
|
||||
let krate = &self.build;
|
||||
|
||||
// Set the env vars that the clients will expect
|
||||
// These need to be stable within a release version (ie 0.6.0)
|
||||
let mut envs = vec![
|
||||
(dioxus_cli_config::CLI_ENABLED_ENV, "true".to_string()),
|
||||
(
|
||||
dioxus_cli_config::DEVSERVER_IP_ENV,
|
||||
devserver_ip.ip().to_string(),
|
||||
),
|
||||
(
|
||||
dioxus_cli_config::DEVSERVER_PORT_ENV,
|
||||
devserver_ip.port().to_string(),
|
||||
),
|
||||
(
|
||||
dioxus_cli_config::APP_TITLE_ENV,
|
||||
krate.config.web.app.title.clone(),
|
||||
@@ -457,6 +446,17 @@ impl AppBuilder {
|
||||
),
|
||||
];
|
||||
|
||||
if let Some(devserver_ip) = devserver_ip {
|
||||
envs.push((
|
||||
dioxus_cli_config::DEVSERVER_IP_ENV,
|
||||
devserver_ip.ip().to_string(),
|
||||
));
|
||||
envs.push((
|
||||
dioxus_cli_config::DEVSERVER_PORT_ENV,
|
||||
devserver_ip.port().to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if crate::VERBOSITY
|
||||
.get()
|
||||
.map(|f| f.verbose)
|
||||
@@ -480,6 +480,27 @@ impl AppBuilder {
|
||||
envs.push((dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string()));
|
||||
}
|
||||
|
||||
envs
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn open(
|
||||
&mut self,
|
||||
devserver_ip: SocketAddr,
|
||||
open_address: Option<SocketAddr>,
|
||||
start_fullstack_on_address: Option<SocketAddr>,
|
||||
open_browser: bool,
|
||||
always_on_top: bool,
|
||||
build_id: BuildId,
|
||||
args: &[String],
|
||||
) -> Result<()> {
|
||||
let envs = self.child_environment_variables(
|
||||
Some(devserver_ip),
|
||||
start_fullstack_on_address,
|
||||
always_on_top,
|
||||
build_id,
|
||||
);
|
||||
|
||||
// We try to use stdin/stdout to communicate with the app
|
||||
match self.build.platform {
|
||||
// Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead
|
||||
|
||||
@@ -12,6 +12,7 @@ mod assets;
|
||||
mod builder;
|
||||
mod context;
|
||||
mod patch;
|
||||
mod pre_render;
|
||||
mod request;
|
||||
mod tools;
|
||||
|
||||
@@ -19,5 +20,6 @@ pub(crate) use assets::*;
|
||||
pub(crate) use builder::*;
|
||||
pub(crate) use context::*;
|
||||
pub(crate) use patch::*;
|
||||
pub(crate) use pre_render::*;
|
||||
pub(crate) use request::*;
|
||||
pub(crate) use tools::*;
|
||||
@@ -0,0 +1,148 @@
|
||||
use anyhow::Context;
|
||||
use dioxus_cli_config::{server_ip, server_port};
|
||||
use dioxus_dx_wire_format::BuildStage;
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::BuildId;
|
||||
|
||||
use super::{AppBuilder, BuilderUpdate};
|
||||
|
||||
/// Pre-render the static routes, performing static-site generation
|
||||
pub(crate) async fn pre_render_static_routes(
|
||||
devserver_ip: Option<SocketAddr>,
|
||||
builder: &mut AppBuilder,
|
||||
updates: Option<&futures_channel::mpsc::UnboundedSender<BuilderUpdate>>,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(updates) = updates {
|
||||
updates
|
||||
.unbounded_send(BuilderUpdate::Progress {
|
||||
stage: BuildStage::Prerendering,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
let server_exe = builder.build.main_exe();
|
||||
|
||||
// Use the address passed in through environment variables or default to localhost:9999. We need
|
||||
// to default to a value that is different than the CLI default address to avoid conflicts
|
||||
let ip = server_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
||||
let port = server_port().unwrap_or(9999);
|
||||
let fullstack_address = SocketAddr::new(ip, port);
|
||||
let address = fullstack_address.ip().to_string();
|
||||
let port = fullstack_address.port().to_string();
|
||||
|
||||
// Borrow port and address so we can easily move them into multiple tasks below
|
||||
let address = &address;
|
||||
let port = &port;
|
||||
|
||||
tracing::info!("Running SSG at http://{address}:{port} for {server_exe:?}");
|
||||
|
||||
let vars = builder.child_environment_variables(
|
||||
devserver_ip,
|
||||
Some(fullstack_address),
|
||||
false,
|
||||
BuildId::SERVER,
|
||||
);
|
||||
// Run the server executable
|
||||
let _child = Command::new(&server_exe)
|
||||
.envs(vars)
|
||||
.current_dir(server_exe.parent().unwrap())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
// Borrow reqwest_client so we only move the reference into the futures
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
let reqwest_client = &reqwest_client;
|
||||
|
||||
// Get the routes from the `/static_routes` endpoint
|
||||
let mut routes = None;
|
||||
|
||||
// The server may take a few seconds to start up. Try fetching the route up to 5 times with a one second delay
|
||||
const RETRY_ATTEMPTS: usize = 5;
|
||||
for i in 0..=RETRY_ATTEMPTS {
|
||||
tracing::debug!(
|
||||
"Attempting to get static routes from server. Attempt {i} of {RETRY_ATTEMPTS}"
|
||||
);
|
||||
|
||||
let request = reqwest_client
|
||||
.post(format!("http://{address}:{port}/api/static_routes"))
|
||||
.body("{}".to_string())
|
||||
.send()
|
||||
.await;
|
||||
match request {
|
||||
Ok(request) => {
|
||||
routes = Some(request
|
||||
.json::<Vec<String>>()
|
||||
.await
|
||||
.inspect(|text| tracing::debug!("Got static routes: {text:?}"))
|
||||
.context("Failed to parse static routes from the server. Make sure your server function returns Vec<String> with the (default) json encoding")?);
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
// If the request fails, try up to 5 times with a one second delay
|
||||
// If it fails 5 times, return the error
|
||||
if i == RETRY_ATTEMPTS {
|
||||
return Err(err).context("Failed to get static routes from server. Make sure you have a server function at the `/api/static_routes` endpoint that returns Vec<String> of static routes.");
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let routes = routes.expect(
|
||||
"static routes should exist or an error should have been returned on the last attempt",
|
||||
);
|
||||
|
||||
// Create a pool of futures that cache each route
|
||||
let mut resolved_routes = routes
|
||||
.into_iter()
|
||||
.map(|route| async move {
|
||||
tracing::info!("Rendering {route} for SSG");
|
||||
|
||||
// For each route, ping the server to force it to cache the response for ssg
|
||||
let request = reqwest_client
|
||||
.get(format!("http://{address}:{port}{route}"))
|
||||
.header("Accept", "text/html")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
// If it takes longer than 30 seconds to resolve the route, log a warning
|
||||
let warning_task = tokio::spawn({
|
||||
let route = route.clone();
|
||||
async move {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
tracing::warn!("Route {route} has been rendering for 30 seconds");
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the streaming response to completely finish before continuing. We don't use the html it returns directly
|
||||
// because it may contain artifacts of intermediate streaming steps while the page is loading. The SSG app should write
|
||||
// the final clean HTML to the disk automatically after the request completes.
|
||||
let _html = request.text().await?;
|
||||
|
||||
// Cancel the warning task if it hasn't already run
|
||||
warning_task.abort();
|
||||
|
||||
Ok::<_, reqwest::Error>(route)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
while let Some(route) = resolved_routes.next().await {
|
||||
match route {
|
||||
Ok(route) => tracing::debug!("ssg success: {route:?}"),
|
||||
Err(err) => tracing::error!("ssg error: {err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("SSG complete");
|
||||
|
||||
drop(_child);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1412,6 +1412,10 @@ impl BuildRequest {
|
||||
// Now extract the assets from the fat binary
|
||||
self.collect_assets(&self.patch_exe(artifacts.time_start), ctx)?;
|
||||
|
||||
// If this is a web build, reset the index.html file in case it was modified by SSG
|
||||
self.write_index_html(&artifacts.assets)
|
||||
.context("Failed to write index.html")?;
|
||||
|
||||
// Clean up the temps manually
|
||||
// todo: we might want to keep them around for debugging purposes
|
||||
for file in object_files {
|
||||
@@ -3202,7 +3206,6 @@ impl BuildRequest {
|
||||
|| will_wasm_opt
|
||||
|| ctx.mode == BuildMode::Fat;
|
||||
let keep_names = will_wasm_opt || ctx.mode == BuildMode::Fat;
|
||||
let package_to_asset = self.release && !should_bundle_split;
|
||||
let demangle = false;
|
||||
let wasm_opt_options = WasmOptConfig {
|
||||
memory_packing: self.wasm_split,
|
||||
@@ -3345,25 +3348,53 @@ impl BuildRequest {
|
||||
|
||||
// In release mode, we make the wasm and bindgen files into assets so they get bundled with max
|
||||
// optimizations.
|
||||
let wasm_path = if package_to_asset {
|
||||
// Make sure to register the main wasm file with the asset system
|
||||
let name = assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?;
|
||||
format!("assets/{}", name.bundled_path())
|
||||
} else {
|
||||
let asset = self.wasm_bindgen_wasm_output_file();
|
||||
format!("wasm/{}", asset.file_name().unwrap().to_str().unwrap())
|
||||
};
|
||||
|
||||
let js_path = if package_to_asset {
|
||||
if self.should_bundle_to_asset() {
|
||||
// Register the main.js with the asset system so it bundles in the snippets and optimizes
|
||||
let name = assets.register_asset(
|
||||
assets.register_asset(
|
||||
&self.wasm_bindgen_js_output_file(),
|
||||
AssetOptions::Js(JsAssetOptions::new().with_minify(true).with_preload(true)),
|
||||
)?;
|
||||
}
|
||||
|
||||
if self.should_bundle_to_asset() {
|
||||
// Make sure to register the main wasm file with the asset system
|
||||
assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?;
|
||||
}
|
||||
|
||||
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
||||
self.write_index_html(assets)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the index.html file to the output directory. This must be called after the wasm and js
|
||||
/// assets are registered with the asset system if this is a release build.
|
||||
pub(crate) fn write_index_html(&self, assets: &AssetManifest) -> Result<()> {
|
||||
// Get the path to the wasm-bindgen output files. Either the direct file or the opitmized one depending on the build mode
|
||||
let wasm_bindgen_wasm_out = self.wasm_bindgen_wasm_output_file();
|
||||
let wasm_path = if self.should_bundle_to_asset() {
|
||||
let name = assets
|
||||
.get_first_asset_for_source(&wasm_bindgen_wasm_out)
|
||||
.expect("The wasm source must exist before creating index.html");
|
||||
format!("assets/{}", name.bundled_path())
|
||||
} else {
|
||||
let asset = self.wasm_bindgen_js_output_file();
|
||||
format!("wasm/{}", asset.file_name().unwrap().to_str().unwrap())
|
||||
format!(
|
||||
"wasm/{}",
|
||||
wasm_bindgen_wasm_out.file_name().unwrap().to_str().unwrap()
|
||||
)
|
||||
};
|
||||
|
||||
let wasm_bindgen_js_out = self.wasm_bindgen_js_output_file();
|
||||
let js_path = if self.should_bundle_to_asset() {
|
||||
let name = assets
|
||||
.get_first_asset_for_source(&wasm_bindgen_js_out)
|
||||
.expect("The js source must exist before creating index.html");
|
||||
format!("assets/{}", name.bundled_path())
|
||||
} else {
|
||||
format!(
|
||||
"wasm/{}",
|
||||
wasm_bindgen_js_out.file_name().unwrap().to_str().unwrap()
|
||||
)
|
||||
};
|
||||
|
||||
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
||||
@@ -3371,7 +3402,6 @@ impl BuildRequest {
|
||||
self.root_dir().join("index.html"),
|
||||
self.prepare_html(assets, &wasm_path, &js_path).unwrap(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ pub struct BuildArgs {
|
||||
#[clap(long)]
|
||||
pub(crate) fullstack: Option<bool>,
|
||||
|
||||
/// Pre-render all routes returned from the app's `/static_routes` endpoint [default: false]
|
||||
#[clap(long)]
|
||||
pub(crate) ssg: bool,
|
||||
|
||||
/// Arguments for the build itself
|
||||
#[clap(flatten)]
|
||||
pub(crate) build_arguments: TargetArgs,
|
||||
@@ -63,6 +67,7 @@ impl CommandWithPlatformOverrides<BuildArgs> {
|
||||
pub async fn build(self) -> Result<StructuredOutput> {
|
||||
tracing::info!("Building project...");
|
||||
|
||||
let ssg = self.shared.ssg;
|
||||
let targets = self.into_targets().await?;
|
||||
|
||||
AppBuilder::start(&targets.client, BuildMode::Base)?
|
||||
@@ -73,9 +78,13 @@ impl CommandWithPlatformOverrides<BuildArgs> {
|
||||
|
||||
if let Some(server) = targets.server.as_ref() {
|
||||
// If the server is present, we need to build it as well
|
||||
AppBuilder::start(server, BuildMode::Base)?
|
||||
.finish_build()
|
||||
.await?;
|
||||
let mut server_build = AppBuilder::start(server, BuildMode::Base)?;
|
||||
server_build.finish_build().await?;
|
||||
|
||||
// Run SSG and cache static routes
|
||||
if ssg {
|
||||
crate::pre_render_static_routes(None, &mut server_build, None).await?;
|
||||
}
|
||||
|
||||
tracing::info!(path = ?targets.client.root_dir(), "Server build completed successfully! 🚀");
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform};
|
||||
use anyhow::{anyhow, Context};
|
||||
use dioxus_cli_config::{server_ip, server_port};
|
||||
use futures_util::stream::FuturesUnordered;
|
||||
use futures_util::StreamExt;
|
||||
use path_absolutize::Absolutize;
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::Path,
|
||||
time::Duration,
|
||||
};
|
||||
use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};
|
||||
use tokio::process::Command;
|
||||
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::*;
|
||||
@@ -35,10 +27,6 @@ pub struct Bundle {
|
||||
#[clap(long)]
|
||||
pub out_dir: Option<PathBuf>,
|
||||
|
||||
/// Run the ssg config of the app and generate the files
|
||||
#[clap(long)]
|
||||
pub(crate) ssg: bool,
|
||||
|
||||
/// The arguments for the dioxus build
|
||||
#[clap(flatten)]
|
||||
pub(crate) args: CommandWithPlatformOverrides<BuildArgs>,
|
||||
@@ -147,17 +135,6 @@ impl Bundle {
|
||||
);
|
||||
}
|
||||
|
||||
// Run SSG and cache static routes
|
||||
if self.ssg {
|
||||
if let Some(server) = server.as_ref() {
|
||||
tracing::info!("Running SSG for static routes...");
|
||||
Self::pre_render_static_routes(&server.main_exe()).await?;
|
||||
tracing::info!("SSG complete");
|
||||
} else {
|
||||
tracing::error!("SSG is only supported for fullstack apps. Ensure you have the server feature enabled and try again.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StructuredOutput::BundleOutput { bundles })
|
||||
}
|
||||
|
||||
@@ -282,121 +259,4 @@ impl Bundle {
|
||||
|
||||
Ok(bundles)
|
||||
}
|
||||
|
||||
/// Pre-render the static routes, performing static-site generation
|
||||
async fn pre_render_static_routes(server_exe: &Path) -> anyhow::Result<()> {
|
||||
// Use the address passed in through environment variables or default to localhost:9999. We need
|
||||
// to default to a value that is different than the CLI default address to avoid conflicts
|
||||
let ip = server_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
||||
let port = server_port().unwrap_or(9999);
|
||||
let fullstack_address = SocketAddr::new(ip, port);
|
||||
let address = fullstack_address.ip().to_string();
|
||||
let port = fullstack_address.port().to_string();
|
||||
|
||||
// Borrow port and address so we can easily moe them into multiple tasks below
|
||||
let address = &address;
|
||||
let port = &port;
|
||||
|
||||
tracing::info!("Running SSG at http://{address}:{port} for {server_exe:?}");
|
||||
|
||||
// Run the server executable
|
||||
let _child = Command::new(server_exe)
|
||||
.env(dioxus_cli_config::SERVER_PORT_ENV, port)
|
||||
.env(dioxus_cli_config::SERVER_IP_ENV, address)
|
||||
.current_dir(server_exe.parent().unwrap())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
// Borrow reqwest_client so we only move the reference into the futures
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
let reqwest_client = &reqwest_client;
|
||||
|
||||
// Get the routes from the `/static_routes` endpoint
|
||||
let mut routes = None;
|
||||
|
||||
// The server may take a few seconds to start up. Try fetching the route up to 5 times with a one second delay
|
||||
const RETRY_ATTEMPTS: usize = 5;
|
||||
for i in 0..=RETRY_ATTEMPTS {
|
||||
tracing::debug!(
|
||||
"Attempting to get static routes from server. Attempt {i} of {RETRY_ATTEMPTS}"
|
||||
);
|
||||
|
||||
let request = reqwest_client
|
||||
.post(format!("http://{address}:{port}/api/static_routes"))
|
||||
.body("{}".to_string())
|
||||
.send()
|
||||
.await;
|
||||
match request {
|
||||
Ok(request) => {
|
||||
routes = Some(request
|
||||
.json::<Vec<String>>()
|
||||
.await
|
||||
.inspect(|text| tracing::debug!("Got static routes: {text:?}"))
|
||||
.context("Failed to parse static routes from the server. Make sure your server function returns Vec<String> with the (default) json encoding")?);
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
// If the request fails, try up to 5 times with a one second delay
|
||||
// If it fails 5 times, return the error
|
||||
if i == RETRY_ATTEMPTS {
|
||||
return Err(err).context("Failed to get static routes from server. Make sure you have a server function at the `/api/static_routes` endpoint that returns Vec<String> of static routes.");
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let routes = routes.expect(
|
||||
"static routes should exist or an error should have been returned on the last attempt",
|
||||
);
|
||||
|
||||
// Create a pool of futures that cache each route
|
||||
let mut resolved_routes = routes
|
||||
.into_iter()
|
||||
.map(|route| async move {
|
||||
tracing::info!("Rendering {route} for SSG");
|
||||
|
||||
// For each route, ping the server to force it to cache the response for ssg
|
||||
let request = reqwest_client
|
||||
.get(format!("http://{address}:{port}{route}"))
|
||||
.header("Accept", "text/html")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
// If it takes longer than 30 seconds to resolve the route, log a warning
|
||||
let warning_task = tokio::spawn({
|
||||
let route = route.clone();
|
||||
async move {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
tracing::warn!("Route {route} has been rendering for 30 seconds");
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the streaming response to completely finish before continuing. We don't use the html it returns directly
|
||||
// because it may contain artifacts of intermediate streaming steps while the page is loading. The SSG app should write
|
||||
// the final clean HTML to the disk automatically after the request completes.
|
||||
let _html = request.text().await?;
|
||||
|
||||
// Cancel the warning task if it hasn't already run
|
||||
warning_task.abort();
|
||||
|
||||
Ok::<_, reqwest::Error>(route)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
while let Some(route) = resolved_routes.next().await {
|
||||
match route {
|
||||
Ok(route) => tracing::debug!("ssg success: {route:?}"),
|
||||
Err(err) => tracing::error!("ssg error: {err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("SSG complete");
|
||||
|
||||
drop(_child);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ impl RunArgs {
|
||||
match update {
|
||||
BuilderUpdate::BuildReady { bundle } => {
|
||||
_ = builder
|
||||
.open(bundle, &mut devserver)
|
||||
.open(&bundle, &mut devserver)
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("Failed to open app: {}", e));
|
||||
|
||||
|
||||
@@ -82,6 +82,11 @@ impl TraceController {
|
||||
))
|
||||
};
|
||||
|
||||
#[cfg(feature = "tokio-console")]
|
||||
let filter = filter
|
||||
.add_directive("tokio=trace".parse().unwrap())
|
||||
.add_directive("runtime=trace".parse().unwrap());
|
||||
|
||||
let json_filter = tracing_subscriber::filter::filter_fn(move |meta| {
|
||||
if meta.fields().len() == 1 && meta.fields().iter().next().unwrap().name() == "json" {
|
||||
return args.verbosity.json_output;
|
||||
|
||||
@@ -126,6 +126,9 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
// And then update the websocketed clients with the new build status in case they want it
|
||||
devserver.new_build_update(&update).await;
|
||||
|
||||
// Start the SSG build if we need to
|
||||
builder.new_build_update(&update, &devserver).await;
|
||||
|
||||
// And then open the app if it's ready
|
||||
match update {
|
||||
BuilderUpdate::Progress { .. } => {}
|
||||
@@ -161,7 +164,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
}
|
||||
BuildMode::Base | BuildMode::Fat => {
|
||||
_ = builder
|
||||
.open(bundle, &mut devserver)
|
||||
.open(&bundle, &mut devserver)
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("Failed to open app: {}", e));
|
||||
}
|
||||
|
||||
@@ -550,6 +550,7 @@ impl Output {
|
||||
BuildStage::Linking => lines.push("Linking".yellow()),
|
||||
BuildStage::Hotpatching => lines.push("Hot-patching...".yellow()),
|
||||
BuildStage::ExtractingAssets => lines.push("Extracting assets".yellow()),
|
||||
BuildStage::Prerendering => lines.push("Pre-rendering...".yellow()),
|
||||
_ => {}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{AppBuilder, ServeUpdate, WebServer};
|
||||
use crate::{
|
||||
platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,
|
||||
BuildTargets, Error, HotpatchModuleCache, Platform, Result, ServeArgs, TailwindCli, TraceSrc,
|
||||
Workspace,
|
||||
BuildTargets, BuilderUpdate, Error, HotpatchModuleCache, Platform, Result, ServeArgs,
|
||||
TailwindCli, TraceSrc, Workspace,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use dioxus_core::internal::{
|
||||
@@ -66,6 +66,7 @@ pub(crate) struct AppServer {
|
||||
pub(crate) _wsl_file_poll_interval: u16,
|
||||
pub(crate) always_on_top: bool,
|
||||
pub(crate) fullstack: bool,
|
||||
pub(crate) ssg: bool,
|
||||
pub(crate) watch_fs: bool,
|
||||
|
||||
// resolve args related to the webserver
|
||||
@@ -138,6 +139,7 @@ impl AppServer {
|
||||
let (watcher_tx, watcher_rx) = futures_channel::mpsc::unbounded();
|
||||
let watcher = create_notify_watcher(watcher_tx.clone(), wsl_file_poll_interval as u64);
|
||||
|
||||
let ssg = args.platform_args.shared.targets.ssg;
|
||||
let target_args = CommandWithPlatformOverrides {
|
||||
shared: args.platform_args.shared.targets,
|
||||
server: args.platform_args.server.map(|s| s.targets),
|
||||
@@ -150,7 +152,7 @@ impl AppServer {
|
||||
let fullstack = server.is_some();
|
||||
let should_proxy_port = match client.platform {
|
||||
Platform::Server => true,
|
||||
_ => fullstack,
|
||||
_ => fullstack && !ssg,
|
||||
};
|
||||
|
||||
let proxied_port = should_proxy_port
|
||||
@@ -204,6 +206,7 @@ impl AppServer {
|
||||
_force_sequential: force_sequential,
|
||||
cross_origin_policy,
|
||||
fullstack,
|
||||
ssg,
|
||||
tw_watcher,
|
||||
server_args,
|
||||
client_args,
|
||||
@@ -226,6 +229,27 @@ impl AppServer {
|
||||
Ok(runner)
|
||||
}
|
||||
|
||||
pub(crate) async fn rebuild_ssg(&mut self, devserver: &WebServer) {
|
||||
if self.client.stage != BuildStage::Success {
|
||||
return;
|
||||
}
|
||||
// Run SSG and cache static routes if the server build is done
|
||||
if let Some(server) = self.server.as_mut() {
|
||||
if !self.ssg || server.stage != BuildStage::Success {
|
||||
return;
|
||||
}
|
||||
if let Err(err) = crate::pre_render_static_routes(
|
||||
Some(devserver.devserver_address()),
|
||||
server,
|
||||
Some(&server.tx.clone()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to pre-render static routes: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn wait(&mut self) -> ServeUpdate {
|
||||
let client = &mut self.client;
|
||||
let server = self.server.as_mut();
|
||||
@@ -294,6 +318,14 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an update from the builder
|
||||
pub(crate) async fn new_build_update(&mut self, update: &BuilderUpdate, devserver: &WebServer) {
|
||||
if let BuilderUpdate::BuildReady { .. } = update {
|
||||
// If the build is ready, we need to check if we need to pre-render with ssg
|
||||
self.rebuild_ssg(devserver).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the list of changed files from the file watcher, attempting to aggressively prevent
|
||||
/// full rebuilds by hot-reloading RSX and hot-patching Rust code.
|
||||
///
|
||||
@@ -488,7 +520,7 @@ impl AppServer {
|
||||
/// Finally "bundle" this app and return a handle to it
|
||||
pub(crate) async fn open(
|
||||
&mut self,
|
||||
artifacts: BuildArtifacts,
|
||||
artifacts: &BuildArtifacts,
|
||||
devserver: &mut WebServer,
|
||||
) -> Result<()> {
|
||||
// Make sure to save artifacts regardless of if we're opening the app or not
|
||||
@@ -555,7 +587,8 @@ impl AppServer {
|
||||
let displayed_address = devserver.displayed_address();
|
||||
|
||||
// Always open the server first after the client has been built
|
||||
if let Some(server) = self.server.as_mut() {
|
||||
// Only open the server if it isn't prerendered
|
||||
if let Some(server) = self.server.as_mut().filter(|_| !self.ssg) {
|
||||
tracing::debug!("Opening server build");
|
||||
server.soft_kill().await;
|
||||
server
|
||||
|
||||
Reference in new issue
Block a user