From 0c5025ffa0c1a094fe33345e95b7a2694516297e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 18 Jul 2023 10:24:29 -0700 Subject: [PATCH 01/31] WIP desktop serve hot reload --- packages/cli/Cargo.toml | 2 + packages/cli/src/builder.rs | 7 +- packages/cli/src/cli/autoformat.rs | 8 +- packages/cli/src/cli/build.rs | 10 +- packages/cli/src/cli/cfg.rs | 21 +- packages/cli/src/cli/serve.rs | 44 +- packages/cli/src/config.rs | 6 +- packages/cli/src/server/desktop/mod.rs | 255 +++++++++ packages/cli/src/server/mod.rs | 498 ++---------------- packages/cli/src/server/output.rs | 54 +- .../cli/src/server/{ => web}/hot_reload.rs | 0 packages/cli/src/server/web/mod.rs | 475 +++++++++++++++++ packages/cli/src/server/{ => web}/proxy.rs | 0 13 files changed, 865 insertions(+), 515 deletions(-) create mode 100644 packages/cli/src/server/desktop/mod.rs rename packages/cli/src/server/{ => web}/hot_reload.rs (100%) create mode 100644 packages/cli/src/server/web/mod.rs rename packages/cli/src/server/{ => web}/proxy.rs (100%) diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index c947baa63..cc78a5d28 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -81,6 +81,8 @@ rsx-rosetta = { workspace = true } dioxus-rsx = { workspace = true } dioxus-html = { workspace = true, features = ["hot-reload-context"] } dioxus-core = { workspace = true, features = ["serialize"] } +dioxus-hot-reload = { workspace = true } +interprocess-docfix = { version = "1.2.2" } [features] default = [] diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 6f50fc49d..57794b728 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -244,7 +244,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { }) } -pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<()> { +pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result { log::info!("🚅 Running build [Desktop] command..."); let ignore_files = build_assets(config)?; @@ -365,7 +365,10 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<()> { ); } - Ok(()) + Ok(BuildResult { + warnings: vec![], + elapsed_time: 0, + }) } fn prettier_build(cmd: subprocess::Exec) -> anyhow::Result> { diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index fbc34402b..ea2c27148 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -136,12 +136,16 @@ async fn autoformat_project(check: bool) -> Result<()> { } fn collect_rs_files(folder: &Path, files: &mut Vec) { - let Ok(folder) = folder.read_dir() else { return }; + let Ok(folder) = folder.read_dir() else { + return; + }; // load the gitignore for entry in folder { - let Ok(entry) = entry else { continue; }; + let Ok(entry) = entry else { + continue; + }; let path = entry.path(); diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 148a4ab76..29edf301b 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,3 +1,4 @@ +use crate::cfg::Platform; #[cfg(feature = "plugin")] use crate::plugin::PluginManager; @@ -42,16 +43,13 @@ impl Build { #[cfg(feature = "plugin")] let _ = PluginManager::on_build_start(&crate_config, &platform); - match platform.as_str() { - "web" => { + match platform { + Platform::Web => { crate::builder::build(&crate_config, false)?; } - "desktop" => { + Platform::Desktop => { crate::builder::build_desktop(&crate_config, false)?; } - _ => { - return custom_error!("Unsupported platform target."); - } } let temp = gen_page(&crate_config.dioxus_config, false); diff --git a/packages/cli/src/cli/cfg.rs b/packages/cli/src/cli/cfg.rs index 30ca89ce9..edc32f346 100644 --- a/packages/cli/src/cli/cfg.rs +++ b/packages/cli/src/cli/cfg.rs @@ -1,3 +1,6 @@ +use clap::ValueEnum; +use serde::Serialize; + use super::*; /// Config options for the build system. @@ -26,8 +29,8 @@ pub struct ConfigOptsBuild { pub profile: Option, /// Build platform: support Web & Desktop [default: "default_platform"] - #[clap(long)] - pub platform: Option, + #[clap(long, value_enum)] + pub platform: Option, /// Space separated list of features to activate #[clap(long)] @@ -69,8 +72,8 @@ pub struct ConfigOptsServe { pub profile: Option, /// Build platform: support Web & Desktop [default: "default_platform"] - #[clap(long)] - pub platform: Option, + #[clap(long, value_enum)] + pub platform: Option, /// Build with hot reloading rsx [default: false] #[clap(long)] @@ -88,6 +91,16 @@ pub struct ConfigOptsServe { pub features: Option>, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)] +pub enum Platform { + #[clap(name = "web")] + #[serde(rename = "web")] + Web, + #[clap(name = "desktop")] + #[serde(rename = "desktop")] + Desktop, +} + /// Ensure the given value for `--public-url` is formatted correctly. pub fn parse_public_url(val: &str) -> String { let prefix = if !val.starts_with('/') { "/" } else { "" }; diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index e50eb0f38..89b9eaaa0 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -47,33 +47,33 @@ impl Serve { .clone() }); - if platform.as_str() == "desktop" { - crate::builder::build_desktop(&crate_config, true)?; + match platform { + cfg::Platform::Web => { + // generate dev-index page + Serve::regen_dev_page(&crate_config)?; - match &crate_config.executable { - crate::ExecutableType::Binary(name) - | crate::ExecutableType::Lib(name) - | crate::ExecutableType::Example(name) => { - let mut file = crate_config.out_dir.join(name); - if cfg!(windows) { - file.set_extension("exe"); + // start the develop server + server::web::startup(self.serve.port, crate_config.clone(), self.serve.open) + .await?; + } + cfg::Platform::Desktop => { + crate::builder::build_desktop(&crate_config, true)?; + + match &crate_config.executable { + crate::ExecutableType::Binary(name) + | crate::ExecutableType::Lib(name) + | crate::ExecutableType::Example(name) => { + let mut file = crate_config.out_dir.join(name); + if cfg!(windows) { + file.set_extension("exe"); + } + Command::new(file.to_str().unwrap()) + .stdout(Stdio::inherit()) + .output()?; } - Command::new(file.to_str().unwrap()) - .stdout(Stdio::inherit()) - .output()?; } } - return Ok(()); - } else if platform != "web" { - return custom_error!("Unsupported platform target."); } - - // generate dev-index page - Serve::regen_dev_page(&crate_config)?; - - // start the develop server - server::startup(self.serve.port, crate_config.clone(), self.serve.open).await?; - Ok(()) } diff --git a/packages/cli/src/config.rs b/packages/cli/src/config.rs index 05aca0d35..e46b4f0fe 100644 --- a/packages/cli/src/config.rs +++ b/packages/cli/src/config.rs @@ -1,4 +1,4 @@ -use crate::error::Result; +use crate::{cfg::Platform, error::Result}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -73,7 +73,7 @@ impl Default for DioxusConfig { Self { application: ApplicationConfig { name: "dioxus".into(), - default_platform: "web".to_string(), + default_platform: Platform::Web, out_dir: Some(PathBuf::from("dist")), asset_dir: Some(PathBuf::from("public")), @@ -115,7 +115,7 @@ impl Default for DioxusConfig { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApplicationConfig { pub name: String, - pub default_platform: String, + pub default_platform: Platform, pub out_dir: Option, pub asset_dir: Option, diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs new file mode 100644 index 000000000..0a0c0f35f --- /dev/null +++ b/packages/cli/src/server/desktop/mod.rs @@ -0,0 +1,255 @@ +use crate::{ + builder, + serve::Serve, + server::{ + output::{print_console_info, PrettierOptions, WebServerInfo}, + setup_file_watcher, setup_file_watcher_hot_reload, BuildManager, + }, + BuildResult, CrateConfig, Result, +}; +use axum::{ + body::{Full, HttpBody}, + extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, + http::{ + header::{HeaderName, HeaderValue}, + Method, Response, StatusCode, + }, + response::IntoResponse, + routing::{get, get_service}, + Router, +}; +use axum_server::tls_rustls::RustlsConfig; +use cargo_metadata::diagnostic::Diagnostic; +use dioxus_core::Template; +use dioxus_hot_reload::HotReloadMsg; +use dioxus_html::HtmlCtx; +use dioxus_rsx::hot_reload::*; +use interprocess_docfix::local_socket::LocalSocketListener; +use notify::{RecommendedWatcher, Watcher}; +use std::{ + net::UdpSocket, + path::PathBuf, + process::{Command, Stdio}, + sync::{Arc, Mutex}, +}; +use tokio::sync::broadcast::{self, Sender}; +use tower::ServiceBuilder; +use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody}; +use tower_http::{ + cors::{Any, CorsLayer}, + ServiceBuilderExt, +}; + +#[cfg(feature = "plugin")] +use plugin::PluginManager; + +struct WsReloadState { + update: broadcast::Sender<()>, +} + +pub async fn startup(config: CrateConfig) -> Result<()> { + // ctrl-c shutdown checker + let _crate_config = config.clone(); + let _ = ctrlc::set_handler(move || { + #[cfg(feature = "plugin")] + let _ = PluginManager::on_serve_shutdown(&_crate_config); + std::process::exit(0); + }); + + match config.hot_reload { + true => serve_hot_reload(config).await?, + false => serve_default(config).await?, + } + + Ok(()) +} + +/// Start the server without hot reload +pub async fn serve_default(config: CrateConfig) -> Result<()> { + let first_build_result = start_desktop(&config)?; + + log::info!("🚀 Starting development server..."); + + // We got to own watcher so that it exists for the duration of serve + // Otherwise full reload won't work. + let _watcher = setup_file_watcher(crate::cfg::Platform::Desktop, &config, None, None).await?; + + // Print serve info + print_console_info( + &config, + PrettierOptions { + changed: vec![], + warnings: first_build_result.warnings, + elapsed_time: first_build_result.elapsed_time, + }, + None, + ); + + Ok(()) +} + +/// Start the server without hot reload + +/// Start dx serve with hot reload +pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { + let first_build_result = start_desktop(&config)?; + + println!("🚀 Starting development server..."); + + // Setup hot reload + let FileMapBuildResult { map, errors } = + FileMap::::create(config.crate_dir.clone()).unwrap(); + + println!("🚀 Starting development server..."); + + for err in errors { + log::error!("{}", err); + } + + let file_map = Arc::new(Mutex::new(map)); + let build_manager = Arc::new(BuildManager { + platform: crate::cfg::Platform::Web, + config: config.clone(), + reload_tx: None, + }); + + let (hot_reload_tx, mut hot_reload_rx) = broadcast::channel(100); + + // States + // Setup file watcher + // We got to own watcher so that it exists for the duration of serve + // Otherwise hot reload won't work. + let _watcher = setup_file_watcher_hot_reload( + &config, + hot_reload_tx, + file_map.clone(), + build_manager, + None, + ) + .await?; + + // Print serve info + print_console_info( + &config, + PrettierOptions { + changed: vec![], + warnings: first_build_result.warnings, + elapsed_time: first_build_result.elapsed_time, + }, + None, + ); + + #[cfg(target_os = "macos")] + { + // On unix, if you force quit the application, it can leave the file socket open + // This will cause the local socket listener to fail to open + // We check if the file socket is already open from an old session and then delete it + let paths = ["./dioxusin", "./@dioxusin"]; + for path in paths { + let path = PathBuf::from(path); + if path.exists() { + let _ = std::fs::remove_file(path); + } + } + } + + let channels = Arc::new(Mutex::new(Vec::new())); + + match LocalSocketListener::bind("@dioxusin") { + Ok(local_socket_stream) => { + let aborted = Arc::new(Mutex::new(false)); + + // listen for connections + std::thread::spawn({ + let file_map = file_map.clone(); + let channels = channels.clone(); + let aborted = aborted.clone(); + let _ = local_socket_stream.set_nonblocking(true); + move || { + loop { + if let Ok(mut connection) = local_socket_stream.accept() { + // send any templates than have changed before the socket connected + let templates: Vec<_> = { + file_map + .lock() + .unwrap() + .map + .values() + .filter_map(|(_, template_slot)| *template_slot) + .collect() + }; + for template in templates { + if !send_msg( + HotReloadMsg::UpdateTemplate(template), + &mut connection, + ) { + continue; + } + } + channels.lock().unwrap().push(connection); + println!("Connected to hot reloading 🚀"); + } + if *aborted.lock().unwrap() { + break; + } + } + } + }); + + // for channel in &mut *channels.lock().unwrap() { + // send_msg(HotReloadMsg::Shutdown, channel); + // } + + while let Ok(template) = hot_reload_rx.recv().await { + let channels = &mut *channels.lock().unwrap(); + let mut i = 0; + while i < channels.len() { + let channel = &mut channels[i]; + if send_msg(HotReloadMsg::UpdateTemplate(template), channel) { + i += 1; + } else { + channels.remove(i); + } + } + } + } + Err(error) => println!("failed to connect to hot reloading\n{error}"), + } + + Ok(()) +} + +fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { + if let Ok(msg) = serde_json::to_string(&msg) { + if channel.write_all(msg.as_bytes()).is_err() { + return false; + } + if channel.write_all(&[b'\n']).is_err() { + return false; + } + true + } else { + false + } +} + +pub fn start_desktop(config: &CrateConfig) -> Result { + // Run the desktop application + let result = crate::builder::build_desktop(config, true)?; + + match &config.executable { + crate::ExecutableType::Binary(name) + | crate::ExecutableType::Lib(name) + | crate::ExecutableType::Example(name) => { + let mut file = config.out_dir.join(name); + if cfg!(windows) { + file.set_extension("exe"); + } + Command::new(file.to_str().unwrap()) + .stdout(Stdio::inherit()) + .spawn()?; + } + } + + Ok(result) +} diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 3bd434005..389f5000b 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -1,4 +1,7 @@ -use crate::{builder, serve::Serve, BuildResult, CrateConfig, Result}; +use crate::{ + builder, cfg::Platform, serve::Serve, server::desktop::start_desktop, BuildResult, CrateConfig, + Result, +}; use axum::{ body::{Full, HttpBody}, extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, @@ -19,7 +22,7 @@ use notify::{RecommendedWatcher, Watcher}; use std::{ net::UdpSocket, path::PathBuf, - process::Command, + process::{Command, Stdio}, sync::{Arc, Mutex}, }; use tokio::sync::broadcast::{self, Sender}; @@ -30,416 +33,20 @@ use tower_http::{ ServiceBuilderExt, }; -#[cfg(feature = "plugin")] -use plugin::PluginManager; - -mod proxy; - -mod hot_reload; -use hot_reload::*; - mod output; use output::*; - -pub struct BuildManager { - config: CrateConfig, - reload_tx: broadcast::Sender<()>, -} - -impl BuildManager { - fn rebuild(&self) -> Result { - log::info!("🪁 Rebuild project"); - let result = builder::build(&self.config, true)?; - // change the websocket reload state to true; - // the page will auto-reload. - if self - .config - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&self.config); - } - let _ = self.reload_tx.send(()); - Ok(result) - } -} - -struct WsReloadState { - update: broadcast::Sender<()>, -} - -pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Result<()> { - // ctrl-c shutdown checker - let _crate_config = config.clone(); - let _ = ctrlc::set_handler(move || { - #[cfg(feature = "plugin")] - let _ = PluginManager::on_serve_shutdown(&_crate_config); - std::process::exit(0); - }); - - let ip = get_ip().unwrap_or(String::from("0.0.0.0")); - - match config.hot_reload { - true => serve_hot_reload(ip, port, config, start_browser).await?, - false => serve_default(ip, port, config, start_browser).await?, - } - - Ok(()) -} - -/// Start the server without hot reload -pub async fn serve_default( - ip: String, - port: u16, - config: CrateConfig, - start_browser: bool, -) -> Result<()> { - let first_build_result = crate::builder::build(&config, false)?; - - log::info!("🚀 Starting development server..."); - - // WS Reload Watching - let (reload_tx, _) = broadcast::channel(100); - - // We got to own watcher so that it exists for the duration of serve - // Otherwise full reload won't work. - let _watcher = setup_file_watcher(&config, port, ip.clone(), reload_tx.clone()).await?; - - let ws_reload_state = Arc::new(WsReloadState { - update: reload_tx.clone(), - }); - - // HTTPS - // Before console info so it can stop if mkcert isn't installed or fails - let rustls_config = get_rustls(&config).await?; - - // Print serve info - print_console_info( - &ip, - port, - &config, - PrettierOptions { - changed: vec![], - warnings: first_build_result.warnings, - elapsed_time: first_build_result.elapsed_time, - }, - ); - - // Router - let router = setup_router(config, ws_reload_state, None).await?; - - // Start server - start_server(port, router, start_browser, rustls_config).await?; - - Ok(()) -} - -/// Start dx serve with hot reload -pub async fn serve_hot_reload( - ip: String, - port: u16, - config: CrateConfig, - start_browser: bool, -) -> Result<()> { - let first_build_result = crate::builder::build(&config, false)?; - - log::info!("🚀 Starting development server..."); - - // Setup hot reload - let (reload_tx, _) = broadcast::channel(100); - let FileMapBuildResult { map, errors } = - FileMap::::create(config.crate_dir.clone()).unwrap(); - - for err in errors { - log::error!("{}", err); - } - - let file_map = Arc::new(Mutex::new(map)); - let build_manager = Arc::new(BuildManager { - config: config.clone(), - reload_tx: reload_tx.clone(), - }); - - let hot_reload_tx = broadcast::channel(100).0; - - // States - let hot_reload_state = Arc::new(HotReloadState { - messages: hot_reload_tx.clone(), - build_manager: build_manager.clone(), - file_map: file_map.clone(), - watcher_config: config.clone(), - }); - - let ws_reload_state = Arc::new(WsReloadState { - update: reload_tx.clone(), - }); - - // Setup file watcher - // We got to own watcher so that it exists for the duration of serve - // Otherwise hot reload won't work. - let _watcher = setup_file_watcher_hot_reload( - &config, - port, - ip.clone(), - hot_reload_tx, - file_map, - build_manager, - ) - .await?; - - // HTTPS - // Before console info so it can stop if mkcert isn't installed or fails - let rustls_config = get_rustls(&config).await?; - - // Print serve info - print_console_info( - &ip, - port, - &config, - PrettierOptions { - changed: vec![], - warnings: first_build_result.warnings, - elapsed_time: first_build_result.elapsed_time, - }, - ); - - // Router - let router = setup_router(config, ws_reload_state, Some(hot_reload_state)).await?; - - // Start server - start_server(port, router, start_browser, rustls_config).await?; - - Ok(()) -} - -const DEFAULT_KEY_PATH: &str = "ssl/key.pem"; -const DEFAULT_CERT_PATH: &str = "ssl/cert.pem"; - -/// Returns an enum of rustls config and a bool if mkcert isn't installed -async fn get_rustls(config: &CrateConfig) -> Result> { - let web_config = &config.dioxus_config.web.https; - if web_config.enabled != Some(true) { - return Ok(None); - } - - let (cert_path, key_path) = match web_config.mkcert { - // mkcert, use it - Some(true) => { - // Get paths to store certs, otherwise use ssl/item.pem - let key_path = web_config - .key_path - .clone() - .unwrap_or(DEFAULT_KEY_PATH.to_string()); - - let cert_path = web_config - .cert_path - .clone() - .unwrap_or(DEFAULT_CERT_PATH.to_string()); - - // Create ssl directory if using defaults - if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH { - _ = fs::create_dir("ssl"); - } - - let cmd = Command::new("mkcert") - .args([ - "-install", - "-key-file", - &key_path, - "-cert-file", - &cert_path, - "localhost", - "::1", - "127.0.0.1", - ]) - .spawn(); - - match cmd { - Err(e) => { - match e.kind() { - io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."), - e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()), - }; - return Err("failed to generate mkcert certificates".into()); - } - Ok(mut cmd) => { - cmd.wait()?; - } - } - - (cert_path, key_path) - } - // not mkcert - Some(false) => { - // get paths to cert & key - if let (Some(key), Some(cert)) = - (web_config.key_path.clone(), web_config.cert_path.clone()) - { - (cert, key) - } else { - // missing cert or key - return Err("https is enabled but cert or key path is missing".into()); - } - } - // other - _ => return Ok(None), - }; - - Ok(Some( - RustlsConfig::from_pem_file(cert_path, key_path).await?, - )) -} - -/// Sets up and returns a router -async fn setup_router( - config: CrateConfig, - ws_reload: Arc, - hot_reload: Option>, -) -> Result { - // Setup cors - let cors = CorsLayer::new() - // allow `GET` and `POST` when accessing the resource - .allow_methods([Method::GET, Method::POST]) - // allow requests from any origin - .allow_origin(Any) - .allow_headers(Any); - - let (coep, coop) = if config.cross_origin_policy { - ( - HeaderValue::from_static("require-corp"), - HeaderValue::from_static("same-origin"), - ) - } else { - ( - HeaderValue::from_static("unsafe-none"), - HeaderValue::from_static("unsafe-none"), - ) - }; - - // Create file service - let file_service_config = config.clone(); - let file_service = ServiceBuilder::new() - .override_response_header( - HeaderName::from_static("cross-origin-embedder-policy"), - coep, - ) - .override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop) - .and_then( - move |response: Response| async move { - let response = if file_service_config - .dioxus_config - .web - .watcher - .index_on_404 - .unwrap_or(false) - && response.status() == StatusCode::NOT_FOUND - { - let body = Full::from( - // TODO: Cache/memoize this. - std::fs::read_to_string( - file_service_config - .crate_dir - .join(file_service_config.out_dir) - .join("index.html"), - ) - .ok() - .unwrap(), - ) - .map_err(|err| match err {}) - .boxed(); - Response::builder() - .status(StatusCode::OK) - .body(body) - .unwrap() - } else { - response.map(|body| body.boxed()) - }; - Ok(response) - }, - ) - .service(ServeDir::new(config.crate_dir.join(&config.out_dir))); - - // Setup websocket - let mut router = Router::new().route("/_dioxus/ws", get(ws_handler)); - - // Setup proxy - for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() { - router = proxy::add_proxy(router, &proxy_config)?; - } - - // Route file service - router = router.fallback(get_service(file_service).handle_error( - |error: std::io::Error| async move { - ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Unhandled internal error: {}", error), - ) - }, - )); - - // Setup routes - router = router - .route("/_dioxus/hot_reload", get(hot_reload_handler)) - .layer(cors) - .layer(Extension(ws_reload)); - - if let Some(hot_reload) = hot_reload { - router = router.layer(Extension(hot_reload)) - } - - Ok(router) -} - -/// Starts dx serve with no hot reload -async fn start_server( - port: u16, - router: Router, - start_browser: bool, - rustls: Option, -) -> Result<()> { - // If plugins, call on_serve_start event - #[cfg(feature = "plugin")] - PluginManager::on_serve_start(&config)?; - - // Parse address - let addr = format!("0.0.0.0:{}", port).parse().unwrap(); - - // Open the browser - if start_browser { - match rustls { - Some(_) => _ = open::that(format!("https://{}", addr)), - None => _ = open::that(format!("http://{}", addr)), - } - } - - // Start the server with or without rustls - match rustls { - Some(rustls) => { - axum_server::bind_rustls(addr, rustls) - .serve(router.into_make_service()) - .await? - } - None => { - axum::Server::bind(&addr) - .serve(router.into_make_service()) - .await? - } - } - - Ok(()) -} +pub mod desktop; +pub mod web; /// Sets up a file watcher async fn setup_file_watcher( + platform: Platform, config: &CrateConfig, - port: u16, - watcher_ip: String, - reload_tx: Sender<()>, + reload_tx: Option>, + web_info: Option, ) -> Result { let build_manager = BuildManager { + platform, config: config.clone(), reload_tx, }; @@ -466,14 +73,13 @@ async fn setup_file_watcher( #[allow(clippy::redundant_clone)] print_console_info( - &watcher_ip, - port, &config, PrettierOptions { changed: e.paths.clone(), warnings: res.warnings, elapsed_time: res.elapsed_time, }, + web_info.clone(), ); #[cfg(feature = "plugin")] @@ -504,11 +110,10 @@ async fn setup_file_watcher( /// Sets up a file watcher with hot reload async fn setup_file_watcher_hot_reload( config: &CrateConfig, - port: u16, - watcher_ip: String, hot_reload_tx: Sender>, file_map: Arc>>, build_manager: Arc, + web_info: Option, ) -> Result { // file watcher: check file change let allow_watch_path = config @@ -536,14 +141,13 @@ async fn setup_file_watcher_hot_reload( match build_manager.rebuild() { Ok(res) => { print_console_info( - &watcher_ip, - port, &config, PrettierOptions { changed: evt.paths, warnings: res.warnings, elapsed_time: res.elapsed_time, }, + web_info.clone(), ); } Err(err) => { @@ -563,14 +167,13 @@ async fn setup_file_watcher_hot_reload( match build_manager.rebuild() { Ok(res) => { print_console_info( - &watcher_ip, - port, &config, PrettierOptions { changed: evt.paths, warnings: res.warnings, elapsed_time: res.elapsed_time, }, + web_info.clone(), ); } Err(err) => { @@ -607,49 +210,34 @@ async fn setup_file_watcher_hot_reload( Ok(watcher) } -/// Get the network ip -fn get_ip() -> Option { - let socket = match UdpSocket::bind("0.0.0.0:0") { - Ok(s) => s, - Err(_) => return None, - }; +pub struct BuildManager { + platform: Platform, + config: CrateConfig, + reload_tx: Option>, +} - match socket.connect("8.8.8.8:80") { - Ok(()) => (), - Err(_) => return None, - }; - - match socket.local_addr() { - Ok(addr) => Some(addr.ip().to_string()), - Err(_) => None, +impl BuildManager { + fn rebuild(&self) -> Result { + log::info!("🪁 Rebuild project"); + match self.platform { + Platform::Web => { + let result = builder::build(&self.config, true)?; + // change the websocket reload state to true; + // the page will auto-reload. + if self + .config + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&self.config); + } + let _ = self.reload_tx.as_ref().map(|tx| tx.send(())); + Ok(result) + } + Platform::Desktop => start_desktop(&self.config), + } } } - -/// Handle websockets -async fn ws_handler( - ws: WebSocketUpgrade, - _: Option>, - Extension(state): Extension>, -) -> impl IntoResponse { - ws.on_upgrade(|mut socket| async move { - let mut rx = state.update.subscribe(); - let reload_watcher = tokio::spawn(async move { - loop { - rx.recv().await.unwrap(); - // ignore the error - if socket - .send(Message::Text(String::from("reload"))) - .await - .is_err() - { - break; - } - - // flush the errors after recompling - rx = rx.resubscribe(); - } - }); - - reload_watcher.await.unwrap(); - }) -} diff --git a/packages/cli/src/server/output.rs b/packages/cli/src/server/output.rs index 4c6e1f3b2..ce0f55064 100644 --- a/packages/cli/src/server/output.rs +++ b/packages/cli/src/server/output.rs @@ -11,7 +11,17 @@ pub struct PrettierOptions { pub elapsed_time: u128, } -pub fn print_console_info(ip: &String, port: u16, config: &CrateConfig, options: PrettierOptions) { +#[derive(Debug, Clone)] +pub struct WebServerInfo { + pub ip: String, + pub port: u16, +} + +pub fn print_console_info( + config: &CrateConfig, + options: PrettierOptions, + web_info: Option, +) { if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") { "cls" } else { @@ -70,26 +80,28 @@ pub fn print_console_info(ip: &String, port: u16, config: &CrateConfig, options: ); } - if config.dioxus_config.web.https.enabled == Some(true) { - println!( - "\t> Local : {}", - format!("https://localhost:{}/", port).blue() - ); - println!( - "\t> Network : {}", - format!("https://{}:{}/", ip, port).blue() - ); - println!("\t> HTTPS : {}", "Enabled".to_string().green()); - } else { - println!( - "\t> Local : {}", - format!("http://localhost:{}/", port).blue() - ); - println!( - "\t> Network : {}", - format!("http://{}:{}/", ip, port).blue() - ); - println!("\t> HTTPS : {}", "Disabled".to_string().red()); + if let Some(WebServerInfo { ip, port }) = web_info { + if config.dioxus_config.web.https.enabled == Some(true) { + println!( + "\t> Local : {}", + format!("https://localhost:{}/", port).blue() + ); + println!( + "\t> Network : {}", + format!("https://{}:{}/", ip, port).blue() + ); + println!("\t> HTTPS : {}", "Enabled".to_string().green()); + } else { + println!( + "\t> Local : {}", + format!("http://localhost:{}/", port).blue() + ); + println!( + "\t> Network : {}", + format!("http://{}:{}/", ip, port).blue() + ); + println!("\t> HTTPS : {}", "Disabled".to_string().red()); + } } println!(); println!("\t> Profile : {}", profile.green()); diff --git a/packages/cli/src/server/hot_reload.rs b/packages/cli/src/server/web/hot_reload.rs similarity index 100% rename from packages/cli/src/server/hot_reload.rs rename to packages/cli/src/server/web/hot_reload.rs diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs new file mode 100644 index 000000000..c7661f028 --- /dev/null +++ b/packages/cli/src/server/web/mod.rs @@ -0,0 +1,475 @@ +use crate::{ + builder, + serve::Serve, + server::{ + output::{print_console_info, PrettierOptions, WebServerInfo}, + setup_file_watcher, setup_file_watcher_hot_reload, BuildManager, + }, + BuildResult, CrateConfig, Result, +}; +use axum::{ + body::{Full, HttpBody}, + extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, + http::{ + header::{HeaderName, HeaderValue}, + Method, Response, StatusCode, + }, + response::IntoResponse, + routing::{get, get_service}, + Router, +}; +use axum_server::tls_rustls::RustlsConfig; +use cargo_metadata::diagnostic::Diagnostic; +use dioxus_core::Template; +use dioxus_html::HtmlCtx; +use dioxus_rsx::hot_reload::*; +use notify::{RecommendedWatcher, Watcher}; +use std::{ + net::UdpSocket, + path::PathBuf, + process::Command, + sync::{Arc, Mutex}, +}; +use tokio::sync::broadcast::{self, Sender}; +use tower::ServiceBuilder; +use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody}; +use tower_http::{ + cors::{Any, CorsLayer}, + ServiceBuilderExt, +}; + +#[cfg(feature = "plugin")] +use plugin::PluginManager; + +mod proxy; + +mod hot_reload; +use hot_reload::*; + +struct WsReloadState { + update: broadcast::Sender<()>, +} + +pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Result<()> { + // ctrl-c shutdown checker + let _crate_config = config.clone(); + let _ = ctrlc::set_handler(move || { + #[cfg(feature = "plugin")] + let _ = PluginManager::on_serve_shutdown(&_crate_config); + std::process::exit(0); + }); + + let ip = get_ip().unwrap_or(String::from("0.0.0.0")); + + match config.hot_reload { + true => serve_hot_reload(ip, port, config, start_browser).await?, + false => serve_default(ip, port, config, start_browser).await?, + } + + Ok(()) +} + +/// Start the server without hot reload +pub async fn serve_default( + ip: String, + port: u16, + config: CrateConfig, + start_browser: bool, +) -> Result<()> { + let first_build_result = crate::builder::build(&config, false)?; + + log::info!("🚀 Starting development server..."); + + // WS Reload Watching + let (reload_tx, _) = broadcast::channel(100); + + // We got to own watcher so that it exists for the duration of serve + // Otherwise full reload won't work. + let _watcher = setup_file_watcher( + crate::cfg::Platform::Web, + &config, + Some(reload_tx.clone()), + Some(WebServerInfo { + ip: ip.clone(), + port, + }), + ) + .await?; + + let ws_reload_state = Arc::new(WsReloadState { + update: reload_tx.clone(), + }); + + // HTTPS + // Before console info so it can stop if mkcert isn't installed or fails + let rustls_config = get_rustls(&config).await?; + + // Print serve info + print_console_info( + &config, + PrettierOptions { + changed: vec![], + warnings: first_build_result.warnings, + elapsed_time: first_build_result.elapsed_time, + }, + Some(crate::server::output::WebServerInfo { + ip: ip.clone(), + port, + }), + ); + + // Router + let router = setup_router(config, ws_reload_state, None).await?; + + // Start server + start_server(port, router, start_browser, rustls_config).await?; + + Ok(()) +} + +/// Start dx serve with hot reload +pub async fn serve_hot_reload( + ip: String, + port: u16, + config: CrateConfig, + start_browser: bool, +) -> Result<()> { + let first_build_result = crate::builder::build(&config, false)?; + + log::info!("🚀 Starting development server..."); + + // Setup hot reload + let (reload_tx, _) = broadcast::channel(100); + let FileMapBuildResult { map, errors } = + FileMap::::create(config.crate_dir.clone()).unwrap(); + + for err in errors { + log::error!("{}", err); + } + + let file_map = Arc::new(Mutex::new(map)); + let build_manager = Arc::new(BuildManager { + platform: crate::cfg::Platform::Web, + config: config.clone(), + reload_tx: Some(reload_tx.clone()), + }); + + let hot_reload_tx = broadcast::channel(100).0; + + // States + let hot_reload_state = Arc::new(HotReloadState { + messages: hot_reload_tx.clone(), + build_manager: build_manager.clone(), + file_map: file_map.clone(), + watcher_config: config.clone(), + }); + + let ws_reload_state = Arc::new(WsReloadState { + update: reload_tx.clone(), + }); + + // Setup file watcher + // We got to own watcher so that it exists for the duration of serve + // Otherwise hot reload won't work. + let _watcher = setup_file_watcher_hot_reload( + &config, + hot_reload_tx, + file_map, + build_manager, + Some(WebServerInfo { + ip: ip.clone(), + port, + }), + ) + .await?; + + // HTTPS + // Before console info so it can stop if mkcert isn't installed or fails + let rustls_config = get_rustls(&config).await?; + + // Print serve info + print_console_info( + &config, + PrettierOptions { + changed: vec![], + warnings: first_build_result.warnings, + elapsed_time: first_build_result.elapsed_time, + }, + Some(WebServerInfo { + ip: ip.clone(), + port, + }), + ); + + // Router + let router = setup_router(config, ws_reload_state, Some(hot_reload_state)).await?; + + // Start server + start_server(port, router, start_browser, rustls_config).await?; + + Ok(()) +} + +const DEFAULT_KEY_PATH: &str = "ssl/key.pem"; +const DEFAULT_CERT_PATH: &str = "ssl/cert.pem"; + +/// Returns an enum of rustls config and a bool if mkcert isn't installed +async fn get_rustls(config: &CrateConfig) -> Result> { + let web_config = &config.dioxus_config.web.https; + if web_config.enabled != Some(true) { + return Ok(None); + } + + let (cert_path, key_path) = match web_config.mkcert { + // mkcert, use it + Some(true) => { + // Get paths to store certs, otherwise use ssl/item.pem + let key_path = web_config + .key_path + .clone() + .unwrap_or(DEFAULT_KEY_PATH.to_string()); + + let cert_path = web_config + .cert_path + .clone() + .unwrap_or(DEFAULT_CERT_PATH.to_string()); + + // Create ssl directory if using defaults + if key_path == DEFAULT_KEY_PATH && cert_path == DEFAULT_CERT_PATH { + _ = fs::create_dir("ssl"); + } + + let cmd = Command::new("mkcert") + .args([ + "-install", + "-key-file", + &key_path, + "-cert-file", + &cert_path, + "localhost", + "::1", + "127.0.0.1", + ]) + .spawn(); + + match cmd { + Err(e) => { + match e.kind() { + io::ErrorKind::NotFound => log::error!("mkcert is not installed. See https://github.com/FiloSottile/mkcert#installation for installation instructions."), + e => log::error!("an error occured while generating mkcert certificates: {}", e.to_string()), + }; + return Err("failed to generate mkcert certificates".into()); + } + Ok(mut cmd) => { + cmd.wait()?; + } + } + + (cert_path, key_path) + } + // not mkcert + Some(false) => { + // get paths to cert & key + if let (Some(key), Some(cert)) = + (web_config.key_path.clone(), web_config.cert_path.clone()) + { + (cert, key) + } else { + // missing cert or key + return Err("https is enabled but cert or key path is missing".into()); + } + } + // other + _ => return Ok(None), + }; + + Ok(Some( + RustlsConfig::from_pem_file(cert_path, key_path).await?, + )) +} + +/// Sets up and returns a router +async fn setup_router( + config: CrateConfig, + ws_reload: Arc, + hot_reload: Option>, +) -> Result { + // Setup cors + let cors = CorsLayer::new() + // allow `GET` and `POST` when accessing the resource + .allow_methods([Method::GET, Method::POST]) + // allow requests from any origin + .allow_origin(Any) + .allow_headers(Any); + + let (coep, coop) = if config.cross_origin_policy { + ( + HeaderValue::from_static("require-corp"), + HeaderValue::from_static("same-origin"), + ) + } else { + ( + HeaderValue::from_static("unsafe-none"), + HeaderValue::from_static("unsafe-none"), + ) + }; + + // Create file service + let file_service_config = config.clone(); + let file_service = ServiceBuilder::new() + .override_response_header( + HeaderName::from_static("cross-origin-embedder-policy"), + coep, + ) + .override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop) + .and_then( + move |response: Response| async move { + let response = if file_service_config + .dioxus_config + .web + .watcher + .index_on_404 + .unwrap_or(false) + && response.status() == StatusCode::NOT_FOUND + { + let body = Full::from( + // TODO: Cache/memoize this. + std::fs::read_to_string( + file_service_config + .crate_dir + .join(file_service_config.out_dir) + .join("index.html"), + ) + .ok() + .unwrap(), + ) + .map_err(|err| match err {}) + .boxed(); + Response::builder() + .status(StatusCode::OK) + .body(body) + .unwrap() + } else { + response.map(|body| body.boxed()) + }; + Ok(response) + }, + ) + .service(ServeDir::new(config.crate_dir.join(&config.out_dir))); + + // Setup websocket + let mut router = Router::new().route("/_dioxus/ws", get(ws_handler)); + + // Setup proxy + for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() { + router = proxy::add_proxy(router, &proxy_config)?; + } + + // Route file service + router = router.fallback(get_service(file_service).handle_error( + |error: std::io::Error| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) + }, + )); + + // Setup routes + router = router + .route("/_dioxus/hot_reload", get(hot_reload_handler)) + .layer(cors) + .layer(Extension(ws_reload)); + + if let Some(hot_reload) = hot_reload { + router = router.layer(Extension(hot_reload)) + } + + Ok(router) +} + +/// Starts dx serve with no hot reload +async fn start_server( + port: u16, + router: Router, + start_browser: bool, + rustls: Option, +) -> Result<()> { + // If plugins, call on_serve_start event + #[cfg(feature = "plugin")] + PluginManager::on_serve_start(&config)?; + + // Parse address + let addr = format!("0.0.0.0:{}", port).parse().unwrap(); + + // Open the browser + if start_browser { + match rustls { + Some(_) => _ = open::that(format!("https://{}", addr)), + None => _ = open::that(format!("http://{}", addr)), + } + } + + // Start the server with or without rustls + match rustls { + Some(rustls) => { + axum_server::bind_rustls(addr, rustls) + .serve(router.into_make_service()) + .await? + } + None => { + axum::Server::bind(&addr) + .serve(router.into_make_service()) + .await? + } + } + + Ok(()) +} + +/// Get the network ip +fn get_ip() -> Option { + let socket = match UdpSocket::bind("0.0.0.0:0") { + Ok(s) => s, + Err(_) => return None, + }; + + match socket.connect("8.8.8.8:80") { + Ok(()) => (), + Err(_) => return None, + }; + + match socket.local_addr() { + Ok(addr) => Some(addr.ip().to_string()), + Err(_) => None, + } +} + +/// Handle websockets +async fn ws_handler( + ws: WebSocketUpgrade, + _: Option>, + Extension(state): Extension>, +) -> impl IntoResponse { + ws.on_upgrade(|mut socket| async move { + let mut rx = state.update.subscribe(); + let reload_watcher = tokio::spawn(async move { + loop { + rx.recv().await.unwrap(); + // ignore the error + if socket + .send(Message::Text(String::from("reload"))) + .await + .is_err() + { + break; + } + + // flush the errors after recompling + rx = rx.resubscribe(); + } + }); + + reload_watcher.await.unwrap(); + }) +} diff --git a/packages/cli/src/server/proxy.rs b/packages/cli/src/server/web/proxy.rs similarity index 100% rename from packages/cli/src/server/proxy.rs rename to packages/cli/src/server/web/proxy.rs From 2c1c147828e6a17bcd368021098c45aa3ece7541 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 18 Jul 2023 11:55:56 -0700 Subject: [PATCH 02/31] fix desktop hot reloading --- examples/simple_desktop.rs | 2 +- packages/cli/src/builder.rs | 1 + packages/cli/src/cli/serve.rs | 16 +----- packages/cli/src/server/desktop/mod.rs | 38 ++++++++----- packages/cli/src/server/mod.rs | 53 +++---------------- packages/cli/src/server/web/hot_reload.rs | 2 - packages/cli/src/server/web/mod.rs | 38 +++++++++---- .../fullstack/src/adapters/salvo_adapter.rs | 2 +- packages/html/src/events/form.rs | 7 ++- packages/rink/src/widgets/button.rs | 16 ++++-- packages/rink/src/widgets/checkbox.rs | 20 +++++-- packages/rink/src/widgets/input.rs | 4 +- packages/rink/src/widgets/slider.rs | 20 +++++-- packages/rink/src/widgets/text_like.rs | 24 ++++++--- packages/router/examples/simple_routes.rs | 2 +- 15 files changed, 130 insertions(+), 115 deletions(-) diff --git a/examples/simple_desktop.rs b/examples/simple_desktop.rs index 74bfb28c6..6c8bf8d72 100644 --- a/examples/simple_desktop.rs +++ b/examples/simple_desktop.rs @@ -48,7 +48,7 @@ fn BlogList(cx: Scope) -> Element { fn BlogPost(cx: Scope) -> Element { let Some(id) = use_route(cx).segment("id") else { - return cx.render(rsx! { div { "No blog post id" } }) + return cx.render(rsx! { div { "No blog post id" } }); }; log::debug!("rendering blog post {}", id); diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 57794b728..1cd6d787e 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -364,6 +364,7 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result { - crate::builder::build_desktop(&crate_config, true)?; - - match &crate_config.executable { - crate::ExecutableType::Binary(name) - | crate::ExecutableType::Lib(name) - | crate::ExecutableType::Example(name) => { - let mut file = crate_config.out_dir.join(name); - if cfg!(windows) { - file.set_extension("exe"); - } - Command::new(file.to_str().unwrap()) - .stdout(Stdio::inherit()) - .output()?; - } - } + server::desktop::startup(crate_config.clone()).await?; } } Ok(()) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 0a0c0f35f..6a2c57539 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -3,7 +3,7 @@ use crate::{ serve::Serve, server::{ output::{print_console_info, PrettierOptions, WebServerInfo}, - setup_file_watcher, setup_file_watcher_hot_reload, BuildManager, + setup_file_watcher, setup_file_watcher_hot_reload, }, BuildResult, CrateConfig, Result, }; @@ -72,7 +72,15 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> { // We got to own watcher so that it exists for the duration of serve // Otherwise full reload won't work. - let _watcher = setup_file_watcher(crate::cfg::Platform::Desktop, &config, None, None).await?; + let _watcher = setup_file_watcher( + { + let config = config.clone(); + move || start_desktop(&config) + }, + &config, + None, + ) + .await?; // Print serve info print_console_info( @@ -107,15 +115,13 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { } let file_map = Arc::new(Mutex::new(map)); - let build_manager = Arc::new(BuildManager { - platform: crate::cfg::Platform::Web, - config: config.clone(), - reload_tx: None, - }); let (hot_reload_tx, mut hot_reload_rx) = broadcast::channel(100); // States + // The open interprocess sockets + let channels = Arc::new(Mutex::new(Vec::new())); + // Setup file watcher // We got to own watcher so that it exists for the duration of serve // Otherwise hot reload won't work. @@ -123,7 +129,17 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { &config, hot_reload_tx, file_map.clone(), - build_manager, + { + let config = config.clone(); + + let channels = channels.clone(); + move || { + for channel in &mut *channels.lock().unwrap() { + send_msg(HotReloadMsg::Shutdown, channel); + } + start_desktop(&config) + } + }, None, ) .await?; @@ -153,8 +169,6 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { } } - let channels = Arc::new(Mutex::new(Vec::new())); - match LocalSocketListener::bind("@dioxusin") { Ok(local_socket_stream) => { let aborted = Arc::new(Mutex::new(false)); @@ -196,10 +210,6 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { } }); - // for channel in &mut *channels.lock().unwrap() { - // send_msg(HotReloadMsg::Shutdown, channel); - // } - while let Ok(template) = hot_reload_rx.recv().await { let channels = &mut *channels.lock().unwrap(); let mut i = 0; diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 389f5000b..8c421f269 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -39,18 +39,11 @@ pub mod desktop; pub mod web; /// Sets up a file watcher -async fn setup_file_watcher( - platform: Platform, +async fn setup_file_watcher Result + Send + 'static>( + build_with: F, config: &CrateConfig, - reload_tx: Option>, web_info: Option, ) -> Result { - let build_manager = BuildManager { - platform, - config: config.clone(), - reload_tx, - }; - let mut last_update_time = chrono::Local::now().timestamp(); // file watcher: check file change @@ -67,7 +60,7 @@ async fn setup_file_watcher( let config = watcher_config.clone(); if let Ok(e) = info { if chrono::Local::now().timestamp() > last_update_time { - match build_manager.rebuild() { + match build_with() { Ok(res) => { last_update_time = chrono::Local::now().timestamp(); @@ -108,11 +101,11 @@ async fn setup_file_watcher( // Todo: reduce duplication and merge with setup_file_watcher() /// Sets up a file watcher with hot reload -async fn setup_file_watcher_hot_reload( +async fn setup_file_watcher_hot_reload Result + Send + 'static>( config: &CrateConfig, hot_reload_tx: Sender>, file_map: Arc>>, - build_manager: Arc, + build_with: F, web_info: Option, ) -> Result { // file watcher: check file change @@ -138,7 +131,7 @@ async fn setup_file_watcher_hot_reload( for path in evt.paths.clone() { // if this is not a rust file, rebuild the whole project if path.extension().and_then(|p| p.to_str()) != Some("rs") { - match build_manager.rebuild() { + match build_with() { Ok(res) => { print_console_info( &config, @@ -164,7 +157,7 @@ async fn setup_file_watcher_hot_reload( messages.extend(msgs); } Ok(UpdateResult::NeedsRebuild) => { - match build_manager.rebuild() { + match build_with() { Ok(res) => { print_console_info( &config, @@ -209,35 +202,3 @@ async fn setup_file_watcher_hot_reload( Ok(watcher) } - -pub struct BuildManager { - platform: Platform, - config: CrateConfig, - reload_tx: Option>, -} - -impl BuildManager { - fn rebuild(&self) -> Result { - log::info!("🪁 Rebuild project"); - match self.platform { - Platform::Web => { - let result = builder::build(&self.config, true)?; - // change the websocket reload state to true; - // the page will auto-reload. - if self - .config - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&self.config); - } - let _ = self.reload_tx.as_ref().map(|tx| tx.send(())); - Ok(result) - } - Platform::Desktop => start_desktop(&self.config), - } - } -} diff --git a/packages/cli/src/server/web/hot_reload.rs b/packages/cli/src/server/web/hot_reload.rs index 167e712af..49cce38d4 100644 --- a/packages/cli/src/server/web/hot_reload.rs +++ b/packages/cli/src/server/web/hot_reload.rs @@ -10,12 +10,10 @@ use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::FileMap; use tokio::sync::broadcast; -use super::BuildManager; use crate::CrateConfig; pub struct HotReloadState { pub messages: broadcast::Sender>, - pub build_manager: Arc, pub file_map: Arc>>, pub watcher_config: CrateConfig, } diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs index c7661f028..e6a429788 100644 --- a/packages/cli/src/server/web/mod.rs +++ b/packages/cli/src/server/web/mod.rs @@ -3,7 +3,7 @@ use crate::{ serve::Serve, server::{ output::{print_console_info, PrettierOptions, WebServerInfo}, - setup_file_watcher, setup_file_watcher_hot_reload, BuildManager, + setup_file_watcher, setup_file_watcher_hot_reload, }, BuildResult, CrateConfig, Result, }; @@ -86,9 +86,12 @@ pub async fn serve_default( // We got to own watcher so that it exists for the duration of serve // Otherwise full reload won't work. let _watcher = setup_file_watcher( - crate::cfg::Platform::Web, + { + let config = config.clone(); + let reload_tx = reload_tx.clone(); + move || build(&config, &reload_tx) + }, &config, - Some(reload_tx.clone()), Some(WebServerInfo { ip: ip.clone(), port, @@ -148,18 +151,12 @@ pub async fn serve_hot_reload( } let file_map = Arc::new(Mutex::new(map)); - let build_manager = Arc::new(BuildManager { - platform: crate::cfg::Platform::Web, - config: config.clone(), - reload_tx: Some(reload_tx.clone()), - }); let hot_reload_tx = broadcast::channel(100).0; // States let hot_reload_state = Arc::new(HotReloadState { messages: hot_reload_tx.clone(), - build_manager: build_manager.clone(), file_map: file_map.clone(), watcher_config: config.clone(), }); @@ -175,7 +172,11 @@ pub async fn serve_hot_reload( &config, hot_reload_tx, file_map, - build_manager, + { + let config = config.clone(); + let reload_tx = reload_tx.clone(); + move || build(&config, &reload_tx) + }, Some(WebServerInfo { ip: ip.clone(), port, @@ -473,3 +474,20 @@ async fn ws_handler( reload_watcher.await.unwrap(); }) } + +fn build(config: &CrateConfig, reload_tx: &Sender<()>) -> Result { + let result = builder::build(&config, true)?; + // change the websocket reload state to true; + // the page will auto-reload. + if config + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&config); + } + let _ = reload_tx.send(()); + Ok(result) +} diff --git a/packages/fullstack/src/adapters/salvo_adapter.rs b/packages/fullstack/src/adapters/salvo_adapter.rs index 59b7c36ea..a31c1ea67 100644 --- a/packages/fullstack/src/adapters/salvo_adapter.rs +++ b/packages/fullstack/src/adapters/salvo_adapter.rs @@ -378,7 +378,7 @@ impl ServerFnHandler { .to_vec() .into(); let body = hyper::body::to_bytes(req.body_mut().unwrap()).await; - let Ok(body)=body else { + let Ok(body) = body else { handle_error(body.err().unwrap(), res); return; }; diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 113c765c5..1fe59a640 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -56,10 +56,9 @@ where { use serde::Deserialize; - let Ok(file_engine) = - SerializedFileEngine::deserialize(deserializer) else{ - return Ok(None); - }; + let Ok(file_engine) = SerializedFileEngine::deserialize(deserializer) else { + return Ok(None); + }; let file_engine = std::sync::Arc::new(file_engine); Ok(Some(file_engine)) diff --git a/packages/rink/src/widgets/button.rs b/packages/rink/src/widgets/button.rs index e4d3754f1..9018a3e9a 100644 --- a/packages/rink/src/widgets/button.rs +++ b/packages/rink/src/widgets/button.rs @@ -82,7 +82,9 @@ impl Button { fn write_value(&self, rdom: &mut RealDom) { if let Some(mut text) = rdom.get_mut(self.text_id) { let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Text(mut text) = node_type else { + panic!("input must be an element") + }; *text.text_mut() = self.value.clone(); } } @@ -111,7 +113,9 @@ impl CustomElement for Button { fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; + let NodeType::Element(el) = &*node_type else { + panic!("input must be an element") + }; let value = el .attributes @@ -146,7 +150,9 @@ impl CustomElement for Button { AttributeMask::All => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; self.update_value_attr(&el); self.update_size_attr(&mut el); } @@ -155,7 +161,9 @@ impl CustomElement for Button { AttributeMask::Some(attrs) => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; if attrs.contains("width") || attrs.contains("height") { self.update_size_attr(&mut el); } diff --git a/packages/rink/src/widgets/checkbox.rs b/packages/rink/src/widgets/checkbox.rs index 24b3f6244..fab809ed2 100644 --- a/packages/rink/src/widgets/checkbox.rs +++ b/packages/rink/src/widgets/checkbox.rs @@ -94,14 +94,18 @@ impl CheckBox { fn write_value(&self, mut root: NodeMut) { let single_char = { let node_type = root.node_type_mut(); - let NodeTypeMut::Element( el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(el) = node_type else { + panic!("input must be an element") + }; Self::width(&el) == "1px" || Self::height(&el) == "1px" }; let rdom = root.real_dom_mut(); if let Some(mut text) = rdom.get_mut(self.text_id) { let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Text(mut text) = node_type else { + panic!("input must be an element") + }; let value = if single_char { if self.checked { "☑" @@ -156,7 +160,9 @@ impl CustomElement for CheckBox { fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; + let NodeType::Element(el) = &*node_type else { + panic!("input must be an element") + }; let value = el .attributes @@ -197,7 +203,9 @@ impl CustomElement for CheckBox { AttributeMask::All => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; self.update_value_attr(&el); self.update_size_attr(&mut el); self.update_checked_attr(&el); @@ -207,7 +215,9 @@ impl CustomElement for CheckBox { AttributeMask::Some(attrs) => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; if attrs.contains("width") || attrs.contains("height") { self.update_size_attr(&mut el); } diff --git a/packages/rink/src/widgets/input.rs b/packages/rink/src/widgets/input.rs index c6a3e56b9..c1fb563d7 100644 --- a/packages/rink/src/widgets/input.rs +++ b/packages/rink/src/widgets/input.rs @@ -56,7 +56,9 @@ impl CustomElement for Input { } let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; + let NodeType::Element(el) = &*node_type else { + panic!("input must be an element") + }; let input_type = el .attributes .get(&OwnedAttributeDiscription { diff --git a/packages/rink/src/widgets/slider.rs b/packages/rink/src/widgets/slider.rs index f97c12bf6..7080b6c12 100644 --- a/packages/rink/src/widgets/slider.rs +++ b/packages/rink/src/widgets/slider.rs @@ -163,7 +163,9 @@ impl Slider { if let Some(mut div) = rdom.get_mut(self.pre_cursor_div) { let node_type = div.node_type_mut(); - let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut element) = node_type else { + panic!("input must be an element") + }; element.set_attribute( OwnedAttributeDiscription { name: "width".to_string(), @@ -175,7 +177,9 @@ impl Slider { if let Some(mut div) = rdom.get_mut(self.post_cursor_div) { let node_type = div.node_type_mut(); - let NodeTypeMut::Element(mut element) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut element) = node_type else { + panic!("input must be an element") + }; element.set_attribute( OwnedAttributeDiscription { name: "width".to_string(), @@ -259,7 +263,9 @@ impl CustomElement for Slider { fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; + let NodeType::Element(el) = &*node_type else { + panic!("input must be an element") + }; let value = el.attributes.get(&OwnedAttributeDiscription { name: "value".to_string(), @@ -390,7 +396,9 @@ impl CustomElement for Slider { AttributeMask::All => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; self.update_value_attr(&el); self.update_size_attr(&mut el); self.update_max_attr(&el); @@ -403,7 +411,9 @@ impl CustomElement for Slider { AttributeMask::Some(attrs) => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; if attrs.contains("width") || attrs.contains("height") { self.update_size_attr(&mut el); } diff --git a/packages/rink/src/widgets/text_like.rs b/packages/rink/src/widgets/text_like.rs index b5874c776..6521073d0 100644 --- a/packages/rink/src/widgets/text_like.rs +++ b/packages/rink/src/widgets/text_like.rs @@ -143,19 +143,25 @@ impl TextLike { if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) { let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Text(mut text) = node_type else { + panic!("input must be an element") + }; *text.text_mut() = self.controller.display_text(text_before_first_cursor); } if let Some(mut text) = rdom.get_mut(self.highlighted_text) { let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Text(mut text) = node_type else { + panic!("input must be an element") + }; *text.text_mut() = self.controller.display_text(text_highlighted); } if let Some(mut text) = rdom.get_mut(self.post_cursor_text) { let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Text(mut text) = node_type else { + panic!("input must be an element") + }; *text.text_mut() = self.controller.display_text(text_after_second_cursor); } @@ -288,7 +294,9 @@ impl CustomElement for fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; + let NodeType::Element(el) = &*node_type else { + panic!("input must be an element") + }; let value = el .attributes @@ -370,7 +378,9 @@ impl CustomElement for AttributeMask::All => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; self.update_value_attr(&el); self.update_size_attr(&mut el); self.update_max_width_attr(&el); @@ -381,7 +391,9 @@ impl CustomElement for AttributeMask::Some(attrs) => { { let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + let NodeTypeMut::Element(mut el) = node_type else { + panic!("input must be an element") + }; if attrs.contains("width") || attrs.contains("height") { self.update_size_attr(&mut el); } diff --git a/packages/router/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index 9b94c7a9d..8c1a6fc76 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -38,7 +38,7 @@ fn BlogList(cx: Scope) -> Element { fn BlogPost(cx: Scope) -> Element { let Some(id) = use_route(cx).segment("id") else { - return cx.render(rsx! { div { "No blog post id" } }) + return cx.render(rsx! { div { "No blog post id" } }); }; log::trace!("rendering blog post {}", id); From 98e2b4acbd9d8a1852e130b61b7e5b8e5941f0c9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 18 Jul 2023 12:40:47 -0700 Subject: [PATCH 03/31] fix normal desktop serve --- packages/cli/src/builder.rs | 165 ++++++++++++------------- packages/cli/src/server/desktop/mod.rs | 32 +++-- 2 files changed, 99 insertions(+), 98 deletions(-) diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 1cd6d787e..0455de93f 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -247,128 +247,121 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result { log::info!("🚅 Running build [Desktop] command..."); + let t_start = std::time::Instant::now(); let ignore_files = build_assets(config)?; - let mut cmd = Command::new("cargo"); - cmd.current_dir(&config.crate_dir) + let mut cmd = subprocess::Exec::cmd("cargo") + .cwd(&config.crate_dir) .arg("build") - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()); + .arg("--message-format=json"); if config.release { - cmd.arg("--release"); + cmd = cmd.arg("--release"); } if config.verbose { - cmd.arg("--verbose"); + cmd = cmd.arg("--verbose"); } if config.custom_profile.is_some() { let custom_profile = config.custom_profile.as_ref().unwrap(); - cmd.arg("--profile"); - cmd.arg(custom_profile); + cmd = cmd.arg("--profile").arg(custom_profile); } if config.features.is_some() { let features_str = config.features.as_ref().unwrap().join(" "); - cmd.arg("--features"); - cmd.arg(features_str); + cmd = cmd.arg("--features").arg(features_str); } - match &config.executable { + let cmd = match &config.executable { crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name), crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name), crate::ExecutableType::Example(name) => cmd.arg("--example").arg(name), }; - let output = cmd.output()?; + let warning_messages = prettier_build(cmd)?; - if !output.status.success() { - return Err(Error::BuildFailed("Program build failed.".into())); - } + let release_type = match config.release { + true => "release", + false => "debug", + }; - if output.status.success() { - let release_type = match config.release { - true => "release", - false => "debug", - }; - - let file_name: String; - let mut res_path = match &config.executable { - crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => { - file_name = name.clone(); - config.target_dir.join(release_type).join(name) - } - crate::ExecutableType::Example(name) => { - file_name = name.clone(); - config - .target_dir - .join(release_type) - .join("examples") - .join(name) - } - }; - - let target_file = if cfg!(windows) { - res_path.set_extension("exe"); - format!("{}.exe", &file_name) - } else { - file_name - }; - - if !config.out_dir.is_dir() { - create_dir_all(&config.out_dir)?; + let file_name: String; + let mut res_path = match &config.executable { + crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => { + file_name = name.clone(); + config.target_dir.join(release_type).join(name) } - copy(res_path, &config.out_dir.join(target_file))?; + crate::ExecutableType::Example(name) => { + file_name = name.clone(); + config + .target_dir + .join(release_type) + .join("examples") + .join(name) + } + }; - // this code will copy all public file to the output dir - if config.asset_dir.is_dir() { - let copy_options = fs_extra::dir::CopyOptions { - overwrite: true, - skip_exist: false, - buffer_size: 64000, - copy_inside: false, - content_only: false, - depth: 0, - }; + let target_file = if cfg!(windows) { + res_path.set_extension("exe"); + format!("{}.exe", &file_name) + } else { + file_name + }; - for entry in std::fs::read_dir(&config.asset_dir)? { - let path = entry?.path(); - if path.is_file() { - std::fs::copy(&path, &config.out_dir.join(path.file_name().unwrap()))?; - } else { - match fs_extra::dir::copy(&path, &config.out_dir, ©_options) { - Ok(_) => {} - Err(e) => { - log::warn!("Error copying dir: {}", e); - } + if !config.out_dir.is_dir() { + create_dir_all(&config.out_dir)?; + } + copy(res_path, &config.out_dir.join(target_file))?; + + // this code will copy all public file to the output dir + if config.asset_dir.is_dir() { + let copy_options = fs_extra::dir::CopyOptions { + overwrite: true, + skip_exist: false, + buffer_size: 64000, + copy_inside: false, + content_only: false, + depth: 0, + }; + + for entry in std::fs::read_dir(&config.asset_dir)? { + let path = entry?.path(); + if path.is_file() { + std::fs::copy(&path, &config.out_dir.join(path.file_name().unwrap()))?; + } else { + match fs_extra::dir::copy(&path, &config.out_dir, ©_options) { + Ok(_) => {} + Err(e) => { + log::warn!("Error copying dir: {}", e); } - for ignore in &ignore_files { - let ignore = ignore.strip_prefix(&config.asset_dir).unwrap(); - let ignore = config.out_dir.join(ignore); - if ignore.is_file() { - std::fs::remove_file(ignore)?; - } + } + for ignore in &ignore_files { + let ignore = ignore.strip_prefix(&config.asset_dir).unwrap(); + let ignore = config.out_dir.join(ignore); + if ignore.is_file() { + std::fs::remove_file(ignore)?; } } } } - - log::info!( - "🚩 Build completed: [./{}]", - config - .dioxus_config - .application - .out_dir - .clone() - .unwrap_or_else(|| PathBuf::from("dist")) - .display() - ); } + + log::info!( + "🚩 Build completed: [./{}]", + config + .dioxus_config + .application + .out_dir + .clone() + .unwrap_or_else(|| PathBuf::from("dist")) + .display() + ); + println!("build desktop done"); Ok(BuildResult { - warnings: vec![], - elapsed_time: 0, + warnings: warning_messages, + elapsed_time: (t_start - std::time::Instant::now()).as_millis(), }) } diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 6a2c57539..bc0d0ef82 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -29,8 +29,8 @@ use notify::{RecommendedWatcher, Watcher}; use std::{ net::UdpSocket, path::PathBuf, - process::{Command, Stdio}, - sync::{Arc, Mutex}, + process::{Child, Command, Stdio}, + sync::{Arc, Mutex, RwLock}, }; use tokio::sync::broadcast::{self, Sender}; use tower::ServiceBuilder; @@ -66,7 +66,8 @@ pub async fn startup(config: CrateConfig) -> Result<()> { /// Start the server without hot reload pub async fn serve_default(config: CrateConfig) -> Result<()> { - let first_build_result = start_desktop(&config)?; + let (child, first_build_result) = start_desktop(&config)?; + let currently_running_child: RwLock = RwLock::new(child); log::info!("🚀 Starting development server..."); @@ -75,7 +76,14 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> { let _watcher = setup_file_watcher( { let config = config.clone(); - move || start_desktop(&config) + + move || { + let mut current_child = currently_running_child.write().unwrap(); + current_child.kill()?; + let (child, result) = start_desktop(&config)?; + *current_child = child; + Ok(result) + } }, &config, None, @@ -93,6 +101,8 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> { None, ); + std::future::pending::<()>().await; + Ok(()) } @@ -100,7 +110,7 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> { /// Start dx serve with hot reload pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { - let first_build_result = start_desktop(&config)?; + let (_, first_build_result) = start_desktop(&config)?; println!("🚀 Starting development server..."); @@ -137,7 +147,7 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { for channel in &mut *channels.lock().unwrap() { send_msg(HotReloadMsg::Shutdown, channel); } - start_desktop(&config) + Ok(start_desktop(&config)?.1) } }, None, @@ -243,7 +253,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { } } -pub fn start_desktop(config: &CrateConfig) -> Result { +pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { // Run the desktop application let result = crate::builder::build_desktop(config, true)?; @@ -255,11 +265,9 @@ pub fn start_desktop(config: &CrateConfig) -> Result { if cfg!(windows) { file.set_extension("exe"); } - Command::new(file.to_str().unwrap()) - .stdout(Stdio::inherit()) - .spawn()?; + let child = Command::new(file.to_str().unwrap()).spawn()?; + + Ok((child, result)) } } - - Ok(result) } From 4a0869de7b3fb08925460515fdb17a00a117c1f2 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 18 Jul 2023 12:41:48 -0700 Subject: [PATCH 04/31] cargo fix --- packages/cli/src/builder.rs | 1 - packages/cli/src/cli/serve.rs | 7 +----- packages/cli/src/server/desktop/mod.rs | 35 +++----------------------- packages/cli/src/server/mod.rs | 28 +++------------------ packages/cli/src/server/web/mod.rs | 5 +--- 5 files changed, 9 insertions(+), 67 deletions(-) diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 0455de93f..c935db106 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -12,7 +12,6 @@ use std::{ io::Read, panic, path::PathBuf, - process::Command, time::Duration, }; use wasm_bindgen_cli_support::Bindgen; diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 1be19cb3b..a5d69c7cc 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -1,10 +1,5 @@ use super::*; -use std::{ - fs::create_dir_all, - io::Write, - path::PathBuf, - process::{Command, Stdio}, -}; +use std::{fs::create_dir_all, io::Write, path::PathBuf}; /// Run the WASM project on dev-server #[derive(Clone, Debug, Parser)] diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index bc0d0ef82..93bd4e9e9 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -1,52 +1,25 @@ use crate::{ - builder, - serve::Serve, server::{ - output::{print_console_info, PrettierOptions, WebServerInfo}, + output::{print_console_info, PrettierOptions}, setup_file_watcher, setup_file_watcher_hot_reload, }, BuildResult, CrateConfig, Result, }; -use axum::{ - body::{Full, HttpBody}, - extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, - http::{ - header::{HeaderName, HeaderValue}, - Method, Response, StatusCode, - }, - response::IntoResponse, - routing::{get, get_service}, - Router, -}; -use axum_server::tls_rustls::RustlsConfig; -use cargo_metadata::diagnostic::Diagnostic; -use dioxus_core::Template; + use dioxus_hot_reload::HotReloadMsg; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; use interprocess_docfix::local_socket::LocalSocketListener; -use notify::{RecommendedWatcher, Watcher}; use std::{ - net::UdpSocket, path::PathBuf, - process::{Child, Command, Stdio}, + process::{Child, Command}, sync::{Arc, Mutex, RwLock}, }; -use tokio::sync::broadcast::{self, Sender}; -use tower::ServiceBuilder; -use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody}; -use tower_http::{ - cors::{Any, CorsLayer}, - ServiceBuilderExt, -}; +use tokio::sync::broadcast::{self}; #[cfg(feature = "plugin")] use plugin::PluginManager; -struct WsReloadState { - update: broadcast::Sender<()>, -} - pub async fn startup(config: CrateConfig) -> Result<()> { // ctrl-c shutdown checker let _crate_config = config.clone(); diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 8c421f269..19dc44b75 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -1,37 +1,15 @@ -use crate::{ - builder, cfg::Platform, serve::Serve, server::desktop::start_desktop, BuildResult, CrateConfig, - Result, -}; -use axum::{ - body::{Full, HttpBody}, - extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, - http::{ - header::{HeaderName, HeaderValue}, - Method, Response, StatusCode, - }, - response::IntoResponse, - routing::{get, get_service}, - Router, -}; -use axum_server::tls_rustls::RustlsConfig; +use crate::{BuildResult, CrateConfig, Result}; + use cargo_metadata::diagnostic::Diagnostic; use dioxus_core::Template; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; use notify::{RecommendedWatcher, Watcher}; use std::{ - net::UdpSocket, path::PathBuf, - process::{Command, Stdio}, sync::{Arc, Mutex}, }; -use tokio::sync::broadcast::{self, Sender}; -use tower::ServiceBuilder; -use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody}; -use tower_http::{ - cors::{Any, CorsLayer}, - ServiceBuilderExt, -}; +use tokio::sync::broadcast::Sender; mod output; use output::*; diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs index e6a429788..28ffa28c0 100644 --- a/packages/cli/src/server/web/mod.rs +++ b/packages/cli/src/server/web/mod.rs @@ -19,14 +19,11 @@ use axum::{ Router, }; use axum_server::tls_rustls::RustlsConfig; -use cargo_metadata::diagnostic::Diagnostic; -use dioxus_core::Template; + use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; -use notify::{RecommendedWatcher, Watcher}; use std::{ net::UdpSocket, - path::PathBuf, process::Command, sync::{Arc, Mutex}, }; From f3b3c0698b2a33a309e35e2fea2d5a3777264a0d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 18 Jul 2023 12:43:02 -0700 Subject: [PATCH 05/31] fix clippy --- packages/cli/src/cli/build.rs | 11 ++++------- packages/cli/src/cli/serve.rs | 11 ++++------- packages/cli/src/server/web/mod.rs | 4 ++-- packages/core/src/scopes.rs | 4 ++-- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 29edf301b..3053a6e5a 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -32,13 +32,10 @@ impl Build { crate_config.set_features(self.build.features.unwrap()); } - let platform = self.build.platform.unwrap_or_else(|| { - crate_config - .dioxus_config - .application - .default_platform - .clone() - }); + let platform = self + .build + .platform + .unwrap_or(crate_config.dioxus_config.application.default_platform); #[cfg(feature = "plugin")] let _ = PluginManager::on_build_start(&crate_config, &platform); diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index a5d69c7cc..433d611b7 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -34,13 +34,10 @@ impl Serve { // Subdirectories don't work with the server crate_config.dioxus_config.web.app.base_path = None; - let platform = self.serve.platform.unwrap_or_else(|| { - crate_config - .dioxus_config - .application - .default_platform - .clone() - }); + let platform = self + .serve + .platform + .unwrap_or(crate_config.dioxus_config.application.default_platform); match platform { cfg::Platform::Web => { diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs index 28ffa28c0..e08d2297a 100644 --- a/packages/cli/src/server/web/mod.rs +++ b/packages/cli/src/server/web/mod.rs @@ -473,7 +473,7 @@ async fn ws_handler( } fn build(config: &CrateConfig, reload_tx: &Sender<()>) -> Result { - let result = builder::build(&config, true)?; + let result = builder::build(config, true)?; // change the websocket reload state to true; // the page will auto-reload. if config @@ -483,7 +483,7 @@ fn build(config: &CrateConfig, reload_tx: &Sender<()>) -> Result { .reload_html .unwrap_or(false) { - let _ = Serve::regen_dev_page(&config); + let _ = Serve::regen_dev_page(config); } let _ = reload_tx.send(()); Ok(result) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index ff94c456d..87e894ef3 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -688,13 +688,13 @@ impl<'src> ScopeState { raw_ref.downcast_mut::() }) .expect( - r###" + r#" Unable to retrieve the hook that was initialized at this index. Consult the `rules of hooks` to understand how to use hooks properly. You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. Functions prefixed with "use" should never be called conditionally. - "###, + "#, ) } } From 7a518f540f54f1a682116ff2bf77bdecdd98c6c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 01:07:27 +0000 Subject: [PATCH 06/31] Bump word-wrap from 1.2.3 to 1.2.4 in /packages/extension Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] --- packages/extension/package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/extension/package-lock.json b/packages/extension/package-lock.json index 6d3a8b27f..2a51fc2e8 100644 --- a/packages/extension/package-lock.json +++ b/packages/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "dioxus", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "dioxus", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "dependencies": { "dioxus-ext": "./pkg", @@ -3720,9 +3720,9 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -6444,9 +6444,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, "wrappy": { From 8527e60e018b071b8d7fc27ab6ebff9952aa932d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 09:52:51 -0700 Subject: [PATCH 07/31] Move playwright stuff into its own directory --- package-lock.json => playwright-tests/package-lock.json | 0 package.json => playwright-tests/package.json | 0 playwright.config.js => playwright-tests/playwright.config.js | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename package-lock.json => playwright-tests/package-lock.json (100%) rename package.json => playwright-tests/package.json (100%) rename playwright.config.js => playwright-tests/playwright.config.js (98%) diff --git a/package-lock.json b/playwright-tests/package-lock.json similarity index 100% rename from package-lock.json rename to playwright-tests/package-lock.json diff --git a/package.json b/playwright-tests/package.json similarity index 100% rename from package.json rename to playwright-tests/package.json diff --git a/playwright.config.js b/playwright-tests/playwright.config.js similarity index 98% rename from playwright.config.js rename to playwright-tests/playwright.config.js index d7f019bbd..026a574ad 100644 --- a/playwright.config.js +++ b/playwright-tests/playwright.config.js @@ -12,7 +12,7 @@ const path = require("path"); * @see https://playwright.dev/docs/test-configuration */ module.exports = defineConfig({ - testDir: "./playwright-tests", + testDir: ".", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ From b9794aaad4bcd6898ee93c5bc8a6b529f7efa52d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 09:54:54 -0700 Subject: [PATCH 08/31] Only run miri for changed files --- .github/workflows/miri.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index 73b38dec9..0146e98bc 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -6,6 +6,13 @@ on: branches: - 'auto' - 'try' + paths: + - packages/** + - examples/** + - src/** + - .github/** + - lib.rs + - Cargo.toml pull_request: types: [opened, synchronize, reopened, ready_for_review] branches: From 5eac6cd1c5d76de3c72bbd1da1543d1ef3c2ebd0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 09:57:00 -0700 Subject: [PATCH 09/31] Add working dir --- .github/workflows/playwright.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 84891f403..77dda0c11 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -9,17 +9,12 @@ jobs: if: github.event.pull_request.draft == false timeout-minutes: 60 runs-on: ubuntu-20.04 + working-directory: playwright-tests steps: - - uses: actions/checkout@v3 + # Do our best to cache the toolchain and node install steps - uses: actions/setup-node@v3 with: node-version: 16 - - name: Install dependencies - run: npm ci - - name: Install Playwright - run: npm install -D @playwright/test - - name: Install Playwright Browsers - run: npx playwright install --with-deps - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -29,11 +24,18 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Install WASM toolchain run: rustup target add wasm32-unknown-unknown + - uses: actions/checkout@v3 + - name: Install dependencies + run: npm ci + - name: Install Playwright + run: npm install -D @playwright/test + - name: Install Playwright Browsers + run: npx playwright install --with-deps - name: Install Dioxus CLI uses: actions-rs/cargo@v1 with: command: install - args: --path packages/cli + args: --path ../packages/cli - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifact@v3 From f51b0099dd6b723f9546d7eb5890283fc31b75fe Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 09:59:05 -0700 Subject: [PATCH 10/31] CD as a step --- .github/workflows/playwright.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 77dda0c11..cdd7fb996 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -9,7 +9,6 @@ jobs: if: github.event.pull_request.draft == false timeout-minutes: 60 runs-on: ubuntu-20.04 - working-directory: playwright-tests steps: # Do our best to cache the toolchain and node install steps - uses: actions/setup-node@v3 @@ -25,6 +24,8 @@ jobs: - name: Install WASM toolchain run: rustup target add wasm32-unknown-unknown - uses: actions/checkout@v3 + - name: Enter playwright-tests + run: cd playwright-tests - name: Install dependencies run: npm ci - name: Install Playwright From da21e5f4c1269e2893c955e44616bebc85e34d77 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 10:03:28 -0700 Subject: [PATCH 11/31] Move order around, set default working directory --- .github/workflows/playwright.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index cdd7fb996..974649419 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -4,6 +4,9 @@ on: branches: [ main, master ] pull_request: branches: [ main, master ] +defaults: + run: + working-directory: ./playwright-tests jobs: test: if: github.event.pull_request.draft == false @@ -11,6 +14,7 @@ jobs: runs-on: ubuntu-20.04 steps: # Do our best to cache the toolchain and node install steps + - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 @@ -23,9 +27,6 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Install WASM toolchain run: rustup target add wasm32-unknown-unknown - - uses: actions/checkout@v3 - - name: Enter playwright-tests - run: cd playwright-tests - name: Install dependencies run: npm ci - name: Install Playwright From 22ea04e45da8d2354c93305d3d82c86c870b48b3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 10:08:50 -0700 Subject: [PATCH 12/31] Use different path for cli --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 974649419..bddc767e6 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -37,7 +37,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: install - args: --path ../packages/cli + args: --path packages/cli - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifact@v3 From 0ceaaa6c964b1c75f2beec47bad321acba4556f1 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 10:11:11 -0700 Subject: [PATCH 13/31] Don't install the CLI --- .github/workflows/playwright.yml | 11 ++++++----- playwright-tests/playwright.config.js | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index bddc767e6..7fe524bb3 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -33,11 +33,12 @@ jobs: run: npm install -D @playwright/test - name: Install Playwright Browsers run: npx playwright install --with-deps - - name: Install Dioxus CLI - uses: actions-rs/cargo@v1 - with: - command: install - args: --path packages/cli + # Cache the CLI by using cargo run internally + # - name: Install Dioxus CLI + # uses: actions-rs/cargo@v1 + # with: + # command: install + # args: --path packages/cli - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifact@v3 diff --git a/playwright-tests/playwright.config.js b/playwright-tests/playwright.config.js index 026a574ad..5ab81f161 100644 --- a/playwright-tests/playwright.config.js +++ b/playwright-tests/playwright.config.js @@ -82,7 +82,7 @@ module.exports = defineConfig({ }, { cwd: path.join(process.cwd(), "playwright-tests", "web"), - command: "dx serve", + command: "cargo run --package dioxus-cli -- serve", port: 8080, timeout: 10 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -90,7 +90,7 @@ module.exports = defineConfig({ }, { cwd: path.join(process.cwd(), 'playwrite-tests', 'fullstack'), - command: 'dx build --features web --release\ncargo run --release --features ssr', + command: 'cargo run --package dioxus-cli -- build --features web --release\ncargo run --release --features ssr', port: 3333, timeout: 10 * 60 * 1000, reuseExistingServer: !process.env.CI, From eb43429ff6c839929ce2d77c648b928761acc845 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 10:16:27 -0700 Subject: [PATCH 14/31] Bump versions, move paths --- playwright-tests/package-lock.json | 20 +++--- playwright-tests/package.json | 2 +- playwright-tests/playwright-report/index.html | 62 +++++++++++++++++++ playwright-tests/playwright.config.js | 4 +- 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 playwright-tests/playwright-report/index.html diff --git a/playwright-tests/package-lock.json b/playwright-tests/package-lock.json index 348b505d4..4f1b932ac 100644 --- a/playwright-tests/package-lock.json +++ b/playwright-tests/package-lock.json @@ -9,23 +9,23 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@playwright/test": "^1.34.3" + "@playwright/test": "^1.36.1" } }, "node_modules/@playwright/test": { - "version": "1.34.3", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.34.3.tgz", - "integrity": "sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw==", + "version": "1.36.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz", + "integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==", "dev": true, "dependencies": { "@types/node": "*", - "playwright-core": "1.34.3" + "playwright-core": "1.36.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" }, "optionalDependencies": { "fsevents": "2.3.2" @@ -52,15 +52,15 @@ } }, "node_modules/playwright-core": { - "version": "1.34.3", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.34.3.tgz", - "integrity": "sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==", + "version": "1.36.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz", + "integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==", "dev": true, "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" } } } diff --git a/playwright-tests/package.json b/playwright-tests/package.json index ceb38db89..31d444420 100644 --- a/playwright-tests/package.json +++ b/playwright-tests/package.json @@ -12,6 +12,6 @@ "author": "", "license": "ISC", "devDependencies": { - "@playwright/test": "^1.34.3" + "@playwright/test": "^1.36.1" } } diff --git a/playwright-tests/playwright-report/index.html b/playwright-tests/playwright-report/index.html new file mode 100644 index 000000000..71730368d --- /dev/null +++ b/playwright-tests/playwright-report/index.html @@ -0,0 +1,62 @@ + + + + + + + + + Playwright Test Report + + + + +
+ + + + \ No newline at end of file diff --git a/playwright-tests/playwright.config.js b/playwright-tests/playwright.config.js index 5ab81f161..54fe810a7 100644 --- a/playwright-tests/playwright.config.js +++ b/playwright-tests/playwright.config.js @@ -81,7 +81,7 @@ module.exports = defineConfig({ stdout: "pipe", }, { - cwd: path.join(process.cwd(), "playwright-tests", "web"), + cwd: path.join(process.cwd(), "web"), command: "cargo run --package dioxus-cli -- serve", port: 8080, timeout: 10 * 60 * 1000, @@ -89,7 +89,7 @@ module.exports = defineConfig({ stdout: "pipe", }, { - cwd: path.join(process.cwd(), 'playwrite-tests', 'fullstack'), + cwd: path.join(process.cwd(), 'fullstack'), command: 'cargo run --package dioxus-cli -- build --features web --release\ncargo run --release --features ssr', port: 3333, timeout: 10 * 60 * 1000, From 2871c8bb4dbaaa29e58d6348fda734d9d60f4d71 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 12:08:13 -0700 Subject: [PATCH 15/31] Don't use boxed cell slice, use a refcell instead --- packages/core/src/create.rs | 5 +- packages/core/src/diff.rs | 18 ++-- packages/core/src/nodes.rs | 105 +------------------- packages/core/src/scope_arena.rs | 8 +- packages/core/src/virtual_dom.rs | 3 + packages/dioxus-tui/examples/colorpicker.rs | 2 +- packages/web/src/rehydrate.rs | 2 +- 7 files changed, 24 insertions(+), 119 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 8efdf13e4..6159c882b 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -88,8 +88,7 @@ impl<'b> VirtualDom { } // Intialize the root nodes slice - node.root_ids - .intialize(vec![ElementId(0); node.template.get().roots.len()].into_boxed_slice()); + *node.root_ids.borrow_mut() = vec![ElementId(0); node.template.get().roots.len()]; // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways @@ -328,7 +327,7 @@ impl<'b> VirtualDom { fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId { // Get an ID for this root since it's a real root let this_id = self.next_root(template, root_idx); - template.root_ids.set(root_idx, this_id); + template.root_ids.borrow_mut()[root_idx] = this_id; self.mutations.push(LoadTemplate { name: template.template.get().name, diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index cfda18505..74988210a 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -129,12 +129,14 @@ impl<'b> VirtualDom { }); // Make sure the roots get transferred over while we're here - right_template.root_ids.transfer(&left_template.root_ids); + *right_template.root_ids.borrow_mut() = left_template.root_ids.borrow().clone(); + + let root_ids = right_template.root_ids.borrow(); // Update the node refs - for i in 0..right_template.root_ids.len() { - if let Some(root_id) = right_template.root_ids.get(i) { - self.update_template(root_id, right_template); + for i in 0..root_ids.len() { + if let Some(root_id) = root_ids.get(i) { + self.update_template(*root_id, right_template); } } } @@ -686,7 +688,7 @@ impl<'b> VirtualDom { Some(node) => node, None => { self.mutations.push(Mutation::PushRoot { - id: node.root_ids.get(idx).unwrap(), + id: node.root_ids.borrow()[idx], }); return 1; } @@ -821,7 +823,7 @@ impl<'b> VirtualDom { if let Some(dy) = node.dynamic_root(idx) { self.remove_dynamic_node(dy, gen_muts); } else { - let id = node.root_ids.get(idx).unwrap(); + let id = node.root_ids.borrow()[idx]; if gen_muts { self.mutations.push(Mutation::Remove { id }); } @@ -928,7 +930,7 @@ impl<'b> VirtualDom { fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId { match node.dynamic_root(0) { - None => node.root_ids.get(0).unwrap(), + None => node.root_ids.borrow()[0], Some(Text(t)) => t.id.get().unwrap(), Some(Fragment(t)) => self.find_first_element(&t[0]), Some(Placeholder(t)) => t.id.get().unwrap(), @@ -944,7 +946,7 @@ impl<'b> VirtualDom { fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId { match node.dynamic_root(node.template.get().roots.len() - 1) { - None => node.root_ids.last().unwrap(), + None => *node.root_ids.borrow().last().unwrap(), Some(Text(t)) => t.id.get().unwrap(), Some(Fragment(t)) => self.find_last_element(t.last().unwrap()), Some(Placeholder(t)) => t.id.get().unwrap(), diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 209c37c77..197550a3b 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -5,7 +5,7 @@ use bumpalo::boxed::Box as BumpBox; use bumpalo::Bump; use std::{ any::{Any, TypeId}, - cell::{Cell, RefCell, UnsafeCell}, + cell::{Cell, RefCell}, fmt::{Arguments, Debug}, }; @@ -54,7 +54,7 @@ pub struct VNode<'a> { /// The IDs for the roots of this template - to be used when moving the template around and removing it from /// the actual Dom - pub root_ids: BoxedCellSlice, + pub root_ids: RefCell>, /// The dynamic parts of the template pub dynamic_nodes: &'a [DynamicNode<'a>], @@ -63,112 +63,13 @@ pub struct VNode<'a> { pub dynamic_attrs: &'a [Attribute<'a>], } -// Saftey: There is no way to get references to the internal data of this struct so no refrences will be invalidated by mutating the data with a immutable reference (The same principle behind Cell) -#[derive(Debug, Default)] -pub struct BoxedCellSlice(UnsafeCell>>); - -impl Clone for BoxedCellSlice { - fn clone(&self) -> Self { - Self(UnsafeCell::new(unsafe { (*self.0.get()).clone() })) - } -} - -impl BoxedCellSlice { - pub fn last(&self) -> Option { - unsafe { - (*self.0.get()) - .as_ref() - .and_then(|inner| inner.as_ref().last().copied()) - } - } - - pub fn get(&self, idx: usize) -> Option { - unsafe { - (*self.0.get()) - .as_ref() - .and_then(|inner| inner.as_ref().get(idx).copied()) - } - } - - pub unsafe fn get_unchecked(&self, idx: usize) -> Option { - (*self.0.get()) - .as_ref() - .and_then(|inner| inner.as_ref().get(idx).copied()) - } - - pub fn set(&self, idx: usize, new: ElementId) { - unsafe { - if let Some(inner) = &mut *self.0.get() { - inner[idx] = new; - } - } - } - - pub fn intialize(&self, contents: Box<[ElementId]>) { - unsafe { - *self.0.get() = Some(contents); - } - } - - pub fn transfer(&self, other: &Self) { - unsafe { - *self.0.get() = (*other.0.get()).clone(); - } - } - - pub fn take_from(&self, other: &Self) { - unsafe { - *self.0.get() = (*other.0.get()).take(); - } - } - - pub fn len(&self) -> usize { - unsafe { - (*self.0.get()) - .as_ref() - .map(|inner| inner.len()) - .unwrap_or(0) - } - } -} - -impl<'a> IntoIterator for &'a BoxedCellSlice { - type Item = ElementId; - - type IntoIter = BoxedCellSliceIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - BoxedCellSliceIter { - index: 0, - borrow: self, - } - } -} - -pub struct BoxedCellSliceIter<'a> { - index: usize, - borrow: &'a BoxedCellSlice, -} - -impl Iterator for BoxedCellSliceIter<'_> { - type Item = ElementId; - - fn next(&mut self) -> Option { - let result = self.borrow.get(self.index); - if result.is_some() { - self.index += 1; - } - result - } -} - impl<'a> VNode<'a> { /// Create a template with no nodes that will be skipped over during diffing pub fn empty() -> Element<'a> { Some(VNode { key: None, parent: None, - root_ids: BoxedCellSlice::default(), + root_ids: Default::default(), dynamic_nodes: &[], dynamic_attrs: &[], template: Cell::new(Template { diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index d058e34de..e8134c238 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -83,12 +83,12 @@ impl VirtualDom { id: scope.id, }); - if matches!(allocated, RenderReturn::Aborted(_)) { - if scope.suspended.get() { + if scope.suspended.get() { + if matches!(allocated, RenderReturn::Aborted(_)) { self.suspended_scopes.insert(scope.id); - } else if !self.suspended_scopes.is_empty() { - _ = self.suspended_scopes.remove(&scope.id); } + } else if !self.suspended_scopes.is_empty() { + _ = self.suspended_scopes.remove(&scope.id); } // rebind the lifetime now that its stored internally diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 31a1b5912..ee6e9c5d6 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -571,12 +571,15 @@ impl VirtualDom { /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content pub async fn wait_for_suspense(&mut self) { loop { + // println!("waiting for suspense {:?}", self.suspended_scopes); if self.suspended_scopes.is_empty() { return; } + // println!("waiting for suspense"); self.wait_for_work().await; + // println!("Rendered immediately"); _ = self.render_immediate(); } } diff --git a/packages/dioxus-tui/examples/colorpicker.rs b/packages/dioxus-tui/examples/colorpicker.rs index 1de6b8209..00f8ef7e0 100644 --- a/packages/dioxus-tui/examples/colorpicker.rs +++ b/packages/dioxus-tui/examples/colorpicker.rs @@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element { background_color: "hsl({hue}, 70%, {brightness}%)", onmousemove: move |evt| { if let RenderReturn::Ready(node) = cx.root_node() { - if let Some(id) = node.root_ids.get(0){ + if let Some(id) = node.root_ids.borrow().get(0).cloned() { let node = tui_query.get(mapping.get_node_id(id).unwrap()); let Size{width, height} = node.size().unwrap(); let pos = evt.inner().element_coordinates(); diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 8af7da16d..5cc9ea57e 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -90,7 +90,7 @@ impl WebsysDom { // make sure we set the root node ids even if the node is not dynamic set_node( hydrated, - vnode.root_ids.get(i).ok_or(VNodeNotInitialized)?, + *vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?, current_child.clone()?, ); From 163fe68f455c93b0a425b3d9fb0bf527e08f2063 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 12:26:32 -0700 Subject: [PATCH 16/31] Remove a bit more unsafe --- packages/core/tests/task.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/tests/task.rs b/packages/core/tests/task.rs index 2704c5eb2..316cea6dd 100644 --- a/packages/core/tests/task.rs +++ b/packages/core/tests/task.rs @@ -1,9 +1,9 @@ //! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely use dioxus::prelude::*; -use std::time::Duration; +use std::{sync::atomic::AtomicUsize, time::Duration}; -static mut POLL_COUNT: usize = 0; +static POLL_COUNT: AtomicUsize = AtomicUsize::new(0); #[tokio::test] async fn it_works() { @@ -18,7 +18,10 @@ async fn it_works() { // By the time the tasks are finished, we should've accumulated ticks from two tasks // Be warned that by setting the delay to too short, tokio might not schedule in the tasks - assert_eq!(unsafe { POLL_COUNT }, 135); + assert_eq!( + POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed), + 135 + ); } fn app(cx: Scope) -> Element { @@ -26,14 +29,14 @@ fn app(cx: Scope) -> Element { cx.spawn(async { for x in 0..10 { tokio::time::sleep(Duration::from_micros(50)).await; - unsafe { POLL_COUNT += x } + POLL_COUNT.fetch_add(x, std::sync::atomic::Ordering::Relaxed); } }); cx.spawn(async { for x in 0..10 { tokio::time::sleep(Duration::from_micros(25)).await; - unsafe { POLL_COUNT += x * 2 } + POLL_COUNT.fetch_add(x * 2, std::sync::atomic::Ordering::Relaxed); } }); }); From d82b2f226f23cbcf8f7b8cb5f817de5e37bdfce3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 12:50:28 -0700 Subject: [PATCH 17/31] Kill previous CI jobs when new pushed --- .github/workflows/docs stable.yml | 4 ++++ .github/workflows/docs.yml | 4 ++++ .github/workflows/macos.yml | 4 ++++ .github/workflows/main.yml | 4 ++++ .github/workflows/miri.yml | 4 +++- .github/workflows/playwright.yml | 5 +++++ .github/workflows/windows.yml | 4 ++++ 7 files changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs stable.yml b/.github/workflows/docs stable.yml index a2d4e2c07..069d4308a 100644 --- a/.github/workflows/docs stable.yml +++ b/.github/workflows/docs stable.yml @@ -3,6 +3,10 @@ name: docs stable on: workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build-deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dafe0384d..3a452e770 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,6 +8,10 @@ on: branches: - master +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build-deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index eef04d521..471bdd715 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -24,6 +24,10 @@ on: - lib.rs - Cargo.toml +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: test: if: github.event.pull_request.draft == false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b37e63aa4..a95af8574 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,6 +27,10 @@ on: - lib.rs - Cargo.toml +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: check: if: github.event.pull_request.draft == false diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index 0146e98bc..e8aab0732 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -38,7 +38,9 @@ env: # - tokio-stream/Cargo.toml # rust_min: 1.49.0 - +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: test: diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7fe524bb3..41fa92fa7 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -7,6 +7,11 @@ on: defaults: run: working-directory: ./playwright-tests + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: test: if: github.event.pull_request.draft == false diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 368df1630..f5792cf1e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -24,6 +24,10 @@ on: - lib.rs - Cargo.toml +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: test: if: github.event.pull_request.draft == false From 31cde43d6d6c54295ff20065f304366cae5f464b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 13:18:45 -0700 Subject: [PATCH 18/31] Add caching to windows CI and test iOS and android --- .github/workflows/macos.yml | 42 ---------------- .github/workflows/main.yml | 73 +++++++++++++++++++++++++++ .github/workflows/windows.yml | 93 ----------------------------------- 3 files changed, 73 insertions(+), 135 deletions(-) delete mode 100644 .github/workflows/macos.yml delete mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 471bdd715..000000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: macOS tests - -on: - push: - branches: - - master - paths: - - packages/** - - examples/** - - src/** - - .github/** - - lib.rs - - Cargo.toml - - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - master - paths: - - packages/** - - examples/** - - src/** - - .github/** - - lib.rs - - Cargo.toml - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - test: - if: github.event.pull_request.draft == false - name: Test Suite - runs-on: macos-latest - steps: - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - uses: actions/checkout@v3 - - run: | - cargo test --all --tests - cargo test --package fermi --release diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a95af8574..8db251254 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,6 +83,78 @@ jobs: - uses: actions/checkout@v3 - run: cargo clippy --workspace --examples --tests -- -D warnings + test: + runs-on: ${{ matrix.platform.os }} + strategy: + matrix: + platform: + - { + target: x86_64-pc-windows-msvc, + os: windows-latest, + toolchain: '1.65.0', + cross: false, + command: 'test --all --tests' + } + - { + target: x86_64-unknown-linux-gnu, + os: ubuntu-latest, + toolchain: '1.65.0', + cross: false, + command: 'test --all --tests' + } + - { + target: x86_64-apple-darwin, + os: macos-latest, + toolchain: '1.65.0', + cross: false, + command: 'test --all --tests' + } + - { + target: aarch64-apple-ios, + os: macos-latest, + toolchain: '1.65.0', + cross: false, + command: 'build --package dioxus-mobile' + } + - { + target: aarch64-linux-android, + os: ubuntu-latest, + toolchain: '1.65.0', + cross: true, + command: 'build --package dioxus-mobile' + } + + steps: + - uses: actions/checkout@v2 + + - name: install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.platform.toolchain }} + target: ${{ matrix.platform.target }} + override: true + default: true + + - name: install Linux dependencies + if: contains(matrix.platform.target, 'unknown-linux') + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: core -> ../target + save-if: ${{ matrix.features.key == 'all' }} + + - name: test + uses: actions-rs/cargo@v1 + with: + use-cross: ${{ matrix.platform.cross }} + command: ${{ matrix.platform.command }} + args: --target ${{ matrix.platform.target }} + + + # Coverage is disabled until we can fix it # coverage: # name: Coverage @@ -103,3 +175,4 @@ jobs: # uses: codecov/codecov-action@v2 # with: # fail_ci_if_error: false + diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index f5792cf1e..000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: windows - -on: - push: - branches: - - master - paths: - - packages/** - - examples/** - - src/** - - .github/** - - lib.rs - - Cargo.toml - - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - master - paths: - - packages/** - - examples/** - - src/** - - .github/** - - lib.rs - - Cargo.toml - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - test: - if: github.event.pull_request.draft == false - runs-on: windows-latest - name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }}) - env: - CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} - strategy: - # https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits - # There's a limit of 60 concurrent jobs across all repos in the rust-lang organization. - # In order to prevent overusing too much of that 60 limit, we throttle the - # number of rustfmt jobs that will run concurrently. - # max-parallel: - # fail-fast: false - matrix: - target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc] - cfg_release_channel: [stable] - - steps: - # The Windows runners have autocrlf enabled by default - # which causes failures for some of rustfmt's line-ending sensitive tests - - name: disable git eol translation - run: git config --global core.autocrlf false - - # Run build - - name: Install Rustup using win.rustup.rs - run: | - # Disable the download progress bar which can cause perf issues - $ProgressPreference = "SilentlyContinue" - Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe - .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none - del rustup-init.exe - rustup target add ${{ matrix.target }} - shell: powershell - - - name: Add mingw64 to path for x86_64-gnu - run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH - if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly' - shell: bash - - # - name: checkout - # uses: actions/checkout@v3 - # with: - # path: C:/dioxus.git - # fetch-depth: 1 - - # we need to use the C drive as the working directory - - - name: Checkout - run: | - mkdir C:/dioxus.git - git clone https://github.com/dioxuslabs/dioxus.git C:/dioxus.git --depth 1 - - - name: test - working-directory: C:/dioxus.git - run: | - rustc -Vv - cargo -V - set RUST_BACKTRACE=1 - cargo build --all --tests --examples - cargo test --all --tests - cargo test --package fermi --release - shell: cmd From d9f0b149688085659e3ea02987c51197aead6be3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 13:22:33 -0700 Subject: [PATCH 19/31] Rename CI field to enable wasm + linux --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8db251254..280cf8c73 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,7 +83,7 @@ jobs: - uses: actions/checkout@v3 - run: cargo clippy --workspace --examples --tests -- -D warnings - test: + matrix_test: runs-on: ${{ matrix.platform.os }} strategy: matrix: From 27a6babbd7625d5cbf2a886c05f9d8098ecbeefd Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 13:26:34 -0700 Subject: [PATCH 20/31] Fix syntax error in yml --- .github/workflows/main.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 280cf8c73..e2b01f525 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -93,35 +93,40 @@ jobs: os: windows-latest, toolchain: '1.65.0', cross: false, - command: 'test --all --tests' + command: 'test', + args: '--all --tests' } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, toolchain: '1.65.0', cross: false, - command: 'test --all --tests' + command: 'test', + args: '--all --tests' } - { target: x86_64-apple-darwin, os: macos-latest, toolchain: '1.65.0', cross: false, - command: 'test --all --tests' + command: 'test', + args: '--all --tests' } - { target: aarch64-apple-ios, os: macos-latest, toolchain: '1.65.0', cross: false, - command: 'build --package dioxus-mobile' + command: 'build', + args: '--package dioxus-mobile' } - { target: aarch64-linux-android, os: ubuntu-latest, toolchain: '1.65.0', cross: true, - command: 'build --package dioxus-mobile' + command: 'build', + args: '--package dioxus-mobile' } steps: @@ -151,7 +156,7 @@ jobs: with: use-cross: ${{ matrix.platform.cross }} command: ${{ matrix.platform.command }} - args: --target ${{ matrix.platform.target }} + args: --target ${{ matrix.platform.target }} ${{ matrix.platform.args }} From 90d23714da373cb824a0dd9674b4b746966b3069 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 13:44:38 -0700 Subject: [PATCH 21/31] Fix clippy around pathbuf --- packages/cli/src/server/desktop/mod.rs | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 93bd4e9e9..9b79bdea9 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -11,7 +11,6 @@ use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; use interprocess_docfix::local_socket::LocalSocketListener; use std::{ - path::PathBuf, process::{Child, Command}, sync::{Arc, Mutex, RwLock}, }; @@ -138,19 +137,7 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { None, ); - #[cfg(target_os = "macos")] - { - // On unix, if you force quit the application, it can leave the file socket open - // This will cause the local socket listener to fail to open - // We check if the file socket is already open from an old session and then delete it - let paths = ["./dioxusin", "./@dioxusin"]; - for path in paths { - let path = PathBuf::from(path); - if path.exists() { - let _ = std::fs::remove_file(path); - } - } - } + clear_paths(); match LocalSocketListener::bind("@dioxusin") { Ok(local_socket_stream) => { @@ -212,6 +199,21 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { Ok(()) } +fn clear_paths() { + if cfg!(target_os = "macos") { + // On unix, if you force quit the application, it can leave the file socket open + // This will cause the local socket listener to fail to open + // We check if the file socket is already open from an old session and then delete it + let paths = ["./dioxusin", "./@dioxusin"]; + for path in paths { + let path = std::path::PathBuf::from(path); + if path.exists() { + let _ = std::fs::remove_file(path); + } + } + } +} + fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { if let Ok(msg) = serde_json::to_string(&msg) { if channel.write_all(msg.as_bytes()).is_err() { From b1a4ac48d4c069e9f8d70af6f3dfe12b8023eea5 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 13:52:27 -0700 Subject: [PATCH 22/31] Move filedialog code into cfg-ed out folder --- packages/desktop/src/file_upload.rs | 75 +++++++++++++++-------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/packages/desktop/src/file_upload.rs b/packages/desktop/src/file_upload.rs index 778c209ac..7b5f08fc3 100644 --- a/packages/desktop/src/file_upload.rs +++ b/packages/desktop/src/file_upload.rs @@ -14,42 +14,6 @@ pub(crate) struct FileDialogRequest { pub bubbles: bool, } -fn get_file_event_for_folder(request: &FileDialogRequest, dialog: rfd::FileDialog) -> Vec { - if request.multiple { - dialog.pick_folders().into_iter().flatten().collect() - } else { - dialog.pick_folder().into_iter().collect() - } -} - -fn get_file_event_for_file( - request: &FileDialogRequest, - mut dialog: rfd::FileDialog, -) -> Vec { - let filters: Vec<_> = request - .accept - .as_deref() - .unwrap_or_default() - .split(',') - .filter_map(|s| Filters::from_str(s).ok()) - .collect(); - - let file_extensions: Vec<_> = filters - .iter() - .flat_map(|f| f.as_extensions().into_iter()) - .collect(); - - dialog = dialog.add_filter("name", file_extensions.as_slice()); - - let files: Vec<_> = if request.multiple { - dialog.pick_files().into_iter().flatten().collect() - } else { - dialog.pick_file().into_iter().collect() - }; - - files -} - #[cfg(not(any( target_os = "windows", target_os = "macos", @@ -73,6 +37,45 @@ pub(crate) fn get_file_event(_request: &FileDialogRequest) -> Vec { target_os = "openbsd" ))] pub(crate) fn get_file_event(request: &FileDialogRequest) -> Vec { + fn get_file_event_for_folder( + request: &FileDialogRequest, + dialog: rfd::FileDialog, + ) -> Vec { + if request.multiple { + dialog.pick_folders().into_iter().flatten().collect() + } else { + dialog.pick_folder().into_iter().collect() + } + } + + fn get_file_event_for_file( + request: &FileDialogRequest, + mut dialog: rfd::FileDialog, + ) -> Vec { + let filters: Vec<_> = request + .accept + .as_deref() + .unwrap_or_default() + .split(',') + .filter_map(|s| Filters::from_str(s).ok()) + .collect(); + + let file_extensions: Vec<_> = filters + .iter() + .flat_map(|f| f.as_extensions().into_iter()) + .collect(); + + dialog = dialog.add_filter("name", file_extensions.as_slice()); + + let files: Vec<_> = if request.multiple { + dialog.pick_files().into_iter().flatten().collect() + } else { + dialog.pick_file().into_iter().collect() + }; + + files + } + let dialog = rfd::FileDialog::new(); if request.directory { From c954a116eb87a41ff5a5cc935afe798bc433a10a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 14:23:34 -0700 Subject: [PATCH 23/31] Fix race condition in use_future --- packages/hooks/src/usefuture.rs | 115 ++++++-------------------------- 1 file changed, 21 insertions(+), 94 deletions(-) diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 18bf50310..4d90adbd4 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -1,12 +1,8 @@ #![allow(missing_docs)] use dioxus_core::{ScopeState, TaskId}; -use std::{ - any::Any, - cell::{Cell, RefCell}, - future::{Future, IntoFuture}, - rc::Rc, - sync::Arc, -}; +use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc}; + +use crate::{use_state, UseState}; /// A future that resolves to a value. /// @@ -31,43 +27,30 @@ where F: Future + 'static, D: UseFutureDep, { + let val = use_state(cx, || None); + let state = cx.use_hook(move || UseFuture { update: cx.schedule_update(), needs_regen: Cell::new(true), - values: Default::default(), - task: Cell::new(None), + state: val.clone(), + task: Default::default(), dependencies: Vec::new(), - waker: Default::default(), }); - *state.waker.borrow_mut() = None; - if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { - // We don't need regen anymore - state.needs_regen.set(false); + // kill the old one, if it exists + if let Some(task) = state.task.take() { + cx.remove_future(task); + } // Create the new future let fut = future(dependencies.out()); - - // Clone in our cells - let values = state.values.clone(); - let schedule_update = state.update.clone(); - let waker = state.waker.clone(); - - // Cancel the current future - if let Some(current) = state.task.take() { - cx.remove_future(current); - } + let val = val.clone(); + let task = state.task.clone(); state.task.set(Some(cx.push_future(async move { - let res = fut.await; - values.borrow_mut().push(Box::leak(Box::new(res))); - - // if there's a waker, we dont re-render the component. Instead we just progress that future - match waker.borrow().as_ref() { - Some(waker) => waker.wake_by_ref(), - None => schedule_update(), - } + val.set(Some(fut.await)); + task.take(); }))); } @@ -80,21 +63,12 @@ pub enum FutureState<'a, T> { Regenerating(&'a T), // the old value } -pub struct UseFuture { +pub struct UseFuture { update: Arc, needs_regen: Cell, - task: Cell>, + task: Rc>>, dependencies: Vec>, - waker: Rc>>, - values: Rc>>, -} - -impl Drop for UseFuture { - fn drop(&mut self) { - for value in self.values.take().into_iter() { - drop(unsafe { Box::from_raw(value) }) - } - } + state: UseState>, } pub enum UseFutureState<'a, T> { @@ -120,30 +94,16 @@ impl UseFuture { } } - // clears the value in the future slot without starting the future over - pub fn clear(&self) -> Option { - todo!() - // (self.update)(); - // self.slot.replace(None) - } - // Manually set the value in the future slot without starting the future over - pub fn set(&self, _new_value: T) { - // self.slot.set(Some(new_value)); - // self.needs_regen.set(true); - // (self.update)(); - todo!() + pub fn set(&self, new_value: T) { + self.state.set(Some(new_value)); } /// Return any value, even old values if the future has not yet resolved. /// /// If the future has never completed, the returned value will be `None`. pub fn value(&self) -> Option<&T> { - self.values - .borrow_mut() - .last() - .cloned() - .map(|x| unsafe { &*x }) + self.state.current_val.as_ref().as_ref() } /// Get the ID of the future in Dioxus' internal scheduler @@ -169,35 +129,6 @@ impl UseFuture { } } -impl<'a, T> IntoFuture for &'a UseFuture { - type Output = &'a T; - type IntoFuture = UseFutureAwait<'a, T>; - fn into_future(self) -> Self::IntoFuture { - UseFutureAwait { hook: self } - } -} - -pub struct UseFutureAwait<'a, T> { - hook: &'a UseFuture, -} - -impl<'a, T> Future for UseFutureAwait<'a, T> { - type Output = &'a T; - - fn poll( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - match self.hook.values.borrow_mut().last().cloned() { - Some(value) => std::task::Poll::Ready(unsafe { &*value }), - None => { - self.hook.waker.replace(Some(cx.waker().clone())); - std::task::Poll::Pending - } - } - } -} - pub trait UseFutureDep: Sized + Clone { type Out; fn out(&self) -> Self::Out; @@ -343,10 +274,6 @@ mod tests { let blah = "asd"; }); - let g2 = a.await; - - let g = fut.await; - todo!() } } From afa86877358594146e1aaf37b1033d90c14add1a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 16:22:19 -0700 Subject: [PATCH 24/31] Bump rust version to 1.69 --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e2b01f525..a710505db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,7 +91,7 @@ jobs: - { target: x86_64-pc-windows-msvc, os: windows-latest, - toolchain: '1.65.0', + toolchain: '1.69.0', cross: false, command: 'test', args: '--all --tests' @@ -99,7 +99,7 @@ jobs: - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, - toolchain: '1.65.0', + toolchain: '1.69.0', cross: false, command: 'test', args: '--all --tests' @@ -107,7 +107,7 @@ jobs: - { target: x86_64-apple-darwin, os: macos-latest, - toolchain: '1.65.0', + toolchain: '1.69.0', cross: false, command: 'test', args: '--all --tests' @@ -115,7 +115,7 @@ jobs: - { target: aarch64-apple-ios, os: macos-latest, - toolchain: '1.65.0', + toolchain: '1.69.0', cross: false, command: 'build', args: '--package dioxus-mobile' @@ -123,7 +123,7 @@ jobs: - { target: aarch64-linux-android, os: ubuntu-latest, - toolchain: '1.65.0', + toolchain: '1.69.0', cross: true, command: 'build', args: '--package dioxus-mobile' From b6ea722d9452b41afcd63d4d9b33d2a92d7fbf77 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 16:59:20 -0700 Subject: [PATCH 25/31] fix some edge cases around multiline attributes --- packages/autofmt/Cargo.toml | 2 +- packages/autofmt/src/collect_macros.rs | 170 ++----------------------- packages/autofmt/src/component.rs | 9 +- packages/autofmt/src/element.rs | 21 ++- packages/autofmt/src/expr.rs | 44 +++---- packages/autofmt/src/lib.rs | 20 ++- packages/cli/src/cli/autoformat.rs | 16 ++- 7 files changed, 77 insertions(+), 205 deletions(-) diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index 254b60093..a76b0fcd4 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react"] dioxus-rsx = { workspace = true } proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" -syn = { version = "1.0.11", features = ["full", "extra-traits"] } +syn = { version = "1.0.11", features = ["full", "extra-traits", "visit"] } serde = { version = "1.0.136", features = ["derive"] } prettyplease = { package = "prettier-please", version = "0.1.16", features = [ "verbatim", diff --git a/packages/autofmt/src/collect_macros.rs b/packages/autofmt/src/collect_macros.rs index ebb6314e2..8c2fe22f8 100644 --- a/packages/autofmt/src/collect_macros.rs +++ b/packages/autofmt/src/collect_macros.rs @@ -3,178 +3,24 @@ //! Returns all macros that match a pattern. You can use this information to autoformat them later use proc_macro2::LineColumn; -use syn::{Block, Expr, File, Item, Macro, Stmt}; +use syn::{visit::Visit, File, Macro}; type CollectedMacro<'a> = &'a Macro; -pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec>) { - for item in file.items.iter() { - collect_from_item(item, macros); - } +pub fn collect_from_file<'a, 'b>(file: &'a File, macros: &'b mut Vec>) { + MacroCollector::visit_file(&mut MacroCollector { macros }, file); } -pub fn collect_from_item<'a>(item: &'a Item, macros: &mut Vec>) { - match item { - Item::Fn(f) => collect_from_block(&f.block, macros), - - // Ignore macros if they're not rsx or render - Item::Macro(macro_) => { - if macro_.mac.path.segments[0].ident == "rsx" - || macro_.mac.path.segments[0].ident == "render" - { - macros.push(¯o_.mac); - } - } - - // Currently disabled since we're not focused on autoformatting these - Item::Impl(_imp) => {} - Item::Trait(_) => {} - - // Global-ish things - Item::Static(f) => collect_from_expr(&f.expr, macros), - Item::Const(f) => collect_from_expr(&f.expr, macros), - Item::Mod(s) => { - if let Some((_, block)) = &s.content { - for item in block { - collect_from_item(item, macros); - } - } - } - - // None of these we can really do anything with at the item level - Item::Macro2(_) - | Item::Enum(_) - | Item::ExternCrate(_) - | Item::ForeignMod(_) - | Item::TraitAlias(_) - | Item::Type(_) - | Item::Struct(_) - | Item::Union(_) - | Item::Use(_) - | Item::Verbatim(_) => {} - _ => {} - } +struct MacroCollector<'a, 'b> { + macros: &'a mut Vec>, } -pub fn collect_from_block<'a>(block: &'a Block, macros: &mut Vec>) { - for stmt in &block.stmts { - match stmt { - Stmt::Item(item) => collect_from_item(item, macros), - Stmt::Local(local) => { - if let Some((_eq, init)) = &local.init { - collect_from_expr(init, macros); - } - } - Stmt::Expr(exp) | Stmt::Semi(exp, _) => collect_from_expr(exp, macros), - } +impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> { + fn visit_macro(&mut self, i: &'b Macro) { + self.macros.push(i); } } -pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec>) { - // collect an expr from the exprs, descending into blocks - match expr { - Expr::Macro(macro_) => { - if macro_.mac.path.segments[0].ident == "rsx" - || macro_.mac.path.segments[0].ident == "render" - { - macros.push(¯o_.mac); - } - } - - Expr::MethodCall(e) => { - collect_from_expr(&e.receiver, macros); - for expr in e.args.iter() { - collect_from_expr(expr, macros); - } - } - Expr::Assign(exp) => { - collect_from_expr(&exp.left, macros); - collect_from_expr(&exp.right, macros); - } - - Expr::Async(b) => collect_from_block(&b.block, macros), - Expr::Block(b) => collect_from_block(&b.block, macros), - Expr::Closure(c) => collect_from_expr(&c.body, macros), - Expr::Let(l) => collect_from_expr(&l.expr, macros), - Expr::Unsafe(u) => collect_from_block(&u.block, macros), - Expr::Loop(l) => collect_from_block(&l.body, macros), - - Expr::Call(c) => { - collect_from_expr(&c.func, macros); - for expr in c.args.iter() { - collect_from_expr(expr, macros); - } - } - - Expr::ForLoop(b) => { - collect_from_expr(&b.expr, macros); - collect_from_block(&b.body, macros); - } - Expr::If(f) => { - collect_from_expr(&f.cond, macros); - collect_from_block(&f.then_branch, macros); - if let Some((_, else_branch)) = &f.else_branch { - collect_from_expr(else_branch, macros); - } - } - Expr::Yield(y) => { - if let Some(expr) = &y.expr { - collect_from_expr(expr, macros); - } - } - - Expr::Return(r) => { - if let Some(expr) = &r.expr { - collect_from_expr(expr, macros); - } - } - - Expr::Match(l) => { - collect_from_expr(&l.expr, macros); - for arm in l.arms.iter() { - if let Some((_, expr)) = &arm.guard { - collect_from_expr(expr, macros); - } - - collect_from_expr(&arm.body, macros); - } - } - - Expr::While(w) => { - collect_from_expr(&w.cond, macros); - collect_from_block(&w.body, macros); - } - - // don't both formatting these for now - Expr::Array(_) - | Expr::AssignOp(_) - | Expr::Await(_) - | Expr::Binary(_) - | Expr::Box(_) - | Expr::Break(_) - | Expr::Cast(_) - | Expr::Continue(_) - | Expr::Field(_) - | Expr::Group(_) - | Expr::Index(_) - | Expr::Lit(_) - | Expr::Paren(_) - | Expr::Path(_) - | Expr::Range(_) - | Expr::Reference(_) - | Expr::Repeat(_) - | Expr::Struct(_) - | Expr::Try(_) - | Expr::TryBlock(_) - | Expr::Tuple(_) - | Expr::Type(_) - | Expr::Unary(_) - | Expr::Verbatim(_) => {} - - _ => {} - }; -} - pub fn byte_offset(input: &str, location: LineColumn) -> usize { let mut offset = 0; for _ in 1..location.line { diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index d5d92b79a..8cf36811d 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -165,7 +165,14 @@ impl Writer<'_> { match &field.content { ContentField::ManExpr(exp) => { let out = prettyplease::unparse_expr(exp); - write!(self.out, "{name}: {out}")?; + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + write!(self.out, "{name}: {first}")?; + for line in lines { + self.out.new_line()?; + self.out.indented_tab()?; + write!(self.out, "{line}")?; + } } ContentField::Formatted(s) => { write!( diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index c541c8f5c..dbc336401 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -216,7 +216,26 @@ impl Writer<'_> { } ElementAttr::AttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); - write!(self.out, "{name}: {out}")?; + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + + // a one-liner for whatever reason + // Does not need a new line + if lines.peek().is_none() { + write!(self.out, "{name}: {first}")?; + } else { + writeln!(self.out, "{name}: {first}")?; + + while let Some(line) = lines.next() { + self.out.indented_tab()?; + write!(self.out, "{line}")?; + if lines.peek().is_none() { + write!(self.out, "")?; + } else { + writeln!(self.out)?; + } + } + } } ElementAttr::CustomAttrText { name, value } => { diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index ddc0892f8..56f2e2254 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -27,41 +27,29 @@ impl Writer<'_> { // If the expr is multiline, we want to collect all of its lines together and write them out properly // This involves unshifting the first line if it's aligned let first_line = &self.src[start.line - 1]; - write!( - self.out, - "{}", - &first_line[start.column - 1..first_line.len()].trim() - )?; + write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?; - let first_prefix = &self.src[start.line - 1][..start.column]; - let offset = match first_prefix.trim() { - "" => 0, - _ => first_prefix - .chars() - .rev() - .take_while(|c| c.is_whitespace()) - .count() as isize, - }; + let prev_block_indent_level = crate::leading_whitespaces(&first_line) / 4; for (id, line) in self.src[start.line..end.line].iter().enumerate() { writeln!(self.out)?; - // trim the leading whitespace - let line = match id { - x if x == (end.line - start.line) - 1 => &line[..end.column], - _ => line, + // check if this is the last line + let line = { + if id == (end.line - start.line) - 1 { + &line[..end.column] + } else { + line + } }; - if offset < 0 { - for _ in 0..-offset { - write!(self.out, " ")?; - } + // trim the leading whitespace + let previous_indent = crate::leading_whitespaces(line) / 4; + let offset = previous_indent.saturating_sub(prev_block_indent_level); + let required_indent = self.out.indent + offset; + self.out.write_tabs(required_indent)?; - write!(self.out, "{line}")?; - } else { - let offset = offset as usize; - let right = &line[offset..]; - write!(self.out, "{right}")?; - } + let line = line.trim_start(); + write!(self.out, "{line}")?; } Ok(()) diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 0d2426a0b..5f7e4da03 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -58,7 +58,7 @@ pub fn fmt_file(contents: &str) -> Vec { let mut writer = Writer::new(contents); - // Dont parse nested macros + // Don't parse nested macros let mut end_span = LineColumn { column: 0, line: 0 }; for item in macros { let macro_path = &item.path.segments[0].ident; @@ -68,16 +68,11 @@ pub fn fmt_file(contents: &str) -> Vec { continue; } - // item.parse_body::(); let body = item.parse_body::().unwrap(); let rsx_start = macro_path.span().start(); - writer.out.indent = &writer.src[rsx_start.line - 1] - .chars() - .take_while(|c| *c == ' ') - .count() - / 4; + writer.out.indent = leading_whitespaces(&writer.src[rsx_start.line - 1]) / 4; write_body(&mut writer, &body); @@ -230,3 +225,14 @@ pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::f let display = DisplayIfmt(input); write!(writable, "{}", display) } + +pub fn leading_whitespaces(input: &str) -> usize { + input + .chars() + .map_while(|c| match c { + ' ' => Some(1), + '\t' => Some(4), + _ => None, + }) + .sum() +} diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index ea2c27148..a0c0a8066 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -109,11 +109,17 @@ async fn autoformat_project(check: bool) -> Result<()> { }) .await; - if res.is_err() { - eprintln!("error formatting file: {}", _path.display()); + match res { + Err(err) => { + eprintln!("error formatting file: {}\n{err}", _path.display()); + None + } + Ok(Err(err)) => { + eprintln!("error formatting file: {}\n{err}", _path.display()); + None + } + Ok(Ok(res)) => Some(res), } - - res }) .collect::>() .collect::>() @@ -122,7 +128,7 @@ async fn autoformat_project(check: bool) -> Result<()> { let files_formatted: usize = counts .into_iter() .map(|f| match f { - Ok(Ok(res)) => res, + Some(res) => res, _ => 0, }) .sum(); From 9d942f1162d3fe6dfaaca92102ace9bb66c8ebd8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 17:20:48 -0700 Subject: [PATCH 26/31] make msrv 1.66 --- packages/router-macro/src/segment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-macro/src/segment.rs b/packages/router-macro/src/segment.rs index 2f7455a3f..62dea59a7 100644 --- a/packages/router-macro/src/segment.rs +++ b/packages/router-macro/src/segment.rs @@ -236,7 +236,7 @@ pub(crate) fn create_error_type( match segment { RouteSegment::Static(index) => { error_variants.push(quote! { #error_name(String) }); - display_match.push(quote! { Self::#error_name(found) => write!(f, "Static segment '{}' did not match instead found '{found}'", #index)? }); + display_match.push(quote! { Self::#error_name(found) => write!(f, "Static segment '{}' did not match instead found '{}'", #index, found)? }); } RouteSegment::Dynamic(ident, ty) => { let missing_error = segment.missing_error_name().unwrap(); From 330950140c0e7477584506f568d1022f6394e4b8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 19 Jul 2023 18:16:32 -0700 Subject: [PATCH 27/31] fix clippy --- packages/autofmt/src/collect_macros.rs | 2 +- packages/autofmt/src/expr.rs | 2 +- packages/autofmt/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/autofmt/src/collect_macros.rs b/packages/autofmt/src/collect_macros.rs index 8c2fe22f8..10e0d185c 100644 --- a/packages/autofmt/src/collect_macros.rs +++ b/packages/autofmt/src/collect_macros.rs @@ -7,7 +7,7 @@ use syn::{visit::Visit, File, Macro}; type CollectedMacro<'a> = &'a Macro; -pub fn collect_from_file<'a, 'b>(file: &'a File, macros: &'b mut Vec>) { +pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec>) { MacroCollector::visit_file(&mut MacroCollector { macros }, file); } diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index 56f2e2254..b0257f8c3 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -29,7 +29,7 @@ impl Writer<'_> { let first_line = &self.src[start.line - 1]; write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?; - let prev_block_indent_level = crate::leading_whitespaces(&first_line) / 4; + let prev_block_indent_level = crate::leading_whitespaces(first_line) / 4; for (id, line) in self.src[start.line..end.line].iter().enumerate() { writeln!(self.out)?; diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 5f7e4da03..08e2eadd7 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -72,7 +72,7 @@ pub fn fmt_file(contents: &str) -> Vec { let rsx_start = macro_path.span().start(); - writer.out.indent = leading_whitespaces(&writer.src[rsx_start.line - 1]) / 4; + writer.out.indent = leading_whitespaces(writer.src[rsx_start.line - 1]) / 4; write_body(&mut writer, &body); From 4240f8428ce4e2b9a16017c8d1dd24b592dfaa8a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 18:40:43 -0700 Subject: [PATCH 28/31] Fix: dont use bumpslab anymore, just box scopestates --- packages/core/Cargo.toml | 2 - packages/core/src/arena.rs | 10 ++-- packages/core/src/diff.rs | 18 +++--- packages/core/src/scheduler/wait.rs | 2 +- packages/core/src/scope_arena.rs | 16 ++--- packages/core/src/scopes.rs | 92 ----------------------------- packages/core/src/virtual_dom.rs | 14 ++--- packages/core/tests/fuzzing.rs | 4 ++ packages/core/tests/suspense.rs | 21 ++++--- packages/core/tests/task.rs | 1 + 10 files changed, 48 insertions(+), 132 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index a077a7379..3b85a8d2d 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -33,8 +33,6 @@ log = { workspace = true } # Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } -bumpslab = { version = "0.2.0" } - [dev-dependencies] tokio = { workspace = true, features = ["full"] } dioxus = { workspace = true } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 04ffb50d1..b6254136d 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -91,21 +91,21 @@ impl VirtualDom { // Note: This will not remove any ids from the arena pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) { self.dirty_scopes.remove(&DirtyScope { - height: self.scopes[id].height, + height: self.scopes[id.0].height, id, }); self.ensure_drop_safety(id); if recursive { - if let Some(root) = self.scopes[id].try_root_node() { + if let Some(root) = self.scopes[id.0].try_root_node() { if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } { self.drop_scope_inner(node) } } } - let scope = &mut self.scopes[id]; + let scope = &mut self.scopes[id.0]; // Drop all the hooks once the children are dropped // this means we'll drop hooks bottom-up @@ -116,7 +116,7 @@ impl VirtualDom { scope.tasks.remove(task_id); } - self.scopes.remove(id); + self.scopes.remove(id.0); } fn drop_scope_inner(&mut self, node: &VNode) { @@ -137,7 +137,7 @@ impl VirtualDom { /// Descend through the tree, removing any borrowed props and listeners pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) { - let scope = &self.scopes[scope_id]; + let scope = &self.scopes[scope_id.0]; // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we // run the hooks (which hold an &mut Reference) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 74988210a..7865224aa 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -15,7 +15,7 @@ use DynamicNode::*; impl<'b> VirtualDom { pub(super) fn diff_scope(&mut self, scope: ScopeId) { - let scope_state = &mut self.scopes[scope]; + let scope_state = &mut self.scopes[scope.0]; self.scope_stack.push(scope); unsafe { @@ -191,7 +191,7 @@ impl<'b> VirtualDom { right.scope.set(Some(scope_id)); // copy out the box for both - let old = self.scopes[scope_id].props.as_ref(); + let old = self.scopes[scope_id.0].props.as_ref(); let new: Box = right.props.take().unwrap(); let new: Box = unsafe { std::mem::transmute(new) }; @@ -203,14 +203,14 @@ impl<'b> VirtualDom { } // First, move over the props from the old to the new, dropping old props in the process - self.scopes[scope_id].props = Some(new); + self.scopes[scope_id.0].props = Some(new); // Now run the component and diff it self.run_scope(scope_id); self.diff_scope(scope_id); self.dirty_scopes.remove(&DirtyScope { - height: self.scopes[scope_id].height, + height: self.scopes[scope_id.0].height, id: scope_id, }); } @@ -714,7 +714,7 @@ impl<'b> VirtualDom { Component(comp) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { RenderReturn::Ready(node) => self.push_all_real_nodes(node), RenderReturn::Aborted(_node) => todo!(), } @@ -915,13 +915,13 @@ impl<'b> VirtualDom { .expect("VComponents to always have a scope"); // Remove the component from the dom - match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { RenderReturn::Ready(t) => self.remove_node(t, gen_muts), RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts), }; // Restore the props back to the vcomponent in case it gets rendered again - let props = self.scopes[scope].props.take(); + let props = self.scopes[scope.0].props.take(); *comp.props.borrow_mut() = unsafe { std::mem::transmute(props) }; // Now drop all the resouces @@ -936,7 +936,7 @@ impl<'b> VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { RenderReturn::Ready(t) => self.find_first_element(t), _ => todo!("cannot handle nonstandard nodes"), } @@ -952,7 +952,7 @@ impl<'b> VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.scopes[scope].root_node().extend_lifetime_ref() } { + match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } { RenderReturn::Ready(t) => self.find_last_element(t), _ => todo!("cannot handle nonstandard nodes"), } diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 0a7dce207..b475c6751 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -20,7 +20,7 @@ impl VirtualDom { // If the task completes... if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { // Remove it from the scope so we dont try to double drop it when the scope dropes - let scope = &self.scopes[task.scope]; + let scope = &self.scopes[task.scope.0]; scope.spawned_tasks.borrow_mut().remove(&id); // Remove it from the scheduler diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index e8134c238..b298a8996 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -16,9 +16,9 @@ impl VirtualDom { let parent = self.acquire_current_scope_raw(); let entry = self.scopes.vacant_entry(); let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) }; - let id = entry.key(); + let id = ScopeId(entry.key()); - entry.insert(ScopeState { + entry.insert(Box::new(ScopeState { parent, id, height, @@ -35,13 +35,13 @@ impl VirtualDom { shared_contexts: Default::default(), borrowed_props: Default::default(), attributes_to_drop: Default::default(), - }) + })) } fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> { let id = self.scope_stack.last().copied()?; - let scope = self.scopes.get(id)?; - Some(scope) + let scope = self.scopes.get(id.0)?; + Some(scope.as_ref()) } pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { @@ -51,9 +51,9 @@ impl VirtualDom { self.ensure_drop_safety(scope_id); let new_nodes = unsafe { - self.scopes[scope_id].previous_frame().bump_mut().reset(); + self.scopes[scope_id.0].previous_frame().bump_mut().reset(); - let scope = &self.scopes[scope_id]; + let scope = &self.scopes[scope_id.0]; scope.suspended.set(false); scope.hook_idx.set(0); @@ -65,7 +65,7 @@ impl VirtualDom { props.render(scope).extend_lifetime() }; - let scope = &self.scopes[scope_id]; + let scope = &self.scopes[scope_id.0]; // We write on top of the previous frame and then make it the current by pushing the generation forward let frame = scope.previous_frame(); diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 33fb9aac5..9470c3d0d 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -9,15 +9,12 @@ use crate::{ AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId, }; use bumpalo::{boxed::Box as BumpBox, Bump}; -use bumpslab::{BumpSlab, Slot}; use rustc_hash::FxHashSet; -use slab::{Slab, VacantEntry}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell, UnsafeCell}, fmt::{Arguments, Debug}, future::Future, - ops::{Index, IndexMut}, rc::Rc, sync::Arc, }; @@ -65,95 +62,6 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct ScopeId(pub usize); -/// A thin wrapper around a BumpSlab that uses ids to index into the slab. -pub(crate) struct ScopeSlab { - slab: BumpSlab, - // a slab of slots of stable pointers to the ScopeState in the bump slab - entries: Slab>, -} - -impl Drop for ScopeSlab { - fn drop(&mut self) { - // Bump slab doesn't drop its contents, so we need to do it manually - for slot in self.entries.drain() { - self.slab.remove(slot); - } - } -} - -impl Default for ScopeSlab { - fn default() -> Self { - Self { - slab: BumpSlab::new(), - entries: Slab::new(), - } - } -} - -impl ScopeSlab { - pub(crate) fn get(&self, id: ScopeId) -> Option<&ScopeState> { - self.entries.get(id.0).map(|slot| unsafe { &*slot.ptr() }) - } - - pub(crate) fn get_mut(&mut self, id: ScopeId) -> Option<&mut ScopeState> { - self.entries - .get(id.0) - .map(|slot| unsafe { &mut *slot.ptr_mut() }) - } - - pub(crate) fn vacant_entry(&mut self) -> ScopeSlabEntry { - let entry = self.entries.vacant_entry(); - ScopeSlabEntry { - slab: &mut self.slab, - entry, - } - } - - pub(crate) fn remove(&mut self, id: ScopeId) { - self.slab.remove(self.entries.remove(id.0)); - } - - pub(crate) fn contains(&self, id: ScopeId) -> bool { - self.entries.contains(id.0) - } - - pub(crate) fn iter(&self) -> impl Iterator { - self.entries.iter().map(|(_, slot)| unsafe { &*slot.ptr() }) - } -} - -pub(crate) struct ScopeSlabEntry<'a> { - slab: &'a mut BumpSlab, - entry: VacantEntry<'a, Slot<'static, ScopeState>>, -} - -impl<'a> ScopeSlabEntry<'a> { - pub(crate) fn key(&self) -> ScopeId { - ScopeId(self.entry.key()) - } - - pub(crate) fn insert(self, scope: ScopeState) -> &'a ScopeState { - let slot = self.slab.push(scope); - // this is safe because the slot is only ever accessed with the lifetime of the borrow of the slab - let slot = unsafe { std::mem::transmute(slot) }; - let entry = self.entry.insert(slot); - unsafe { &*entry.ptr() } - } -} - -impl Index for ScopeSlab { - type Output = ScopeState; - fn index(&self, id: ScopeId) -> &Self::Output { - self.get(id).unwrap() - } -} - -impl IndexMut for ScopeSlab { - fn index_mut(&mut self, id: ScopeId) -> &mut Self::Output { - self.get_mut(id).unwrap() - } -} - /// A component's state separate from its props. /// /// This struct exists to provide a common interface for all scopes without relying on generics. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index ee6e9c5d6..68a4e5a1b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -5,7 +5,7 @@ use crate::{ any_props::VProps, arena::{ElementId, ElementRef}, - innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg, ScopeSlab}, + innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, mutations::Mutation, nodes::RenderReturn, nodes::{Template, TemplateId}, @@ -176,7 +176,7 @@ use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc}; pub struct VirtualDom { // Maps a template path to a map of byteindexes to templates pub(crate) templates: FxHashMap>>, - pub(crate) scopes: ScopeSlab, + pub(crate) scopes: Slab>, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Rc, @@ -281,14 +281,14 @@ impl VirtualDom { /// /// This is useful for inserting or removing contexts from a scope, or rendering out its root node pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> { - self.scopes.get(id) + self.scopes.get(id.0).map(|f| f.as_ref()) } /// Get the single scope at the top of the VirtualDom tree that will always be around /// /// This scope has a ScopeId of 0 and is the root of the tree pub fn base_scope(&self) -> &ScopeState { - self.scopes.get(ScopeId(0)).unwrap() + self.get_scope(ScopeId(0)).unwrap() } /// Build the virtualdom with a global context inserted into the base scope @@ -303,7 +303,7 @@ impl VirtualDom { /// /// Whenever the VirtualDom "works", it will re-render this scope pub fn mark_dirty(&mut self, id: ScopeId) { - if let Some(scope) = self.scopes.get(id) { + if let Some(scope) = self.get_scope(id) { let height = scope.height; self.dirty_scopes.insert(DirtyScope { height, id }); } @@ -496,7 +496,7 @@ impl VirtualDom { pub fn replace_template(&mut self, template: Template<'static>) { self.register_template_first_byte_index(template); // iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine - for scope in self.scopes.iter() { + for (_, scope) in self.scopes.iter() { if let Some(RenderReturn::Ready(sync)) = scope.try_root_node() { if sync.template.get().name.rsplit_once(':').unwrap().0 == template.name.rsplit_once(':').unwrap().0 @@ -601,7 +601,7 @@ impl VirtualDom { self.dirty_scopes.remove(&dirty); // If the scope doesn't exist for whatever reason, then we should skip it - if !self.scopes.contains(dirty.id) { + if !self.scopes.contains(dirty.id.0) { continue; } diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index 3c617dc02..f193aa2f7 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -1,3 +1,5 @@ +#![cfg(not(miri))] + use dioxus::prelude::Props; use dioxus_core::*; use std::{cell::Cell, collections::HashSet}; @@ -314,6 +316,7 @@ fn create_random_element(cx: Scope) -> Element { } // test for panics when creating random nodes and templates +#[cfg(not(miri))] #[test] fn create() { for _ in 0..1000 { @@ -325,6 +328,7 @@ fn create() { // test for panics when diffing random nodes // This test will change the template every render which is not very realistic, but it helps stress the system +#[cfg(not(miri))] #[test] fn diff() { for _ in 0..100000 { diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 861b609ca..7b2516447 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -1,17 +1,22 @@ use dioxus::prelude::*; -#[tokio::test] -async fn it_works() { +#[test] +fn it_works() { // wait just a moment, not enough time for the boundary to resolve - let mut dom = VirtualDom::new(app); - _ = dom.rebuild(); - dom.wait_for_suspense().await; - let out = dioxus_ssr::pre_render(&dom); + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(async { + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + dom.wait_for_suspense().await; + let out = dioxus_ssr::pre_render(&dom); - assert_eq!(out, "
Waiting for... child
"); + assert_eq!(out, "
Waiting for... child
"); - dbg!(out); + dbg!(out); + }); } fn app(cx: Scope) -> Element { diff --git a/packages/core/tests/task.rs b/packages/core/tests/task.rs index 316cea6dd..1d190ccaf 100644 --- a/packages/core/tests/task.rs +++ b/packages/core/tests/task.rs @@ -5,6 +5,7 @@ use std::{sync::atomic::AtomicUsize, time::Duration}; static POLL_COUNT: AtomicUsize = AtomicUsize::new(0); +#[cfg(not(miri))] #[tokio::test] async fn it_works() { let mut dom = VirtualDom::new(app); From af363b6bb547c500368fcfab561365e7a3831f91 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 18:42:03 -0700 Subject: [PATCH 29/31] Unleash all core tests with miri --- .github/workflows/miri.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index e8aab0732..b1d772512 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -81,7 +81,7 @@ jobs: # #[tokio::main] that calls epoll_create1 that Miri does not support. # run: cargo miri test --features full --lib --no-fail-fast run: | - cargo miri test --package dioxus-core --test miri_stress -- --exact --nocapture + cargo miri test --package dioxus-core -- --exact --nocapture cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture # working-directory: tokio From 462338e49ff7cf7bb7744a1470098777c5096cdc Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 18:48:17 -0700 Subject: [PATCH 30/31] Remove linux from matrix since we already test it with the wasmpack test --- .github/workflows/main.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a710505db..5f1c761bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -96,14 +96,6 @@ jobs: command: 'test', args: '--all --tests' } - - { - target: x86_64-unknown-linux-gnu, - os: ubuntu-latest, - toolchain: '1.69.0', - cross: false, - command: 'test', - args: '--all --tests' - } - { target: x86_64-apple-darwin, os: macos-latest, @@ -140,12 +132,6 @@ jobs: override: true default: true - - name: install Linux dependencies - if: contains(matrix.platform.target, 'unknown-linux') - run: | - sudo apt-get update - sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev - - uses: Swatinem/rust-cache@v2 with: workspaces: core -> ../target From 223d588c8589b4886a9e57cd0b1db080d3e5e2dd Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 19 Jul 2023 19:33:39 -0700 Subject: [PATCH 31/31] Bump msrv to support simple logger --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5f1c761bb..0abb1404c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,7 +91,7 @@ jobs: - { target: x86_64-pc-windows-msvc, os: windows-latest, - toolchain: '1.69.0', + toolchain: '1.70.0', cross: false, command: 'test', args: '--all --tests' @@ -99,7 +99,7 @@ jobs: - { target: x86_64-apple-darwin, os: macos-latest, - toolchain: '1.69.0', + toolchain: '1.70.0', cross: false, command: 'test', args: '--all --tests' @@ -107,7 +107,7 @@ jobs: - { target: aarch64-apple-ios, os: macos-latest, - toolchain: '1.69.0', + toolchain: '1.70.0', cross: false, command: 'build', args: '--package dioxus-mobile' @@ -115,7 +115,7 @@ jobs: - { target: aarch64-linux-android, os: ubuntu-latest, - toolchain: '1.69.0', + toolchain: '1.70.0', cross: true, command: 'build', args: '--package dioxus-mobile'