From 9c2e713dae8440ca368cda4d2d8b55f6ab8f737b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 15 Aug 2023 09:30:43 -0500 Subject: [PATCH 01/73] fix recycling nodes in native core --- packages/native-core/src/dioxus.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 3e9ff9ee0..e5042f756 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -57,6 +57,12 @@ impl DioxusState { node.insert(ElementIdComponent(element_id)); if self.node_id_mapping.len() <= element_id.0 { self.node_id_mapping.resize(element_id.0 + 1, None); + } else { + if let Some(mut node) = + self.node_id_mapping[element_id.0].and_then(|id| node.real_dom_mut().get_mut(id)) + { + node.remove(); + } } self.node_id_mapping[element_id.0] = Some(node_id); } From 0d773eac63cbf8b6d9ffe459d9d5ca81c96bb2fc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 15 Aug 2023 10:37:27 -0500 Subject: [PATCH 02/73] fix clippy --- packages/native-core/src/dioxus.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index e5042f756..c245de4fc 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -57,13 +57,12 @@ impl DioxusState { node.insert(ElementIdComponent(element_id)); if self.node_id_mapping.len() <= element_id.0 { self.node_id_mapping.resize(element_id.0 + 1, None); - } else { - if let Some(mut node) = - self.node_id_mapping[element_id.0].and_then(|id| node.real_dom_mut().get_mut(id)) - { - node.remove(); - } + } else if let Some(mut node) = + self.node_id_mapping[element_id.0].and_then(|id| node.real_dom_mut().get_mut(id)) + { + node.remove(); } + self.node_id_mapping[element_id.0] = Some(node_id); } From df85b25548cfff642af2b4ac4a4521b53027cb52 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 21 Aug 2023 14:23:02 -0500 Subject: [PATCH 03/73] Add debug information to borrows and ownership in signals --- packages/generational-box/Cargo.toml | 1 + packages/generational-box/src/lib.rs | 445 ++++++++++++++++++++++++--- packages/signals/src/effect.rs | 2 +- packages/signals/src/impls.rs | 33 +- packages/signals/src/rt.rs | 33 +- packages/signals/src/signal.rs | 58 +++- 6 files changed, 497 insertions(+), 75 deletions(-) diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 6c5a5a841..cc0db9cec 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -15,3 +15,4 @@ rand = "0.8.5" [features] default = ["check_generation"] check_generation = [] +debug_borrows = [] diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 60bd41419..7d64a1ed3 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -2,9 +2,13 @@ #![warn(missing_docs)] use std::{ + any::Any, cell::{Cell, Ref, RefCell, RefMut}, - fmt::Debug, + error::Error, + fmt::{Debug, Display}, marker::PhantomData, + ops::{Deref, DerefMut}, + panic::Location, rc::Rc, }; @@ -153,6 +157,8 @@ pub struct GenerationalBox { raw: MemoryLocation, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, + #[cfg(any(debug_assertions, feature = "check_generation"))] + created_at: &'static std::panic::Location<'static>, _marker: PhantomData, } @@ -161,7 +167,7 @@ impl Debug for GenerationalBox { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( "{:?}@{:?}", - self.raw.data.as_ptr(), + self.raw.0.data.as_ptr(), self.generation ))?; #[cfg(not(any(debug_assertions, feature = "check_generation")))] @@ -175,7 +181,7 @@ impl GenerationalBox { fn validate(&self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.generation.get() == self.generation + self.raw.0.generation.get() == self.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -184,43 +190,51 @@ impl GenerationalBox { } /// Try to read the value. Returns None if the value is no longer valid. - pub fn try_read(&self) -> Option> { - self.validate() - .then(|| { - Ref::filter_map(self.raw.data.borrow(), |any| { - any.as_ref()?.downcast_ref::() - }) - .ok() - }) - .flatten() + #[track_caller] + pub fn try_read(&self) -> Result, BorrowError> { + if !self.validate() { + return Err(BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: self.created_at, + })); + } + self.raw.try_borrow( + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + self.created_at, + ) } /// Read the value. Panics if the value is no longer valid. - pub fn read(&self) -> Ref<'_, T> { + #[track_caller] + pub fn read(&self) -> GenerationalRef<'_, T> { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. - pub fn try_write(&self) -> Option> { - self.validate() - .then(|| { - RefMut::filter_map(self.raw.data.borrow_mut(), |any| { - any.as_mut()?.downcast_mut::() - }) - .ok() - }) - .flatten() + #[track_caller] + pub fn try_write(&self) -> Result, BorrowMutError> { + if !self.validate() { + return Err(BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: self.created_at, + })); + } + self.raw.try_borrow_mut( + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + self.created_at, + ) } /// Write the value. Panics if the value is no longer valid. - pub fn write(&self) -> RefMut<'_, T> { + #[track_caller] + pub fn write(&self) -> GenerationalRefMut<'_, T> { self.try_write().unwrap() } /// Set the value. Panics if the value is no longer valid. pub fn set(&self, value: T) { self.validate().then(|| { - *self.raw.data.borrow_mut() = Some(Box::new(value)); + *self.raw.0.data.borrow_mut() = Some(Box::new(value)); }); } @@ -228,7 +242,8 @@ impl GenerationalBox { pub fn ptr_eq(&self, other: &Self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation + self.raw.0.data.as_ptr() == other.raw.0.data.as_ptr() + && self.generation == other.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -246,26 +261,38 @@ impl Clone for GenerationalBox { } #[derive(Clone, Copy)] -struct MemoryLocation { - data: &'static RefCell>>, +struct MemoryLocation(&'static MemoryLocationInner); + +struct MemoryLocationInner { + data: RefCell>>, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: &'static Cell, + generation: Cell, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: RefCell>>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: Cell>>, } impl MemoryLocation { #[allow(unused)] fn drop(&self) { - let old = self.data.borrow_mut().take(); + let old = self.0.data.borrow_mut().take(); #[cfg(any(debug_assertions, feature = "check_generation"))] if old.is_some() { drop(old); - let new_generation = self.generation.get() + 1; - self.generation.set(new_generation); + let new_generation = self.0.generation.get() + 1; + self.0.generation.set(new_generation); } } - fn replace(&mut self, value: T) -> GenerationalBox { - let mut inner_mut = self.data.borrow_mut(); + fn replace_with_caller( + &mut self, + value: T, + #[cfg(any(debug_assertions, feature = "check_generation"))] caller: &'static Location< + 'static, + >, + ) -> GenerationalBox { + let mut inner_mut = self.0.data.borrow_mut(); let raw = Box::new(value); let old = inner_mut.replace(raw); @@ -273,10 +300,318 @@ impl MemoryLocation { GenerationalBox { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.generation.get(), + generation: self.0.generation.get(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + created_at: caller, _marker: PhantomData, } } + + #[track_caller] + fn try_borrow( + &self, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: &'static std::panic::Location<'static>, + ) -> Result, BorrowError> { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + self.0 + .borrowed_at + .borrow_mut() + .push(std::panic::Location::caller()); + match self.0.data.try_borrow() { + Ok(borrow) => match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::()) { + Ok(reference) => Ok(GenerationalRef { + inner: reference, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: std::panic::Location::caller(), + borrowed_from: self.0, + }, + }), + Err(_) => Err(BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at, + })), + }, + Err(_) => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: self.0.borrowed_mut_at.get().unwrap(), + })), + } + } + + #[track_caller] + fn try_borrow_mut( + &self, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: &'static std::panic::Location<'static>, + ) -> Result, BorrowMutError> { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + { + self.0 + .borrowed_mut_at + .set(Some(std::panic::Location::caller())); + } + match self.0.data.try_borrow_mut() { + Ok(borrow_mut) => { + match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::()) { + Ok(reference) => Ok(GenerationalRefMut { + inner: reference, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo { + borrowed_from: self.0, + }, + }), + Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at, + })), + } + } + Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: self.0.borrowed_at.borrow().clone(), + })), + } + } +} + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value. +pub enum BorrowError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowError::Dropped(error) => Display::fmt(error, f), + BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowError {} + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value mutably. +pub enum BorrowMutError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed. + AlreadyBorrowed(AlreadyBorrowedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowMutError::Dropped(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowMutError {} + +/// An error that can occur when trying to use a value that has been dropped. +#[derive(Debug, Copy, Clone)] +pub struct ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: &'static std::panic::Location<'static>, +} + +impl Display for ValueDroppedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was dropped.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_fmt(format_args!("created_at: {}", self.created_at))?; + Ok(()) + } +} + +impl std::error::Error for ValueDroppedError {} + +/// An error that can occur when trying to borrow a value that has already been borrowed mutably. +#[derive(Debug, Copy, Clone)] +pub struct AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: &'static std::panic::Location<'static>, +} + +impl Display for AlreadyBorrowedMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was already borrowed mutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?; + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedMutError {} + +/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably. +#[derive(Debug, Clone)] +pub struct AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: Vec<&'static std::panic::Location<'static>>, +} + +impl Display for AlreadyBorrowedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_str("borrowed_at:")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + for location in self.borrowed_at.iter() { + f.write_fmt(format_args!("\t{}", location))?; + } + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedError {} + +/// A reference to a value in a generational box. +pub struct GenerationalRef<'a, T: 'static> { + inner: Ref<'a, T>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo, +} + +impl<'a, T: 'static> GenerationalRef<'a, T> { + /// Map one ref type to another. + pub fn map(orig: GenerationalRef<'a, T>, f: F) -> GenerationalRef<'a, U> + where + F: FnOnce(&T) -> &U, + { + GenerationalRef { + inner: Ref::map(orig.inner, f), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: orig.borrow.borrowed_at, + borrowed_from: orig.borrow.borrowed_from, + }, + } + } + + /// Filter one ref type to another. + pub fn filter_map(orig: GenerationalRef<'a, T>, f: F) -> Option> + where + F: FnOnce(&T) -> Option<&U>, + { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } = orig; + Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: borrow.borrowed_at, + borrowed_from: borrow.borrowed_from, + }, + }) + } +} + +impl<'a, T: 'static> Deref for GenerationalRef<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +struct GenerationalRefBorrowInfo { + borrowed_at: &'static std::panic::Location<'static>, + borrowed_from: &'static MemoryLocationInner, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefBorrowInfo { + fn drop(&mut self) { + self.borrowed_from + .borrowed_at + .borrow_mut() + .retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _)); + } +} + +/// A mutable reference to a value in a generational box. +pub struct GenerationalRefMut<'a, T: 'static> { + inner: RefMut<'a, T>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo, +} + +impl<'a, T: 'static> GenerationalRefMut<'a, T> { + /// Map one ref type to another. + pub fn map(orig: GenerationalRefMut<'a, T>, f: F) -> GenerationalRefMut<'a, U> + where + F: FnOnce(&mut T) -> &mut U, + { + GenerationalRefMut { + inner: RefMut::map(orig.inner, f), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: orig.borrow, + } + } + + /// Filter one ref type to another. + pub fn filter_map( + orig: GenerationalRefMut<'a, T>, + f: F, + ) -> Option> + where + F: FnOnce(&mut T) -> Option<&mut U>, + { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } = orig; + RefMut::filter_map(inner, f) + .ok() + .map(|inner| GenerationalRefMut { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + }) + } +} + +impl<'a, T: 'static> Deref for GenerationalRefMut<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl<'a, T: 'static> DerefMut for GenerationalRefMut<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +struct GenerationalRefMutBorrowInfo { + borrowed_from: &'static MemoryLocationInner, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefMutBorrowInfo { + fn drop(&mut self) { + self.borrowed_from.borrowed_mut_at.take(); + } } /// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread. @@ -305,12 +640,16 @@ impl Store { if let Some(location) = self.recycled.borrow_mut().pop() { location } else { - let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None)); - MemoryLocation { - data, + let data: &'static MemoryLocationInner = self.bump.alloc(MemoryLocationInner { + data: RefCell::new(None), #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.bump.alloc(Cell::new(0)), - } + generation: Cell::new(0), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: Default::default(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: Default::default(), + }); + MemoryLocation(data) } } @@ -331,9 +670,31 @@ pub struct Owner { impl Owner { /// Insert a value into the store. The value will be dropped when the owner is dropped. + #[track_caller] pub fn insert(&self, value: T) -> GenerationalBox { let mut location = self.store.claim(); - let key = location.replace(value); + let key = location.replace_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + std::panic::Location::caller(), + ); + self.owned.borrow_mut().push(location); + key + } + + /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. + pub fn insert_with_caller( + &self, + value: T, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + caller: &'static std::panic::Location<'static>, + ) -> GenerationalBox { + let mut location = self.store.claim(); + let key = location.replace_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + caller, + ); self.owned.borrow_mut().push(location); key } @@ -344,7 +705,9 @@ impl Owner { GenerationalBox { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: location.generation.get(), + generation: location.0.generation.get(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + created_at: std::panic::Location::caller(), _marker: PhantomData, } } diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 0685938a0..0110ce4d1 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -83,7 +83,7 @@ impl Effect { /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. pub fn try_run(&self) { - if let Some(mut callback) = self.callback.try_write() { + if let Ok(mut callback) = self.callback.try_write() { { get_effect_stack().effects.borrow_mut().push(*self); } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 6427c2e2b..8029ac312 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,7 +1,7 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; - -use std::cell::{Ref, RefMut}; +use generational_box::GenerationalRef; +use generational_box::GenerationalRefMut; use std::{ fmt::{Debug, Display}, @@ -38,8 +38,8 @@ macro_rules! read_impls { impl $ty> { /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { - Ref::filter_map(self.read(), |v| v.get(index)).ok() + pub fn get(&self, index: usize) -> Option> { + GenerationalRef::filter_map(self.read(), |v| v.get(index)) } } @@ -52,9 +52,9 @@ macro_rules! read_impls { self.with(|v| v.clone()).unwrap() } - /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { - Ref::filter_map(self.read(), |v| v.as_ref()).ok() + /// Attempts to read the inner value of the Option. + pub fn as_ref(&self) -> Option> { + GenerationalRef::filter_map(self.read(), |v| v.as_ref()) } } }; @@ -182,19 +182,22 @@ macro_rules! write_impls { } /// Gets the value out of the Option, or inserts the given value if the Option is empty. - pub fn get_or_insert(&self, default: T) -> Ref<'_, T> { + pub fn get_or_insert(&self, default: T) -> GenerationalRef<'_, T> { self.get_or_insert_with(|| default) } /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. - pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> { + pub fn get_or_insert_with( + &self, + default: impl FnOnce() -> T, + ) -> GenerationalRef<'_, T> { let borrow = self.read(); if borrow.is_none() { drop(borrow); self.with_mut(|v| *v = Some(default())); - Ref::map(self.read(), |v| v.as_ref().unwrap()) + GenerationalRef::map(self.read(), |v| v.as_ref().unwrap()) } else { - Ref::map(borrow, |v| v.as_ref().unwrap()) + GenerationalRef::map(borrow, |v| v.as_ref().unwrap()) } } } @@ -238,15 +241,15 @@ impl IntoIterator for CopyValue> { impl CopyValue> { /// Write to an element in the inner vector. - pub fn get_mut(&self, index: usize) -> Option> { - RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok() + pub fn get_mut(&self, index: usize) -> Option> { + GenerationalRefMut::filter_map(self.write(), |v| v.get_mut(index)) } } impl CopyValue> { /// Deref the inner value mutably. - pub fn as_mut(&self) -> Option> { - RefMut::filter_map(self.write(), |v| v.as_mut()).ok() + pub fn as_mut(&self) -> Option> { + GenerationalRefMut::filter_map(self.write(), |v| v.as_mut()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 7322d43b4..acf598217 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,14 +1,15 @@ -use std::cell::{Ref, RefMut}; - +use std::panic::Location; use std::rc::Rc; use dioxus_core::prelude::{ - consume_context, consume_context_from_scope, current_scope_id, provide_context, + consume_context, consume_context_from_scope, current_scope_id, has_context, provide_context, provide_context_to_scope, provide_root_context, }; use dioxus_core::ScopeId; -use generational_box::{GenerationalBox, Owner, Store}; +use generational_box::{ + BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, Store, +}; fn current_store() -> Store { match consume_context() { @@ -21,7 +22,7 @@ fn current_store() -> Store { } fn current_owner() -> Rc { - match consume_context() { + match has_context() { Some(rt) => rt, None => { let owner = Rc::new(current_store().owner()); @@ -74,6 +75,7 @@ impl CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. + #[track_caller] pub fn new(value: T) -> Self { let owner = current_owner(); @@ -83,6 +85,15 @@ impl CopyValue { } } + pub(crate) fn new_with_caller(value: T, caller: &'static Location<'static>) -> Self { + let owner = current_owner(); + + Self { + value: owner.insert_with_caller(value, caller), + origin_scope: current_scope_id().expect("in a virtual dom"), + } + } + /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. pub fn new_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); @@ -108,22 +119,26 @@ impl CopyValue { } /// Try to read the value. If the value has been dropped, this will return None. - pub fn try_read(&self) -> Option> { + #[track_caller] + pub fn try_read(&self) -> Result, BorrowError> { self.value.try_read() } /// Read the value. If the value has been dropped, this will panic. - pub fn read(&self) -> Ref<'_, T> { + #[track_caller] + pub fn read(&self) -> GenerationalRef<'_, T> { self.value.read() } /// Try to write the value. If the value has been dropped, this will return None. - pub fn try_write(&self) -> Option> { + #[track_caller] + pub fn try_write(&self) -> Result, BorrowMutError> { self.value.try_write() } /// Write the value. If the value has been dropped, this will panic. - pub fn write(&self) -> RefMut<'_, T> { + #[track_caller] + pub fn write(&self) -> GenerationalRefMut<'_, T> { self.value.write() } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index a5599a1ce..1656f559e 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,6 +1,7 @@ use std::{ - cell::{Ref, RefCell, RefMut}, + cell::RefCell, ops::{Deref, DerefMut}, + panic::Location, rc::Rc, sync::Arc, }; @@ -9,6 +10,7 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; +use generational_box::{GenerationalRef, GenerationalRefMut}; use crate::{CopyValue, Effect}; @@ -43,8 +45,18 @@ use crate::{CopyValue, Effect}; /// } /// } /// ``` +#[track_caller] pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { - *cx.use_hook(|| Signal::new(f())) + #[cfg(debug_assertions)] + let caller = Location::caller(); + + *cx.use_hook(|| { + Signal::new_with_caller( + f(), + #[cfg(debug_assertions)] + caller, + ) + }) } #[derive(Clone)] @@ -134,6 +146,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + #[track_caller] pub fn new(value: T) -> Self { Self { inner: CopyValue::new(SignalData { @@ -145,6 +158,25 @@ impl Signal { } } + /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + fn new_with_caller( + value: T, + #[cfg(debug_assertions)] caller: &'static Location<'static>, + ) -> Self { + Self { + inner: CopyValue::new_with_caller( + SignalData { + subscribers: Default::default(), + effect_subscribers: Default::default(), + update_any: schedule_update_any().expect("in a virtual dom"), + value, + }, + #[cfg(debug_assertions)] + caller, + ), + } + } + /// Get the scope the signal was created in. pub fn origin_scope(&self) -> ScopeId { self.inner.origin_scope() @@ -152,7 +184,8 @@ impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. - pub fn read(&self) -> Ref { + #[track_caller] + pub fn read(&self) -> GenerationalRef { let inner = self.inner.read(); if let Some(effect) = Effect::current() { let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); @@ -176,14 +209,15 @@ impl Signal { } } } - Ref::map(inner, |v| &v.value) + GenerationalRef::map(inner, |v| &v.value) } /// Get a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn write(&self) -> Write<'_, T> { let inner = self.inner.write(); - let borrow = RefMut::map(inner, |v| &mut v.value); + let borrow = GenerationalRefMut::map(inner, |v| &mut v.value); Write { write: borrow, signal: SignalSubscriberDrop { signal: *self }, @@ -219,12 +253,14 @@ impl Signal { } /// Set the value of the signal. This will trigger an update on all subscribers. + #[track_caller] pub fn set(&self, value: T) { *self.write() = value; } /// Run a closure with a reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { let write = self.read(); f(&*write) @@ -232,6 +268,7 @@ impl Signal { /// Run a closure with a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { let mut write = self.write(); f(&mut *write) @@ -241,6 +278,7 @@ impl Signal { impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn value(&self) -> T { self.read().clone() } @@ -264,7 +302,7 @@ impl Drop for SignalSubscriberDrop { /// A mutable reference to a signal's value. pub struct Write<'a, T: 'static, I: 'static = T> { - write: RefMut<'a, T>, + write: GenerationalRefMut<'a, T>, signal: SignalSubscriberDrop, } @@ -273,7 +311,7 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> { pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> { let Self { write, signal } = myself; Write { - write: RefMut::map(write, f), + write: GenerationalRefMut::map(write, f), signal, } } @@ -284,7 +322,7 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> { f: impl FnOnce(&mut T) -> Option<&mut O>, ) -> Option> { let Self { write, signal } = myself; - let write = RefMut::filter_map(write, f).ok(); + let write = GenerationalRefMut::filter_map(write, f); write.map(|write| Write { write, signal }) } } @@ -320,11 +358,13 @@ impl ReadOnlySignal { } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> Ref { + #[track_caller] + pub fn read(&self) -> GenerationalRef { self.inner.read() } /// Run a closure with a reference to the signal's value. + #[track_caller] pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { self.inner.with(f) } From f09a2e2280f74a9ac4e50d298e6f665bad1f8ea2 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 21 Aug 2023 14:28:45 -0500 Subject: [PATCH 04/73] fix release builds --- packages/generational-box/Cargo.toml | 1 + packages/generational-box/src/lib.rs | 26 ++++++++++++-------------- packages/signals/src/rt.rs | 12 +++++++++--- packages/signals/src/signal.rs | 5 ++--- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index cc0db9cec..7151ec103 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -16,3 +16,4 @@ rand = "0.8.5" default = ["check_generation"] check_generation = [] debug_borrows = [] +debug_ownership = [] diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 7d64a1ed3..1f8cbbe11 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -8,7 +8,6 @@ use std::{ fmt::{Debug, Display}, marker::PhantomData, ops::{Deref, DerefMut}, - panic::Location, rc::Rc, }; @@ -157,7 +156,7 @@ pub struct GenerationalBox { raw: MemoryLocation, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, - #[cfg(any(debug_assertions, feature = "check_generation"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, _marker: PhantomData, } @@ -288,9 +287,8 @@ impl MemoryLocation { fn replace_with_caller( &mut self, value: T, - #[cfg(any(debug_assertions, feature = "check_generation"))] caller: &'static Location< - 'static, - >, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + caller: &'static std::panic::Location<'static>, ) -> GenerationalBox { let mut inner_mut = self.0.data.borrow_mut(); @@ -301,7 +299,7 @@ impl MemoryLocation { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: self.0.generation.get(), - #[cfg(any(debug_assertions, feature = "check_generation"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: caller, _marker: PhantomData, } @@ -310,7 +308,7 @@ impl MemoryLocation { #[track_caller] fn try_borrow( &self, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, ) -> Result, BorrowError> { #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -329,7 +327,7 @@ impl MemoryLocation { }, }), Err(_) => Err(BorrowError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at, })), }, @@ -343,7 +341,7 @@ impl MemoryLocation { #[track_caller] fn try_borrow_mut( &self, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, ) -> Result, BorrowMutError> { #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -363,7 +361,7 @@ impl MemoryLocation { }, }), Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at, })), } @@ -422,14 +420,14 @@ impl Error for BorrowMutError {} /// An error that can occur when trying to use a value that has been dropped. #[derive(Debug, Copy, Clone)] pub struct ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, } impl Display for ValueDroppedError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("Failed to borrow because the value was dropped.")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] f.write_fmt(format_args!("created_at: {}", self.created_at))?; Ok(()) } @@ -686,7 +684,7 @@ impl Owner { pub fn insert_with_caller( &self, value: T, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] caller: &'static std::panic::Location<'static>, ) -> GenerationalBox { let mut location = self.store.claim(); @@ -706,7 +704,7 @@ impl Owner { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: location.0.generation.get(), - #[cfg(any(debug_assertions, feature = "check_generation"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: std::panic::Location::caller(), _marker: PhantomData, } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index acf598217..aee3b0858 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,4 +1,3 @@ -use std::panic::Location; use std::rc::Rc; use dioxus_core::prelude::{ @@ -85,11 +84,18 @@ impl CopyValue { } } - pub(crate) fn new_with_caller(value: T, caller: &'static Location<'static>) -> Self { + pub(crate) fn new_with_caller( + value: T, + #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, + ) -> Self { let owner = current_owner(); Self { - value: owner.insert_with_caller(value, caller), + value: owner.insert_with_caller( + value, + #[cfg(debug_assertions)] + caller, + ), origin_scope: current_scope_id().expect("in a virtual dom"), } } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 1656f559e..70a809e85 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,7 +1,6 @@ use std::{ cell::RefCell, ops::{Deref, DerefMut}, - panic::Location, rc::Rc, sync::Arc, }; @@ -48,7 +47,7 @@ use crate::{CopyValue, Effect}; #[track_caller] pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { #[cfg(debug_assertions)] - let caller = Location::caller(); + let caller = std::panic::Location::caller(); *cx.use_hook(|| { Signal::new_with_caller( @@ -161,7 +160,7 @@ impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. fn new_with_caller( value: T, - #[cfg(debug_assertions)] caller: &'static Location<'static>, + #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, ) -> Self { Self { inner: CopyValue::new_with_caller( From 18f8daf0bdc30a6eb71ab7e91863442a13d47ca6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 21 Aug 2023 15:00:31 -0500 Subject: [PATCH 05/73] fix generational box tests --- packages/generational-box/README.md | 2 ++ packages/generational-box/src/lib.rs | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/generational-box/README.md b/packages/generational-box/README.md index 5ab7926ba..f8d4885b8 100644 --- a/packages/generational-box/README.md +++ b/packages/generational-box/README.md @@ -11,6 +11,8 @@ Three main types manage state in Generational Box: Example: ```rust +use generational_box::Store; + // Create a store for this thread let store = Store::default(); diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 1f8cbbe11..84a81769d 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -32,12 +32,12 @@ fn reused() { let first_ptr; { let owner = store.owner(); - first_ptr = owner.insert(1).raw.data.as_ptr(); + first_ptr = owner.insert(1).raw.0.data.as_ptr(); drop(owner); } { let owner = store.owner(); - let second_ptr = owner.insert(1234).raw.data.as_ptr(); + let second_ptr = owner.insert(1234).raw.0.data.as_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); } @@ -56,7 +56,10 @@ fn leaking_is_ok() { // don't drop the owner std::mem::forget(owner); } - assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string())); + assert_eq!( + key.try_read().as_deref().unwrap(), + &"hello world".to_string() + ); } #[test] @@ -71,7 +74,7 @@ fn drops() { key = owner.insert(data); // drop the owner } - assert!(key.try_read().is_none()); + assert!(key.try_read().is_err()); } #[test] @@ -132,7 +135,7 @@ fn fuzz() { println!("{:?}", path); for key in valid_keys.iter() { let value = key.read(); - println!("{:?}", value); + println!("{:?}", &*value); assert!(value.starts_with("hello world")); } #[cfg(any(debug_assertions, feature = "check_generation"))] From d994e3e7221b404b1a69c6f099a9b46a9e5a4eed Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 23 Sep 2023 07:57:41 -0500 Subject: [PATCH 06/73] make the layer module public in fullstack --- packages/fullstack/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index f2d3e5533..384560c0a 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -19,7 +19,7 @@ mod hooks; mod hot_reload; pub mod launch; #[cfg(feature = "ssr")] -mod layer; +pub mod layer; #[cfg(feature = "ssr")] mod render; #[cfg(feature = "ssr")] From d2f0f4b4b6009f995c9dcb1e228cb8f67f93d4a9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 25 Sep 2023 12:25:21 -0500 Subject: [PATCH 07/73] document layer and service --- packages/fullstack/src/layer.rs | 5 +++++ packages/fullstack/src/lib.rs | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/fullstack/src/layer.rs b/packages/fullstack/src/layer.rs index 10e67b3f5..60f76107b 100644 --- a/packages/fullstack/src/layer.rs +++ b/packages/fullstack/src/layer.rs @@ -3,7 +3,9 @@ use tracing_futures::Instrument; use http::{Request, Response}; +/// A layer that wraps a service. This can be used to add additional information to the request, or response on top of some other service pub trait Layer: Send + Sync + 'static { + /// Wrap a boxed service with this layer fn layer(&self, inner: BoxedService) -> BoxedService; } @@ -17,7 +19,9 @@ where } } +/// A service is a function that takes a request and returns an async response pub trait Service { + /// Run the service and produce a future that resolves to a response fn run( &mut self, req: http::Request, @@ -55,6 +59,7 @@ where } } +/// A boxed service is a type-erased service that can be used without knowing the underlying type pub struct BoxedService(pub Box); impl tower::Service> for BoxedService { diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 384560c0a..75707b931 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -19,7 +19,7 @@ mod hooks; mod hot_reload; pub mod launch; #[cfg(feature = "ssr")] -pub mod layer; +mod layer; #[cfg(feature = "ssr")] mod render; #[cfg(feature = "ssr")] @@ -40,6 +40,8 @@ pub mod prelude { #[cfg(not(feature = "ssr"))] pub use crate::html_storage::deserialize::get_root_props_from_document; pub use crate::launch::LaunchBuilder; + #[cfg(feature = "ssr")] + pub use crate::layer::{Layer, Service}; #[cfg(all(feature = "ssr", feature = "router"))] pub use crate::render::pre_cache_static_routes_with_props; #[cfg(feature = "ssr")] From 6e85ecea128d5d30aaadc6ae3f466623950b8c75 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 25 Oct 2023 10:23:23 -0500 Subject: [PATCH 08/73] fix use shared state lint in release mode --- packages/hooks/src/use_shared_state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 37226c759..0c0f43508 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -26,6 +26,7 @@ macro_rules! debug_location { } pub mod error { + #[cfg(debug_assertions)] fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String { locations .iter() From b9dae3e1e0c535225a64756e58dd7570caabf9ad Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 25 Oct 2023 15:06:08 -0500 Subject: [PATCH 09/73] don't ignore leaks in miri --- .github/workflows/miri.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index 4a467b9b2..f9ad3855f 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -86,8 +86,7 @@ jobs: # working-directory: tokio env: - # todo: disable memory leaks ignore - MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields PROPTEST_CASES: 10 # Cache the global cargo directory, but NOT the local `target` directory which From b99f081c0839defc9d9c61cb428f5e6b1464aab6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 25 Oct 2023 16:37:04 -0500 Subject: [PATCH 10/73] drop any attribute after rendering --- packages/core/src/arena.rs | 5 +---- packages/core/src/bump_frame.rs | 28 +++++++++++++++++++++++++--- packages/core/src/scope_arena.rs | 4 ++-- packages/core/src/scopes.rs | 12 +++++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index b7f768755..4f441bb81 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -164,16 +164,13 @@ impl VirtualDom { }); // Now that all the references are gone, we can safely drop our own references in our listeners. - let mut listeners = scope.attributes_to_drop.borrow_mut(); + let mut listeners = scope.attributes_to_drop_before_render.borrow_mut(); listeners.drain(..).for_each(|listener| { let listener = unsafe { &*listener }; match &listener.value { AttributeValue::Listener(l) => { _ = l.take(); } - AttributeValue::Any(a) => { - _ = a.take(); - } _ => (), } }); diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index 0fe7b3867..d4b3d2e07 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -1,10 +1,13 @@ use crate::nodes::RenderReturn; +use crate::{Attribute, AttributeValue}; use bumpalo::Bump; +use std::cell::RefCell; use std::cell::{Cell, UnsafeCell}; pub(crate) struct BumpFrame { pub bump: UnsafeCell, pub node: Cell<*const RenderReturn<'static>>, + pub(crate) attributes_to_drop_before_reset: RefCell>>, } impl BumpFrame { @@ -13,6 +16,7 @@ impl BumpFrame { Self { bump: UnsafeCell::new(bump), node: Cell::new(std::ptr::null()), + attributes_to_drop_before_reset: Default::default(), } } @@ -31,8 +35,26 @@ impl BumpFrame { unsafe { &*self.bump.get() } } - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn bump_mut(&self) -> &mut Bump { - unsafe { &mut *self.bump.get() } + pub(crate) fn add_attribute_to_drop(&self, attribute: *const Attribute<'static>) { + self.attributes_to_drop_before_reset + .borrow_mut() + .push(attribute); + } + + pub(crate) unsafe fn reset(&self) { + let mut attributes = self.attributes_to_drop_before_reset.borrow_mut(); + attributes.drain(..).for_each(|attribute| { + let attribute = unsafe { &*attribute }; + match &attribute.value { + AttributeValue::Any(l) => { + _ = l.take(); + } + _ => (), + } + }); + unsafe { + let bump = &mut *self.bump.get(); + bump.reset(); + } } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 1ed5b816c..397f5328e 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -35,7 +35,7 @@ impl VirtualDom { hook_idx: Default::default(), borrowed_props: Default::default(), - attributes_to_drop: Default::default(), + attributes_to_drop_before_render: Default::default(), })); let context = @@ -54,7 +54,7 @@ impl VirtualDom { let new_nodes = unsafe { let scope = &self.scopes[scope_id.0]; - scope.previous_frame().bump_mut().reset(); + scope.previous_frame().reset(); scope.context().suspended.set(false); diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 996529a0d..3fd2aac30 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -94,7 +94,7 @@ pub struct ScopeState { pub(crate) hook_idx: Cell, pub(crate) borrowed_props: RefCell>>, - pub(crate) attributes_to_drop: RefCell>>, + pub(crate) attributes_to_drop_before_render: RefCell>>, pub(crate) props: Option>>, } @@ -348,13 +348,19 @@ impl<'src> ScopeState { pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { let element = rsx.call(self); - let mut listeners = self.attributes_to_drop.borrow_mut(); + let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); for attr in element.dynamic_attrs { match attr.value { - AttributeValue::Any(_) | AttributeValue::Listener(_) => { + // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped + AttributeValue::Listener(_) => { let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; listeners.push(unbounded); } + // We need to drop any values manually to make sure that their drop implementation is called before the next render + AttributeValue::Any(_) => { + let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; + self.previous_frame().add_attribute_to_drop(unbounded); + } _ => (), } From 370c6cb9d2333b5bee1f5bafe92d9dad05d8f11f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 25 Oct 2023 16:47:04 -0500 Subject: [PATCH 11/73] fix clippy --- packages/core/src/arena.rs | 7 ++----- packages/core/src/bump_frame.rs | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 4f441bb81..785d4b174 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -167,11 +167,8 @@ impl VirtualDom { let mut listeners = scope.attributes_to_drop_before_render.borrow_mut(); listeners.drain(..).for_each(|listener| { let listener = unsafe { &*listener }; - match &listener.value { - AttributeValue::Listener(l) => { - _ = l.take(); - } - _ => (), + if let AttributeValue::Listener(l) = &listener.value { + _ = l.take(); } }); } diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index d4b3d2e07..bf630c605 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -45,11 +45,8 @@ impl BumpFrame { let mut attributes = self.attributes_to_drop_before_reset.borrow_mut(); attributes.drain(..).for_each(|attribute| { let attribute = unsafe { &*attribute }; - match &attribute.value { - AttributeValue::Any(l) => { - _ = l.take(); - } - _ => (), + if let AttributeValue::Any(l) = &attribute.value { + _ = l.take(); } }); unsafe { From eea4a02ef59c07fcd2e143e838a4080a8d59cb49 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 26 Oct 2023 13:23:12 -0500 Subject: [PATCH 12/73] add comments about the cause of the leak --- packages/core/src/bump_frame.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index 9ae11ab79..6927bd8bc 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -7,6 +7,8 @@ use std::cell::{Cell, UnsafeCell}; pub(crate) struct BumpFrame { pub bump: UnsafeCell, pub node: Cell<*const RenderReturn<'static>>, + + // The bump allocator will not call the destructor of the objects it allocated. Attributes and props need to have there destructor called, so we keep a list of them to drop before the bump allocator is reset. pub(crate) attributes_to_drop_before_reset: RefCell>>, pub(crate) props_to_drop_before_reset: RefCell>>, } @@ -43,6 +45,10 @@ impl BumpFrame { .push(attribute); } + /// Reset the bump allocator and drop all the attributes and props that were allocated in it. + /// + /// # Safety + /// The caller must insure that no reference to anything allocated in the bump allocator is available after this function is called. pub(crate) unsafe fn reset(&self) { let mut attributes = self.attributes_to_drop_before_reset.borrow_mut(); attributes.drain(..).for_each(|attribute| { From c5e647e97d0567e8362662a80ce474b95aa638ac Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 30 Oct 2023 08:39:37 -0500 Subject: [PATCH 13/73] add read untracked to signals --- packages/signals/src/signal.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 9213307ff..0ad96349d 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -200,6 +200,13 @@ impl Signal { Ref::map(inner, |v| &v.value) } + /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** + /// If the signal has been dropped, this will panic. + pub fn read_untracked(&self) -> Ref { + let inner = self.inner.read(); + Ref::map(inner, |v| &v.value) + } + /// Get a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. pub fn write(&self) -> Write<'_, T> { @@ -385,6 +392,12 @@ impl ReadOnlySignal { self.inner.read() } + /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** + /// If the signal has been dropped, this will panic. + pub fn read_untracked(&self) -> Ref { + self.inner.read_untracked() + } + /// Run a closure with a reference to the signal's value. pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { self.inner.with(f) From bf36fc6def522c75f0f608d5c6a779ca1477f1ec Mon Sep 17 00:00:00 2001 From: tigerros Date: Wed, 1 Nov 2023 14:14:07 +0100 Subject: [PATCH 14/73] Fix `#[component]` expansion Clippy warning (#1599) Adds a `#[allow(clippy::inline_always)]` attribute to the generated `__dx_inner_comp` function. --- .../core-macro/src/component_body_deserializers/component.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core-macro/src/component_body_deserializers/component.rs b/packages/core-macro/src/component_body_deserializers/component.rs index e43b1819f..36cda850c 100644 --- a/packages/core-macro/src/component_body_deserializers/component.rs +++ b/packages/core-macro/src/component_body_deserializers/component.rs @@ -31,6 +31,7 @@ fn get_out_comp_fn(orig_comp_fn: &ItemFn, cx_pat: &Pat) -> ItemFn { block: parse_quote! { { #[warn(non_snake_case)] + #[allow(clippy::inline_always)] #[inline(always)] #inner_comp_fn #inner_comp_ident (#cx_pat) From fdec47b90bc09945ccb5f26012b492f38587d872 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 1 Nov 2023 08:19:35 -0500 Subject: [PATCH 15/73] fix clippy --- packages/generational-box/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 628308340..f56904ede 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -485,7 +485,7 @@ pub struct GenerationalRef { borrow: GenerationalRefBorrowInfo, } -impl<'a, T: 'static> GenerationalRef { +impl GenerationalRef { /// Map one ref type to another. pub fn map(orig: GenerationalRef, f: F) -> GenerationalRef where @@ -522,7 +522,7 @@ impl<'a, T: 'static> GenerationalRef { } } -impl<'a, T: 'static> Deref for GenerationalRef { +impl Deref for GenerationalRef { type Target = T; fn deref(&self) -> &Self::Target { @@ -586,7 +586,7 @@ impl GenerationalRefMut { } } -impl<'a, T: 'static> Deref for GenerationalRefMut { +impl Deref for GenerationalRefMut { type Target = T; fn deref(&self) -> &Self::Target { @@ -594,7 +594,7 @@ impl<'a, T: 'static> Deref for GenerationalRefMut { } } -impl<'a, T: 'static> DerefMut for GenerationalRefMut { +impl DerefMut for GenerationalRefMut { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } From 52fb0801933eed40bea1c9edb58544693810e544 Mon Sep 17 00:00:00 2001 From: Alex Parrill Date: Tue, 31 Oct 2023 20:48:19 -0400 Subject: [PATCH 16/73] Use indentation settings in dx fmt and vscode extension Adds the ability to specify an indent string to public autofmt methods - either a sequence of spaces or a tab character. Get the indentation style and size from rustfmt for dx fmt, or from the editor settings for the vscode extension. Closes #1595 --- packages/autofmt/src/buffer.rs | 11 +- packages/autofmt/src/element.rs | 12 +- packages/autofmt/src/expr.rs | 6 +- packages/autofmt/src/indent.rs | 108 ++++++++++++++++++ packages/autofmt/src/lib.rs | 27 ++--- packages/autofmt/src/writer.rs | 4 +- packages/autofmt/tests/samples.rs | 2 +- packages/autofmt/tests/wrong.rs | 15 ++- .../wrong/{comments.rsx => comments-4sp.rsx} | 0 ...ments.wrong.rsx => comments-4sp.wrong.rsx} | 0 packages/autofmt/tests/wrong/comments-tab.rsx | 7 ++ .../tests/wrong/comments-tab.wrong.rsx | 5 + .../tests/wrong/{multi.rsx => multi-4sp.rsx} | 0 .../{multi.wrong.rsx => multi-4sp.wrong.rsx} | 0 packages/autofmt/tests/wrong/multi-tab.rsx | 3 + .../autofmt/tests/wrong/multi-tab.wrong.rsx | 5 + .../{multiexpr.rsx => multiexpr-4sp.rsx} | 0 ...expr.wrong.rsx => multiexpr-4sp.wrong.rsx} | 0 .../autofmt/tests/wrong/multiexpr-tab.rsx | 8 ++ .../tests/wrong/multiexpr-tab.wrong.rsx | 5 + packages/cli/src/cli/autoformat.rs | 68 ++++++++++- packages/extension/src/lib.rs | 46 ++++++-- packages/extension/src/main.ts | 2 +- 23 files changed, 283 insertions(+), 51 deletions(-) create mode 100644 packages/autofmt/src/indent.rs rename packages/autofmt/tests/wrong/{comments.rsx => comments-4sp.rsx} (100%) rename packages/autofmt/tests/wrong/{comments.wrong.rsx => comments-4sp.wrong.rsx} (100%) create mode 100644 packages/autofmt/tests/wrong/comments-tab.rsx create mode 100644 packages/autofmt/tests/wrong/comments-tab.wrong.rsx rename packages/autofmt/tests/wrong/{multi.rsx => multi-4sp.rsx} (100%) rename packages/autofmt/tests/wrong/{multi.wrong.rsx => multi-4sp.wrong.rsx} (100%) create mode 100644 packages/autofmt/tests/wrong/multi-tab.rsx create mode 100644 packages/autofmt/tests/wrong/multi-tab.wrong.rsx rename packages/autofmt/tests/wrong/{multiexpr.rsx => multiexpr-4sp.rsx} (100%) rename packages/autofmt/tests/wrong/{multiexpr.wrong.rsx => multiexpr-4sp.wrong.rsx} (100%) create mode 100644 packages/autofmt/tests/wrong/multiexpr-tab.rsx create mode 100644 packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index fea5a4a3f..f19a7d8dc 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -8,13 +8,14 @@ use std::fmt::{Result, Write}; use dioxus_rsx::IfmtInput; -use crate::write_ifmt; +use crate::{indent::IndentOptions, write_ifmt}; /// The output buffer that tracks indent and string #[derive(Debug, Default)] pub struct Buffer { pub buf: String, - pub indent: usize, + pub indent_level: usize, + pub indent: IndentOptions, } impl Buffer { @@ -31,16 +32,16 @@ impl Buffer { } pub fn tab(&mut self) -> Result { - self.write_tabs(self.indent) + self.write_tabs(self.indent_level) } pub fn indented_tab(&mut self) -> Result { - self.write_tabs(self.indent + 1) + self.write_tabs(self.indent_level + 1) } pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result { for _ in 0..num { - write!(self.buf, " ")? + write!(self.buf, "{}", self.indent.indent_str())? } Ok(()) } diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index dbc336401..27e7b31f1 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -66,7 +66,7 @@ impl Writer<'_> { // check if we have a lot of attributes let attr_len = self.is_short_attrs(attributes); - let is_short_attr_list = (attr_len + self.out.indent * 4) < 80; + let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80; let children_len = self.is_short_children(children); let is_small_children = children_len.is_some(); @@ -86,7 +86,7 @@ impl Writer<'_> { // if we have few children and few attributes, make it a one-liner if is_short_attr_list && is_small_children { - if children_len.unwrap() + attr_len + self.out.indent * 4 < 100 { + if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 { opt_level = ShortOptimization::Oneliner; } else { opt_level = ShortOptimization::PropsOnTop; @@ -185,11 +185,11 @@ impl Writer<'_> { } while let Some(attr) = attr_iter.next() { - self.out.indent += 1; + self.out.indent_level += 1; if !sameline { self.write_comments(attr.attr.start())?; } - self.out.indent -= 1; + self.out.indent_level -= 1; if !sameline { self.out.indented_tabbed_line()?; @@ -398,14 +398,14 @@ impl Writer<'_> { for idx in start.line..end.line { let line = &self.src[idx]; if line.trim().starts_with("//") { - for _ in 0..self.out.indent + 1 { + for _ in 0..self.out.indent_level + 1 { write!(self.out, " ")? } writeln!(self.out, "{}", line.trim()).unwrap(); } } - for _ in 0..self.out.indent { + for _ in 0..self.out.indent_level { write!(self.out, " ")? } diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index b0257f8c3..b7a93a4fc 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 = self.out.indent.count_indents(first_line); for (id, line) in self.src[start.line..end.line].iter().enumerate() { writeln!(self.out)?; @@ -43,9 +43,9 @@ impl Writer<'_> { }; // trim the leading whitespace - let previous_indent = crate::leading_whitespaces(line) / 4; + let previous_indent = self.out.indent.count_indents(line); let offset = previous_indent.saturating_sub(prev_block_indent_level); - let required_indent = self.out.indent + offset; + let required_indent = self.out.indent_level + offset; self.out.write_tabs(required_indent)?; let line = line.trim_start(); diff --git a/packages/autofmt/src/indent.rs b/packages/autofmt/src/indent.rs new file mode 100644 index 000000000..2cce7cf1e --- /dev/null +++ b/packages/autofmt/src/indent.rs @@ -0,0 +1,108 @@ +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IndentType { + Spaces, + Tabs, +} + +#[derive(Debug, Clone)] +pub struct IndentOptions { + width: usize, + indent_string: String, +} + +impl IndentOptions { + pub fn new(typ: IndentType, width: usize) -> Self { + assert_ne!(width, 0, "Cannot have an indent width of 0"); + Self { + width, + indent_string: match typ { + IndentType::Tabs => "\t".into(), + IndentType::Spaces => " ".repeat(width), + }, + } + } + + /// Gets a string containing one indent worth of whitespace + pub fn indent_str(&self) -> &str { + &self.indent_string + } + + /// Computes the line length in characters, counting tabs as the indent width. + pub fn line_length(&self, line: &str) -> usize { + line.chars() + .map(|ch| if ch == '\t' { self.width } else { 1 }) + .sum() + } + + /// Estimates how many times the line has been indented. + pub fn count_indents(&self, mut line: &str) -> usize { + let mut indent = 0; + while !line.is_empty() { + // Try to count tabs + let num_tabs = line.chars().take_while(|ch| *ch == '\t').count(); + if num_tabs > 0 { + indent += num_tabs; + line = &line[num_tabs..]; + continue; + } + + // Try to count spaces + let num_spaces = line.chars().take_while(|ch| *ch == ' ').count(); + if num_spaces >= self.width { + // Intentionally floor here to take only the amount of space that matches an indent + let num_space_indents = num_spaces / self.width; + indent += num_space_indents; + line = &line[num_space_indents * self.width..]; + continue; + } + + // Line starts with either non-indent characters or an unevent amount of spaces, + // so no more indent remains. + break; + } + indent + } +} + +impl Default for IndentOptions { + fn default() -> Self { + Self::new(IndentType::Spaces, 4) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn count_indents() { + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents("no indentation here!"), + 0 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"), + 1 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\tv += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\t v += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 2).count_indents(" v += 2"), + 2 + ); + } +} diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 64a342885..0c3fe6bb8 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -16,8 +16,11 @@ mod collect_macros; mod component; mod element; mod expr; +mod indent; mod writer; +pub use indent::{IndentOptions, IndentType}; + /// A modification to the original file to be applied by an IDE /// /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes. @@ -47,7 +50,7 @@ pub struct FormattedBlock { /// back to the file precisely. /// /// Nested blocks of RSX will be handled automatically -pub fn fmt_file(contents: &str) -> Vec { +pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec { let mut formatted_blocks = Vec::new(); let parsed = syn::parse_file(contents).unwrap(); @@ -61,6 +64,7 @@ pub fn fmt_file(contents: &str) -> Vec { } let mut writer = Writer::new(contents); + writer.out.indent = indent; // Don't parse nested macros let mut end_span = LineColumn { column: 0, line: 0 }; @@ -76,7 +80,10 @@ 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_level = writer + .out + .indent + .count_indents(writer.src[rsx_start.line - 1]); write_body(&mut writer, &body); @@ -159,12 +166,13 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option { buf.consume() } -pub fn fmt_block(block: &str, indent_level: usize) -> Option { +pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option { let body = syn::parse_str::(block).unwrap(); let mut buf = Writer::new(block); - buf.out.indent = indent_level; + buf.out.indent = indent; + buf.out.indent_level = indent_level; write_body(&mut buf, &body); @@ -230,14 +238,3 @@ 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/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 59f135592..bb333e8df 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -96,11 +96,11 @@ impl<'a> Writer<'a> { // Push out the indent level and write each component, line by line pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { - self.out.indent += 1; + self.out.indent_level += 1; self.write_body_no_indent(children)?; - self.out.indent -= 1; + self.out.indent_level -= 1; Ok(()) } diff --git a/packages/autofmt/tests/samples.rs b/packages/autofmt/tests/samples.rs index 8145b0e8e..9431d700e 100644 --- a/packages/autofmt/tests/samples.rs +++ b/packages/autofmt/tests/samples.rs @@ -12,7 +12,7 @@ macro_rules! twoway { #[test] fn $name() { let src = include_str!(concat!("./samples/", stringify!($name), ".rsx")); - let formatted = dioxus_autofmt::fmt_file(src); + let formatted = dioxus_autofmt::fmt_file(src, Default::default()); let out = dioxus_autofmt::apply_formats(src, formatted); // normalize line endings let out = out.replace("\r", ""); diff --git a/packages/autofmt/tests/wrong.rs b/packages/autofmt/tests/wrong.rs index 5a0fb8a87..145d03807 100644 --- a/packages/autofmt/tests/wrong.rs +++ b/packages/autofmt/tests/wrong.rs @@ -1,10 +1,12 @@ +use dioxus_autofmt::{IndentType, IndentOptions}; + macro_rules! twoway { - ($val:literal => $name:ident) => { + ($val:literal => $name:ident ($indent:expr)) => { #[test] fn $name() { let src_right = include_str!(concat!("./wrong/", $val, ".rsx")); let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx")); - let formatted = dioxus_autofmt::fmt_file(src_wrong); + let formatted = dioxus_autofmt::fmt_file(src_wrong, $indent); let out = dioxus_autofmt::apply_formats(src_wrong, formatted); // normalize line endings @@ -16,8 +18,11 @@ macro_rules! twoway { }; } -twoway!("comments" => comments); +twoway!("comments-4sp" => comments_4sp (IndentOptions::new(IndentType::Spaces, 4))); +twoway!("comments-tab" => comments_tab (IndentOptions::new(IndentType::Tabs, 4))); -twoway!("multi" => multi); +twoway!("multi-4sp" => multi_4sp (IndentOptions::new(IndentType::Spaces, 4))); +twoway!("multi-tab" => multi_tab (IndentOptions::new(IndentType::Tabs, 4))); -twoway!("multiexpr" => multiexpr); +twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4))); +twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4))); diff --git a/packages/autofmt/tests/wrong/comments.rsx b/packages/autofmt/tests/wrong/comments-4sp.rsx similarity index 100% rename from packages/autofmt/tests/wrong/comments.rsx rename to packages/autofmt/tests/wrong/comments-4sp.rsx diff --git a/packages/autofmt/tests/wrong/comments.wrong.rsx b/packages/autofmt/tests/wrong/comments-4sp.wrong.rsx similarity index 100% rename from packages/autofmt/tests/wrong/comments.wrong.rsx rename to packages/autofmt/tests/wrong/comments-4sp.wrong.rsx diff --git a/packages/autofmt/tests/wrong/comments-tab.rsx b/packages/autofmt/tests/wrong/comments-tab.rsx new file mode 100644 index 000000000..4c05e347e --- /dev/null +++ b/packages/autofmt/tests/wrong/comments-tab.rsx @@ -0,0 +1,7 @@ +rsx! { + div { + // Comments + class: "asdasd", + "hello world" + } +} diff --git a/packages/autofmt/tests/wrong/comments-tab.wrong.rsx b/packages/autofmt/tests/wrong/comments-tab.wrong.rsx new file mode 100644 index 000000000..eac96642d --- /dev/null +++ b/packages/autofmt/tests/wrong/comments-tab.wrong.rsx @@ -0,0 +1,5 @@ +rsx! { + div { + // Comments + class: "asdasd", "hello world" } +} diff --git a/packages/autofmt/tests/wrong/multi.rsx b/packages/autofmt/tests/wrong/multi-4sp.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multi.rsx rename to packages/autofmt/tests/wrong/multi-4sp.rsx diff --git a/packages/autofmt/tests/wrong/multi.wrong.rsx b/packages/autofmt/tests/wrong/multi-4sp.wrong.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multi.wrong.rsx rename to packages/autofmt/tests/wrong/multi-4sp.wrong.rsx diff --git a/packages/autofmt/tests/wrong/multi-tab.rsx b/packages/autofmt/tests/wrong/multi-tab.rsx new file mode 100644 index 000000000..1fb85c3d0 --- /dev/null +++ b/packages/autofmt/tests/wrong/multi-tab.rsx @@ -0,0 +1,3 @@ +fn app(cx: Scope) -> Element { + cx.render(rsx! { div { "hello world" } }) +} diff --git a/packages/autofmt/tests/wrong/multi-tab.wrong.rsx b/packages/autofmt/tests/wrong/multi-tab.wrong.rsx new file mode 100644 index 000000000..d818f9535 --- /dev/null +++ b/packages/autofmt/tests/wrong/multi-tab.wrong.rsx @@ -0,0 +1,5 @@ +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div {"hello world" } + }) +} diff --git a/packages/autofmt/tests/wrong/multiexpr.rsx b/packages/autofmt/tests/wrong/multiexpr-4sp.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multiexpr.rsx rename to packages/autofmt/tests/wrong/multiexpr-4sp.rsx diff --git a/packages/autofmt/tests/wrong/multiexpr.wrong.rsx b/packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multiexpr.wrong.rsx rename to packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx diff --git a/packages/autofmt/tests/wrong/multiexpr-tab.rsx b/packages/autofmt/tests/wrong/multiexpr-tab.rsx new file mode 100644 index 000000000..1fc3401c4 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr-tab.rsx @@ -0,0 +1,8 @@ +fn ItWroks() { + cx.render(rsx! { + div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", + left, + right + } + }) +} diff --git a/packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx b/packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx new file mode 100644 index 000000000..073107541 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx @@ -0,0 +1,5 @@ +fn ItWroks() { + cx.render(rsx! { + div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right } + }) +} diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index 1d1cbcd24..6c1ce04a1 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -1,3 +1,4 @@ +use dioxus_autofmt::{IndentOptions, IndentType}; use futures::{stream::FuturesUnordered, StreamExt}; use std::{fs, path::Path, process::exit}; @@ -35,7 +36,8 @@ impl Autoformat { } if let Some(raw) = self.raw { - if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0) { + let indent = indentation_for(".")?; + if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) { println!("{}", inner); } else { // exit process with error @@ -46,17 +48,21 @@ impl Autoformat { // Format single file if let Some(file) = self.file { - let file_content = if file == "-" { + let file_content; + let indent; + if file == "-" { + indent = indentation_for(".")?; let mut contents = String::new(); std::io::stdin().read_to_string(&mut contents)?; - Ok(contents) + file_content = Ok(contents); } else { - fs::read_to_string(&file) + indent = indentation_for(".")?; + file_content = fs::read_to_string(&file); }; match file_content { Ok(s) => { - let edits = dioxus_autofmt::fmt_file(&s); + let edits = dioxus_autofmt::fmt_file(&s, indent); let out = dioxus_autofmt::apply_formats(&s, edits); if file == "-" { print!("{}", out); @@ -93,6 +99,12 @@ async fn autoformat_project(check: bool) -> Result<()> { let mut files_to_format = vec![]; collect_rs_files(&crate_config.crate_dir, &mut files_to_format); + if files_to_format.is_empty() { + return Ok(()); + } + + let indent = indentation_for(&files_to_format[0])?; + let counts = files_to_format .into_iter() .filter(|file| { @@ -104,10 +116,11 @@ async fn autoformat_project(check: bool) -> Result<()> { }) .map(|path| async { let _path = path.clone(); + let _indent = indent.clone(); let res = tokio::spawn(async move { let contents = tokio::fs::read_to_string(&path).await?; - let edits = dioxus_autofmt::fmt_file(&contents); + let edits = dioxus_autofmt::fmt_file(&contents, _indent.clone()); let len = edits.len(); if !edits.is_empty() { @@ -151,6 +164,49 @@ async fn autoformat_project(check: bool) -> Result<()> { Ok(()) } +fn indentation_for(file_or_dir: impl AsRef) -> Result { + let out = std::process::Command::new("cargo") + .args(["fmt", "--", "--print-config", "current"]) + .arg(file_or_dir.as_ref()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::inherit()) + .output()?; + if !out.status.success() { + return Err(Error::CargoError("cargo fmt failed".into())); + } + + let config = String::from_utf8_lossy(&out.stdout); + + let hard_tabs = config + .lines() + .find(|line| line.starts_with("hard_tabs ")) + .and_then(|line| line.split_once('=')) + .map(|(_, value)| value.trim() == "true") + .ok_or_else(|| { + Error::RuntimeError("Could not find hard_tabs option in rustfmt config".into()) + })?; + let tab_spaces = config + .lines() + .find(|line| line.starts_with("tab_spaces ")) + .and_then(|line| line.split_once('=')) + .map(|(_, value)| value.trim().parse::()) + .ok_or_else(|| { + Error::RuntimeError("Could not find tab_spaces option in rustfmt config".into()) + })? + .map_err(|_| { + Error::RuntimeError("Could not parse tab_spaces option in rustfmt config".into()) + })?; + + Ok(IndentOptions::new( + if hard_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + tab_spaces, + )) +} + fn collect_rs_files(folder: &Path, files: &mut Vec) { let Ok(folder) = folder.read_dir() else { return; diff --git a/packages/extension/src/lib.rs b/packages/extension/src/lib.rs index abcdaf598..fb9cca8a9 100644 --- a/packages/extension/src/lib.rs +++ b/packages/extension/src/lib.rs @@ -1,17 +1,39 @@ //! This file exports functions into the vscode extension -use dioxus_autofmt::FormattedBlock; +use dioxus_autofmt::{FormattedBlock, IndentOptions, IndentType}; use wasm_bindgen::prelude::*; #[wasm_bindgen] -pub fn format_rsx(raw: String) -> String { - let block = dioxus_autofmt::fmt_block(&raw, 0); +pub fn format_rsx(raw: String, use_tabs: bool, indent_size: usize) -> String { + let block = dioxus_autofmt::fmt_block( + &raw, + 0, + IndentOptions::new( + if use_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + indent_size, + ), + ); block.unwrap() } #[wasm_bindgen] -pub fn format_selection(raw: String) -> String { - let block = dioxus_autofmt::fmt_block(&raw, 0); +pub fn format_selection(raw: String, use_tabs: bool, indent_size: usize) -> String { + let block = dioxus_autofmt::fmt_block( + &raw, + 0, + IndentOptions::new( + if use_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + indent_size, + ), + ); block.unwrap() } @@ -35,8 +57,18 @@ impl FormatBlockInstance { } #[wasm_bindgen] -pub fn format_file(contents: String) -> FormatBlockInstance { - let _edits = dioxus_autofmt::fmt_file(&contents); +pub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance { + let _edits = dioxus_autofmt::fmt_file( + &contents, + IndentOptions::new( + if use_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + indent_size, + ), + ); let out = dioxus_autofmt::apply_formats(&contents, _edits.clone()); FormatBlockInstance { new: out, _edits } } diff --git a/packages/extension/src/main.ts b/packages/extension/src/main.ts index fc12457ad..4ceee487c 100644 --- a/packages/extension/src/main.ts +++ b/packages/extension/src/main.ts @@ -90,7 +90,7 @@ function fmtDocument(document: vscode.TextDocument) { if (!editor) return; // Need an editor to apply text edits. const contents = editor.document.getText(); - const formatted = dioxus.format_file(contents); + const formatted = dioxus.format_file(contents, !editor.options.insertSpaces, editor.options.tabSize); // Replace the entire text document // Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using From 0f2923a385c94e13f53ba6920a632ed21d9fcc8d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Nov 2023 13:17:21 -0500 Subject: [PATCH 17/73] fix extension types --- packages/extension/src/main.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/extension/src/main.ts b/packages/extension/src/main.ts index 4ceee487c..76b7261f1 100644 --- a/packages/extension/src/main.ts +++ b/packages/extension/src/main.ts @@ -90,7 +90,13 @@ function fmtDocument(document: vscode.TextDocument) { if (!editor) return; // Need an editor to apply text edits. const contents = editor.document.getText(); - const formatted = dioxus.format_file(contents, !editor.options.insertSpaces, editor.options.tabSize); + let tabSize: number; + if (typeof editor.options.tabSize === 'number') { + tabSize = editor.options.tabSize; + } else { + tabSize = 4; + } + const formatted = dioxus.format_file(contents, !editor.options.insertSpaces, tabSize); // Replace the entire text document // Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using From 0aa4875ec0b32e85f8d2d4e23997dacb9ddec3b6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Nov 2023 13:59:46 -0500 Subject: [PATCH 18/73] fix formating --- packages/autofmt/tests/wrong.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/autofmt/tests/wrong.rs b/packages/autofmt/tests/wrong.rs index 145d03807..06a0f00d1 100644 --- a/packages/autofmt/tests/wrong.rs +++ b/packages/autofmt/tests/wrong.rs @@ -1,4 +1,4 @@ -use dioxus_autofmt::{IndentType, IndentOptions}; +use dioxus_autofmt::{IndentOptions, IndentType}; macro_rules! twoway { ($val:literal => $name:ident ($indent:expr)) => { From 7590cf822d892c360ed24b0825a5db034584e2e6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Nov 2023 21:00:43 -0500 Subject: [PATCH 19/73] fix the scroll event on the web renderer --- packages/web/src/dom.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 4fa5c9444..f45d6558a 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -294,7 +294,7 @@ pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) - "select" => Rc::new(SelectionData {}), "touchcancel" | "touchend" | "touchmove" | "touchstart" => Rc::new(TouchData::from(event)), - "scroll" => Rc::new(()), + "scroll" => Rc::new(ScrollData {}), "wheel" => Rc::new(WheelData::from(event)), "animationstart" | "animationend" | "animationiteration" => { Rc::new(AnimationData::from(event)) From bb5738a0baf9f43af3db98a9a6fb481687026d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Esp=C3=ADn?= Date: Sat, 4 Nov 2023 19:22:47 +0100 Subject: [PATCH 20/73] fix: Read value from root when calling `AtomState::current()` (#1609) --- packages/fermi/src/hooks/state.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/fermi/src/hooks/state.rs b/packages/fermi/src/hooks/state.rs index d4ebc529c..7d473588a 100644 --- a/packages/fermi/src/hooks/state.rs +++ b/packages/fermi/src/hooks/state.rs @@ -86,7 +86,9 @@ impl AtomState { /// ``` #[must_use] pub fn current(&self) -> Rc { - self.value.as_ref().unwrap().clone() + let atoms = self.root.atoms.borrow(); + let slot = atoms.get(&self.id).unwrap(); + slot.value.clone().downcast().unwrap() } /// Get the `setter` function directly without the `AtomState` wrapper. From be1decf9f2ffc0b66cb7a176e9aafcabc2a529b2 Mon Sep 17 00:00:00 2001 From: Lee TaeWoo Date: Sun, 5 Nov 2023 03:53:50 +0900 Subject: [PATCH 21/73] fix syntax error at derive `Props` using const generics (#1607) --- packages/core-macro/src/props/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 605983afc..aba8137e7 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -551,18 +551,16 @@ mod struct_info { let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into())); }); - let phantom_generics = self.generics.params.iter().map(|param| match param { + let phantom_generics = self.generics.params.iter().filter_map(|param| match param { syn::GenericParam::Lifetime(lifetime) => { let lifetime = &lifetime.lifetime; - quote!(::core::marker::PhantomData<&#lifetime ()>) + Some(quote!(::core::marker::PhantomData<&#lifetime ()>)) } syn::GenericParam::Type(ty) => { let ty = &ty.ident; - quote!(::core::marker::PhantomData<#ty>) - } - syn::GenericParam::Const(_cnst) => { - quote!() + Some(quote!(::core::marker::PhantomData<#ty>)) } + syn::GenericParam::Const(_cnst) => None, }); let builder_method_doc = match self.builder_attr.builder_method_doc { Some(ref doc) => quote!(#doc), From c18f91142937937a32708d9f9c9294cf249e9d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Esp=C3=ADn?= Date: Sat, 4 Nov 2023 20:04:14 +0100 Subject: [PATCH 22/73] Update atom_root.rs (#1611) --- packages/fermi/src/hooks/atom_root.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs index 24fd70093..468f5f980 100644 --- a/packages/fermi/src/hooks/atom_root.rs +++ b/packages/fermi/src/hooks/atom_root.rs @@ -7,6 +7,6 @@ use dioxus_core::ScopeState; pub fn use_atom_root(cx: &ScopeState) -> &Rc { cx.use_hook(|| match cx.consume_context::>() { Some(root) => root, - None => panic!("No atom root found in context. Did you forget place an AtomRoot component at the top of your app?"), + None => panic!("No atom root found in context. Did you forget to call use_init_atom_root at the top of your app?"), }) } From b9554fd744426eeb1fd2ae03202ec9c57f8d5d58 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 5 Nov 2023 08:32:39 -0600 Subject: [PATCH 23/73] add a noop evaluator to the ssr renderer --- .../examples/axum-hello-world/src/main.rs | 1 + packages/fullstack/src/render.rs | 2 + packages/ssr/Cargo.toml | 3 ++ packages/ssr/src/eval.rs | 42 +++++++++++++++++++ packages/ssr/src/incremental.rs | 1 + packages/ssr/src/lib.rs | 2 + 6 files changed, 51 insertions(+) create mode 100644 packages/ssr/src/eval.rs diff --git a/packages/fullstack/examples/axum-hello-world/src/main.rs b/packages/fullstack/examples/axum-hello-world/src/main.rs index 8f1d3c8c6..64e5b44a0 100644 --- a/packages/fullstack/examples/axum-hello-world/src/main.rs +++ b/packages/fullstack/examples/axum-hello-world/src/main.rs @@ -24,6 +24,7 @@ fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); let text = use_state(cx, || "...".to_string()); + let eval = use_eval(cx); cx.render(rsx! { div { diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs index cda41f82c..886a5bd99 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/fullstack/src/render.rs @@ -45,6 +45,8 @@ impl SsrRendererPool { .expect("couldn't spawn runtime") .block_on(async move { let mut vdom = VirtualDom::new_with_props(component, props); + // Make sure the evaluator is initialized + dioxus_ssr::eval::init_eval(vdom.base_scope()); let mut to = WriteBuffer { buffer: Vec::new() }; // before polling the future, we need to set the context let prev_context = diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index f6376452d..71f5b26a0 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr"] [dependencies] dioxus-core = { workspace = true, features = ["serialize"] } +dioxus-html = { workspace = true } askama_escape = "0.10.3" thiserror = "1.0.23" rustc-hash = "1.1.0" @@ -17,6 +18,8 @@ lru = "0.10.0" tracing = { workspace = true } http = "0.2.9" tokio = { version = "1.28", features = ["full"], optional = true } +async-trait = "0.1.58" +serde_json = { version = "1.0" } [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/ssr/src/eval.rs b/packages/ssr/src/eval.rs new file mode 100644 index 000000000..2ab74f4e1 --- /dev/null +++ b/packages/ssr/src/eval.rs @@ -0,0 +1,42 @@ +use async_trait::async_trait; +use dioxus_core::ScopeState; +use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator}; +use std::rc::Rc; + +/// Provides the SSREvalProvider through [`cx.provide_context`]. +pub fn init_eval(cx: &ScopeState) { + let provider: Rc = Rc::new(SSREvalProvider {}); + cx.provide_context(provider); +} + +/// Reprents the ssr-target's provider of evaluators. +pub struct SSREvalProvider; +impl EvalProvider for SSREvalProvider { + fn new_evaluator(&self, _: String) -> Result, EvalError> { + Ok(Rc::new(SSREvaluator) as Rc) + } +} + +/// Represents a ssr-target's JavaScript evaluator. +pub struct SSREvaluator; + +// In ssr rendering we never run or resolve evals. +#[async_trait(?Send)] +impl Evaluator for SSREvaluator { + /// Runs the evaluated JavaScript. + async fn join(&self) -> Result { + std::future::pending::<()>().await; + unreachable!() + } + + /// Sends a message to the evaluated JavaScript. + fn send(&self, _el: serde_json::Value) -> Result<(), EvalError> { + Ok(()) + } + + /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript. + async fn recv(&self) -> Result { + std::future::pending::<()>().await; + unreachable!() + } +} diff --git a/packages/ssr/src/incremental.rs b/packages/ssr/src/incremental.rs index c3bb25314..34ffa3443 100644 --- a/packages/ssr/src/incremental.rs +++ b/packages/ssr/src/incremental.rs @@ -81,6 +81,7 @@ impl IncrementalRenderer { let mut html_buffer = WriteBuffer { buffer: Vec::new() }; { let mut vdom = VirtualDom::new_with_props(comp, props); + crate::eval::init_eval(vdom.base_scope()); rebuild_with(&mut vdom).await; renderer.render_before_body(&mut *html_buffer)?; diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 6794d1f14..fdc0e9333 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -10,6 +10,7 @@ pub mod incremental; #[cfg(feature = "incremental")] mod incremental_cfg; +pub mod eval; pub mod renderer; pub mod template; @@ -42,6 +43,7 @@ pub fn render_lazy(f: LazyNodes<'_, '_>) -> String { }; let mut dom = VirtualDom::new_with_props(lazy_app, props); + crate::eval::init_eval(dom.base_scope()); _ = dom.rebuild(); Renderer::new().render(&dom) From ca556ea9cbeef5217b05b84d467049daa9ae76fc Mon Sep 17 00:00:00 2001 From: xTeKc <81730792+xTeKc@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:32:42 -0500 Subject: [PATCH 24/73] update readme (#1615) * Fix wasm-bindgen version mismatch * fixes #1613 --- Cargo.toml | 2 +- packages/cli/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a78f60d3e..d687d74d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ slab = "0.4.2" futures-channel = "0.3.21" futures-util = { version = "0.3", default-features = false } rustc-hash = "1.1.0" -wasm-bindgen = "0.2.87" +wasm-bindgen = "0.2.88" html_parser = "0.7.0" thiserror = "1.0.40" prettyplease = { package = "prettier-please", version = "0.2", features = [ diff --git a/packages/cli/README.md b/packages/cli/README.md index 72a813468..8379f22cc 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -11,7 +11,7 @@ It handles building, bundling, development and publishing to simplify developmen ### Install the stable version (recommended) ``` -cargo install dioxus-cli --locked +cargo install dioxus-cli ``` ### Install the latest development build through git From 8ea7f076e5ff2b0a4ccc06cbfb93e93428d41b4b Mon Sep 17 00:00:00 2001 From: Joshua Wolfe <1369773+WolfeCub@users.noreply.github.com> Date: Wed, 8 Nov 2023 09:33:41 -0500 Subject: [PATCH 25/73] Updated server_fn to 0.5.2 (#1620) --- packages/fullstack/Cargo.toml | 2 +- packages/fullstack/src/server_fn.rs | 8 -------- packages/server-macro/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index 8ab213e78..8de27a9b1 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["ui", "gui", "react", "ssr", "fullstack"] [dependencies] # server functions -server_fn = { version = "0.4.6", default-features = false } +server_fn = { version = "0.5.2", default-features = false } dioxus_server_macro = { workspace = true } # warp diff --git a/packages/fullstack/src/server_fn.rs b/packages/fullstack/src/server_fn.rs index 539a0d70a..fd0639372 100644 --- a/packages/fullstack/src/server_fn.rs +++ b/packages/fullstack/src/server_fn.rs @@ -125,14 +125,6 @@ impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry { } } - fn register( - url: &'static str, - server_function: ServerFunction, - encoding: server_fn::Encoding, - ) -> Result<(), Self::Error> { - Self::register_explicit("", url, server_function, encoding) - } - /// Returns the server function registered at the given URL, or `None` if no function is registered at that URL. fn get(url: &str) -> Option> { REGISTERED_SERVER_FUNCTIONS diff --git a/packages/server-macro/Cargo.toml b/packages/server-macro/Cargo.toml index 5a38acb40..a91cc626a 100644 --- a/packages/server-macro/Cargo.toml +++ b/packages/server-macro/Cargo.toml @@ -17,7 +17,7 @@ proc-macro2 = "^1.0.63" quote = "^1.0.26" syn = { version = "2", features = ["full"] } convert_case = "^0.6.0" -server_fn_macro = "^0.4.6" +server_fn_macro = "^0.5.2" [lib] proc-macro = true From d4b0451d096c67616be4b27de7a9e482ac5d8fd6 Mon Sep 17 00:00:00 2001 From: tigerros Date: Thu, 9 Nov 2023 19:31:59 +0100 Subject: [PATCH 26/73] Allow warning in necessary functions (#1626) --- packages/core-macro/src/props/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index aba8137e7..6e6c43d66 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -631,7 +631,7 @@ Finally, call `.build()` to create the instance of `{name}`. Ok(quote! { impl #impl_generics #name #ty_generics #where_clause { #[doc = #builder_method_doc] - #[allow(dead_code)] + #[allow(dead_code, clippy::type_complexity)] #vis fn builder() -> #builder_name #generics_with_empty { #builder_name { fields: #empties_tuple, @@ -823,6 +823,7 @@ Finally, call `.build()` to create the instance of `{name}`. #[allow(dead_code, non_camel_case_types, missing_docs)] impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause { #doc + #[allow(clippy::type_complexity)] pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > { let #field_name = (#arg_expr,); let ( #(#descructuring,)* ) = self.fields; @@ -841,6 +842,7 @@ Finally, call `.build()` to create the instance of `{name}`. #[deprecated( note = #repeated_fields_error_message )] + #[allow(clippy::type_complexity)] pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > { self } From 7f4e2af0c48e33915e96f0ac5e75b4c18e706632 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Nov 2023 08:17:08 -0600 Subject: [PATCH 27/73] fix string memory leak --- packages/core/src/nodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c83b1059a..b6a685ad3 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -707,7 +707,7 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str { impl IntoDynNode<'_> for String { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { DynamicNode::Text(VText { - value: cx.bump().alloc(self), + value: cx.bump().alloc_str(&self), id: Default::default(), }) } From 62719c309a26e5320b0bc9dde04a9d1689e351e4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 14 Nov 2023 16:39:37 +0000 Subject: [PATCH 28/73] fix keyboard input on calc example (#1639) --- examples/calculator.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/calculator.rs b/examples/calculator.rs index bc7f13459..648ccfbc3 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -62,6 +62,7 @@ fn app(cx: Scope) -> Element { div { id: "wrapper", div { class: "app", div { class: "calculator", + tabindex: "0", onkeydown: handle_key_down_event, div { class: "calculator-display", val.to_string() } div { class: "calculator-keypad", From 098689083d9961640ac2da7f2a4c12c58a80ed07 Mon Sep 17 00:00:00 2001 From: Raman Hafiyatulin Date: Wed, 15 Nov 2023 23:14:16 +0200 Subject: [PATCH 29/73] Related to #1547: use `dioxus-cli` within a workspace (wildcard-members, real package names) (#1642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Related to #1547: use `dioxus-cli` within a workspace Although the `dx` CLI allows to specify a package name to chose from workspace members, it does not support workspace members specified as glob-wildcards. Neither it respects the effective package name, specified in the crate's `Cargo.toml`. This PR addresses that issue: - upon `dx build ...`, if the `--bin` CLI-argument is provided, treat the current dir as a workspace; - search through the workspace's `members`: resolve each of them with `glob`; - assume that any workspace member has a `Cargo.toml` in it (cargo does it, so it's okay); - read said manifest, and check the package name in it; - if found — there we have our sought package. * Use cargo-metadata to find out the workspace structure * glob is unused --- packages/cli/src/error.rs | 3 ++ packages/cli/src/main.rs | 67 ++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/packages/cli/src/error.rs b/packages/cli/src/error.rs index 84b9d4b71..d577b2b40 100644 --- a/packages/cli/src/error.rs +++ b/packages/cli/src/error.rs @@ -29,6 +29,9 @@ pub enum Error { #[error("Cargo Error: {0}")] CargoError(String), + #[error("Couldn't retrieve cargo metadata")] + CargoMetadata(#[source] cargo_metadata::Error), + #[error("{0}")] CustomError(String), diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index fe860a0e6..7ca1b2c9a 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -9,42 +9,31 @@ use dioxus_cli::plugin::PluginManager; use Commands::*; -fn get_bin(bin: Option) -> Result> { - const ERR_MESSAGE: &str = "The `--bin` flag has to be ran in a Cargo workspace."; +fn get_bin(bin: Option) -> Result { + let metadata = cargo_metadata::MetadataCommand::new() + .exec() + .map_err(Error::CargoMetadata)?; + let package = if let Some(bin) = bin { + metadata + .workspace_packages() + .into_iter() + .find(|p| p.name == bin) + .ok_or(format!("no such package: {}", bin)) + .map_err(Error::CargoError)? + } else { + metadata + .root_package() + .ok_or("no root package?".into()) + .map_err(Error::CargoError)? + }; - if let Some(ref bin) = bin { - let manifest = cargo_toml::Manifest::from_path("./Cargo.toml") - .map_err(|_| Error::CargoError(ERR_MESSAGE.to_string()))?; + let crate_dir = package + .manifest_path + .parent() + .ok_or("couldn't take parent dir".into()) + .map_err(Error::CargoError)?; - if let Some(workspace) = manifest.workspace { - for item in workspace.members.iter() { - let path = PathBuf::from(item); - - if !path.exists() { - continue; - } - - if !path.is_dir() { - continue; - } - - if path.ends_with(bin.clone()) { - return Ok(Some(path)); - } - } - } else { - return Err(Error::CargoError(ERR_MESSAGE.to_string())); - } - } - - // If the bin exists but we couldn't find it - if bin.is_some() { - return Err(Error::CargoError( - "The specified bin does not exist.".to_string(), - )); - } - - Ok(None) + Ok(crate_dir.into()) } #[tokio::main] @@ -55,7 +44,7 @@ async fn main() -> anyhow::Result<()> { let bin = get_bin(args.bin)?; - let _dioxus_config = DioxusConfig::load(bin.clone()) + let _dioxus_config = DioxusConfig::load(Some(bin.clone())) .map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))? .unwrap_or_else(|| { log::warn!("You appear to be creating a Dioxus project from scratch; we will use the default config"); @@ -72,15 +61,15 @@ async fn main() -> anyhow::Result<()> { .map_err(|e| anyhow!("🚫 Translation of HTML into RSX failed: {}", e)), Build(opts) => opts - .build(bin.clone()) + .build(Some(bin.clone())) .map_err(|e| anyhow!("🚫 Building project failed: {}", e)), Clean(opts) => opts - .clean(bin.clone()) + .clean(Some(bin.clone())) .map_err(|e| anyhow!("🚫 Cleaning project failed: {}", e)), Serve(opts) => opts - .serve(bin.clone()) + .serve(Some(bin.clone())) .await .map_err(|e| anyhow!("🚫 Serving project failed: {}", e)), @@ -93,7 +82,7 @@ async fn main() -> anyhow::Result<()> { .map_err(|e| anyhow!("🚫 Configuring new project failed: {}", e)), Bundle(opts) => opts - .bundle(bin.clone()) + .bundle(Some(bin.clone())) .map_err(|e| anyhow!("🚫 Bundling project failed: {}", e)), #[cfg(feature = "plugin")] From 83f7ef9a447547c720e310b10e1682dc7762bdbc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 19 Nov 2023 09:58:00 -0600 Subject: [PATCH 30/73] add an optional cleanup closure to the use effect hook --- examples/readme.rs | 6 +++ packages/hooks/src/use_effect.rs | 79 ++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/examples/readme.rs b/examples/readme.rs index f4668488b..b39c04765 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -11,6 +11,12 @@ fn main() { fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); + use_effect(cx, (), move |()| async {}); + + use_effect(cx, (count.get(),), move |(count,)| async move { + move || println!("Count unmounted from {}", count) + }); + cx.render(rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index e72afe27f..1f5faecdd 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -1,5 +1,10 @@ use dioxus_core::{ScopeState, TaskId}; -use std::{any::Any, cell::Cell, future::Future}; +use std::{ + any::Any, + cell::{Cell, RefCell}, + future::Future, + rc::Rc, +}; use crate::UseFutureDep; @@ -14,7 +19,7 @@ use crate::UseFutureDep; /// ## Arguments /// /// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`. -/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. +/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. That future may return nothing or a closure that will be executed when the dependencies change to clean up the effect. /// /// ## Examples /// @@ -33,6 +38,16 @@ use crate::UseFutureDep; /// } /// }); /// +/// // Only fetch the user data when the id changes. +/// use_effect(cx, (id,), |(id,)| { +/// to_owned![name]; +/// async move { +/// let user = fetch_user(id).await; +/// name.set(user.name); +/// move || println!("Cleaning up from {}", id); +/// } +/// }); +/// /// let name = name.get().clone().unwrap_or("Loading...".to_string()); /// /// render!( @@ -45,34 +60,80 @@ use crate::UseFutureDep; /// render!(Profile { id: 0 }) /// } /// ``` -pub fn use_effect(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> F) +pub fn use_effect(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> R) where - T: 'static, - F: Future + 'static, D: UseFutureDep, + R: UseEffectReturn, { struct UseEffect { needs_regen: bool, task: Cell>, dependencies: Vec>, + cleanup: UseEffectCleanup, + } + + impl Drop for UseEffect { + fn drop(&mut self) { + if let Some(cleanup) = self.cleanup.borrow_mut().take() { + cleanup(); + } + } } let state = cx.use_hook(move || UseEffect { needs_regen: true, task: Cell::new(None), dependencies: Vec::new(), + cleanup: Rc::new(RefCell::new(None)), }); if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen { + // Call the cleanup function if it exists + if let Some(cleanup) = state.cleanup.borrow_mut().take() { + cleanup(); + } + // We don't need regen anymore state.needs_regen = false; // Create the new future - let fut = future(dependencies.out()); + let return_value = future(dependencies.out()); - state.task.set(Some(cx.push_future(async move { - fut.await; - }))); + if let Some(task) = return_value.apply(state.cleanup.clone(), cx) { + state.task.set(Some(task)); + } + } +} + +type UseEffectCleanup = Rc>>>; + +/// Something that can be returned from a `use_effect` hook. +pub trait UseEffectReturn { + fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option; +} + +impl UseEffectReturn<()> for T +where + T: Future + 'static, +{ + fn apply(self, _: UseEffectCleanup, cx: &ScopeState) -> Option { + Some(cx.push_future(self)) + } +} + +#[doc(hidden)] +pub struct CleanupFutureMarker; +impl UseEffectReturn for T +where + T: Future + 'static, + F: FnOnce() + 'static, +{ + fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option { + let task = cx.push_future(async move { + let cleanup = self.await; + *oncleanup.borrow_mut() = Some(Box::new(cleanup) as Box); + }); + Some(task) } } From 3c115bbef76f21435d8db886a799a47503026e5f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 19 Nov 2023 10:03:18 -0600 Subject: [PATCH 31/73] add into attribute value impl for String --- packages/core/src/nodes.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c83b1059a..b489a7e4d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -791,6 +791,12 @@ impl<'a> IntoAttributeValue<'a> for &'a str { } } +impl<'a> IntoAttributeValue<'a> for String { + fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> { + AttributeValue::Text(cx.alloc_str(&self)) + } +} + impl<'a> IntoAttributeValue<'a> for f64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Float(self) From ff6c7efb41ef16bea467bfad164b33a2fb20219e Mon Sep 17 00:00:00 2001 From: "HJin.me" Date: Mon, 20 Nov 2023 01:22:54 +0800 Subject: [PATCH 32/73] fix: tailwind.css use absolute path (#1649) --- packages/cli/src/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index c85cdc484..71be8f115 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -469,7 +469,7 @@ pub fn gen_page(config: &DioxusConfig, serve: bool) -> String { .unwrap_or_default() .contains_key("tailwindcss") { - style_str.push_str("\n"); + style_str.push_str("\n"); } replace_or_insert_before("{style_include}", &style_str, " Date: Mon, 20 Nov 2023 22:24:16 +0800 Subject: [PATCH 33/73] Fix grammar typo in comment (#1652) --- packages/fullstack/src/router.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fullstack/src/router.rs b/packages/fullstack/src/router.rs index b556aa000..b8d93f0e0 100644 --- a/packages/fullstack/src/router.rs +++ b/packages/fullstack/src/router.rs @@ -53,7 +53,7 @@ fn default_external_navigation_handler() -> fn(Scope) -> Element { dioxus_router::prelude::FailureExternalNavigation } -/// The configeration for the router +/// The configuration for the router #[derive(Props, serde::Serialize, serde::Deserialize)] pub struct FullstackRouterConfig where From 0ee21dff1e962bb5e1c22cfde15ecb4bc07c49fb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 20 Nov 2023 09:33:39 -0600 Subject: [PATCH 34/73] fix missing item in hot reloading diffing --- packages/cli/src/logging.rs | 14 +++++- packages/cli/src/server/output.rs | 25 ++++++----- packages/rsx/Cargo.toml | 1 + .../rsx/src/hot_reload/hot_reload_diff.rs | 44 ++++++++++++++++--- .../src/hot_reload/hot_reloading_file_map.rs | 27 +++++++++--- 5 files changed, 87 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/logging.rs b/packages/cli/src/logging.rs index 48c2a2788..31d088737 100644 --- a/packages/cli/src/logging.rs +++ b/packages/cli/src/logging.rs @@ -28,7 +28,19 @@ pub fn set_up_logging() { message = message, )); }) - .level(log::LevelFilter::Info) + .level(match std::env::var("DIOXUS_LOG") { + Ok(level) => match level.to_lowercase().as_str() { + "error" => log::LevelFilter::Error, + "warn" => log::LevelFilter::Warn, + "info" => log::LevelFilter::Info, + "debug" => log::LevelFilter::Debug, + "trace" => log::LevelFilter::Trace, + _ => { + panic!("Invalid log level: {}", level) + } + }, + Err(_) => log::LevelFilter::Info, + }) .chain(std::io::stdout()) .apply() .unwrap(); diff --git a/packages/cli/src/server/output.rs b/packages/cli/src/server/output.rs index 71323dd29..4e148b0f1 100644 --- a/packages/cli/src/server/output.rs +++ b/packages/cli/src/server/output.rs @@ -22,17 +22,20 @@ pub fn print_console_info( options: PrettierOptions, web_info: Option, ) { - if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") { - "cls" - } else { - "clear" - }) - .output() - { - print!("{}", String::from_utf8_lossy(&native_clearseq.stdout)); - } else { - // Try ANSI-Escape characters - print!("\x1b[2J\x1b[H"); + // Don't clear the screen if the user has set the DIOXUS_LOG environment variable to "trace" so that we can see the logs + if Some("trace") != std::env::var("DIOXUS_LOG").ok().as_deref() { + if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") { + "cls" + } else { + "clear" + }) + .output() + { + print!("{}", String::from_utf8_lossy(&native_clearseq.stdout)); + } else { + // Try ANSI-Escape characters + print!("\x1b[2J\x1b[H"); + } } let mut profile = if config.release { "Release" } else { "Debug" }.to_string(); diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index bc866acb7..72b51a051 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -20,6 +20,7 @@ quote = { version = "1.0" } serde = { version = "1.0", features = ["derive"], optional = true } internment = { version = "0.7.0", optional = true } krates = { version = "0.12.6", optional = true } +tracing.workspace = true [features] hot_reload = ["krates", "internment"] diff --git a/packages/rsx/src/hot_reload/hot_reload_diff.rs b/packages/rsx/src/hot_reload/hot_reload_diff.rs index 22a01875d..7e19d3aad 100644 --- a/packages/rsx/src/hot_reload/hot_reload_diff.rs +++ b/packages/rsx/src/hot_reload/hot_reload_diff.rs @@ -1,4 +1,5 @@ use proc_macro2::TokenStream; +use quote::ToTokens; use syn::{File, Macro}; pub enum DiffResult { @@ -10,13 +11,30 @@ pub enum DiffResult { pub fn find_rsx(new: &File, old: &File) -> DiffResult { let mut rsx_calls = Vec::new(); if new.items.len() != old.items.len() { + tracing::trace!( + "found not hot reload-able change {:#?} != {:#?}", + new.items + .iter() + .map(|i| i.to_token_stream().to_string()) + .collect::>(), + old.items + .iter() + .map(|i| i.to_token_stream().to_string()) + .collect::>() + ); return DiffResult::CodeChanged; } for (new, old) in new.items.iter().zip(old.items.iter()) { if find_rsx_item(new, old, &mut rsx_calls) { + tracing::trace!( + "found not hot reload-able change {:#?} != {:#?}", + new.to_token_stream().to_string(), + old.to_token_stream().to_string() + ); return DiffResult::CodeChanged; } } + tracing::trace!("found hot reload-able changes {:#?}", rsx_calls); DiffResult::RsxChanged(rsx_calls) } @@ -94,6 +112,9 @@ fn find_rsx_item( (syn::ImplItem::Macro(new_item), syn::ImplItem::Macro(old_item)) => { old_item != new_item } + (syn::ImplItem::Verbatim(stream), syn::ImplItem::Verbatim(stream2)) => { + stream.to_string() != stream2.to_string() + } _ => true, } { return true; @@ -186,10 +207,12 @@ fn find_rsx_trait( } } (syn::TraitItem::Fn(new_item), syn::TraitItem::Fn(old_item)) => { - if let (Some(new_block), Some(old_block)) = (&new_item.default, &old_item.default) { - find_rsx_block(new_block, old_block, rsx_calls) - } else { - true + match (&new_item.default, &old_item.default) { + (Some(new_block), Some(old_block)) => { + find_rsx_block(new_block, old_block, rsx_calls) + } + (None, None) => false, + _ => true, } } (syn::TraitItem::Type(new_item), syn::TraitItem::Type(old_item)) => { @@ -198,6 +221,9 @@ fn find_rsx_trait( (syn::TraitItem::Macro(new_item), syn::TraitItem::Macro(old_item)) => { old_item != new_item } + (syn::TraitItem::Verbatim(stream), syn::TraitItem::Verbatim(stream2)) => { + stream.to_string() != stream2.to_string() + } _ => true, } { return true; @@ -355,6 +381,11 @@ fn find_rsx_expr( || new_expr.or2_token != old_expr.or2_token || new_expr.output != old_expr.output } + (syn::Expr::Const(new_expr), syn::Expr::Const(old_expr)) => { + find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.const_token != old_expr.const_token + } (syn::Expr::Continue(new_expr), syn::Expr::Continue(old_expr)) => old_expr != new_expr, (syn::Expr::Field(new_expr), syn::Expr::Field(old_expr)) => { find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls) @@ -402,6 +433,7 @@ fn find_rsx_expr( || new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token } + (syn::Expr::Infer(new_expr), syn::Expr::Infer(old_expr)) => new_expr != old_expr, (syn::Expr::Let(new_expr), syn::Expr::Let(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs @@ -589,7 +621,9 @@ fn find_rsx_expr( _ => true, } } - (syn::Expr::Verbatim(_), syn::Expr::Verbatim(_)) => false, + (syn::Expr::Verbatim(stream), syn::Expr::Verbatim(stream2)) => { + stream.to_string() != stream2.to_string() + } _ => true, } } diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs index 0393142b4..7e36dddf9 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs @@ -51,17 +51,24 @@ impl FileMap { fn find_rs_files( root: PathBuf, filter: &mut impl FnMut(&Path) -> bool, - ) -> io::Result { + ) -> FileMapSearchResult { let mut files = HashMap::new(); let mut errors = Vec::new(); if root.is_dir() { - for entry in (fs::read_dir(root)?).flatten() { + let read_dir = match fs::read_dir(root) { + Ok(read_dir) => read_dir, + Err(err) => { + errors.push(err); + return FileMapSearchResult { map: files, errors }; + } + }; + for entry in read_dir.flatten() { let path = entry.path(); if !filter(&path) { let FileMapSearchResult { map, errors: child_errors, - } = find_rs_files(path, filter)?; + } = find_rs_files(path, filter); errors.extend(child_errors); files.extend(map); } @@ -69,14 +76,20 @@ impl FileMap { } else if root.extension().and_then(|s| s.to_str()) == Some("rs") { if let Ok(mut file) = File::open(root.clone()) { let mut src = String::new(); - file.read_to_string(&mut src)?; - files.insert(root, (src, None)); + match file.read_to_string(&mut src) { + Ok(_) => { + files.insert(root, (src, None)); + } + Err(err) => { + errors.push(err); + } + } } } - Ok(FileMapSearchResult { map: files, errors }) + FileMapSearchResult { map: files, errors } } - let FileMapSearchResult { map, errors } = find_rs_files(path, &mut filter)?; + let FileMapSearchResult { map, errors } = find_rs_files(path, &mut filter); let result = Self { map, in_workspace: HashMap::new(), From 8cf8f66c272e0bc13942d7fa01c1adfae3d2155d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 20 Nov 2023 09:47:03 -0600 Subject: [PATCH 35/73] add more logging to desktop hot reloading --- packages/cli/src/server/desktop/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 978b97d43..be722323b 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -73,6 +73,7 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option move || { let mut current_child = currently_running_child.write().unwrap(); + log::trace!("Killing old process"); current_child.kill()?; let (child, result) = start_desktop(&config)?; *current_child = child; @@ -212,6 +213,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { // Run the desktop application + log::trace!("Building application"); let result = crate::builder::build_desktop(config, true)?; match &config.executable { @@ -222,6 +224,7 @@ pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { if cfg!(windows) { file.set_extension("exe"); } + log::trace!("Running application from {:?}", file); let child = Command::new(file.to_str().unwrap()).spawn()?; Ok((child, result)) From f8ce72c6054701f03786bf55df1bba1a82471884 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Mon, 20 Nov 2023 09:49:18 -0600 Subject: [PATCH 36/73] Fix outdated lazynode documentation (#1648) * Fix outdated lazynode documentation * Use expanded rsx! instead of just rsx macro --- packages/core/src/lazynodes.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 811dbb734..3189086b4 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -23,8 +23,37 @@ use crate::{innerlude::VNode, ScopeState}; /// /// /// ```rust, ignore -/// LazyNodes::new(|f| f.element("div", [], [], [] None)) +/// LazyNodes::new(|f| { +/// static TEMPLATE: dioxus::core::Template = dioxus::core::Template { +/// name: "main.rs:5:5:20", // Source location of the template for hot reloading +/// roots: &[ +/// dioxus::core::TemplateNode::Element { +/// tag: dioxus_elements::div::TAG_NAME, +/// namespace: dioxus_elements::div::NAME_SPACE, +/// attrs: &[], +/// children: &[], +/// }, +/// ], +/// node_paths: &[], +/// attr_paths: &[], +/// }; +/// dioxus::core::VNode { +/// parent: None, +/// key: None, +/// template: std::cell::Cell::new(TEMPLATE), +/// root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( +/// 1usize, +/// f.bump(), +/// ) +/// .into(), +/// dynamic_nodes: f.bump().alloc([]), +/// dynamic_attrs: f.bump().alloc([]), +/// }) +/// } /// ``` +/// +/// Find more information about how to construct [`VNode`] at + pub struct LazyNodes<'a, 'b> { #[cfg(not(miri))] inner: SmallBox VNode<'a> + 'b, S16>, @@ -61,7 +90,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { /// Call the closure with the given factory to produce real [`VNode`]. /// /// ```rust, ignore - /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None)); + /// let f = LazyNodes::new(/* Closure for creating VNodes */); /// /// let node = f.call(cac); /// ``` From 7a459e15d8316abfdb5b39d25deb4fff9318fbfe Mon Sep 17 00:00:00 2001 From: ealmloff Date: Tue, 21 Nov 2023 16:28:06 -0600 Subject: [PATCH 37/73] Update packages/hooks/src/use_effect.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marc Espín --- packages/hooks/src/use_effect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index 1f5faecdd..6fb2fcf9e 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -44,7 +44,7 @@ use crate::UseFutureDep; /// async move { /// let user = fetch_user(id).await; /// name.set(user.name); -/// move || println!("Cleaning up from {}", id); +/// move || println!("Cleaning up from {}", id) /// } /// }); /// From 017d7e96bf1ed70463f01d3fb9dc52824179269e Mon Sep 17 00:00:00 2001 From: ealmloff Date: Tue, 21 Nov 2023 16:28:31 -0600 Subject: [PATCH 38/73] restore readme.rs --- examples/readme.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/readme.rs b/examples/readme.rs index b39c04765..f4668488b 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -11,12 +11,6 @@ fn main() { fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); - use_effect(cx, (), move |()| async {}); - - use_effect(cx, (count.get(),), move |(count,)| async move { - move || println!("Count unmounted from {}", count) - }); - cx.render(rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } From d9220d4e42b86114096a639ba0b308062b6d763a Mon Sep 17 00:00:00 2001 From: tigerros Date: Thu, 23 Nov 2023 23:44:39 +0100 Subject: [PATCH 39/73] Add it (#1660) --- packages/html/src/global_attributes.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 3792397ad..02b0b32ed 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -269,6 +269,9 @@ trait_methods! { /// azimuth: "azimuth", "style"; + /// + backdrop_filter: "backdrop-filter", "style"; + /// backface_visibility: "backface-visibility", "style"; From 1c0b33cef44678beab17bbcccaeabff48f3c2027 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 26 Nov 2023 09:21:27 -0600 Subject: [PATCH 40/73] fix hot reloading svg elements --- packages/hot-reload/src/file_watcher.rs | 2 +- packages/html/src/elements.rs | 53 +++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/packages/hot-reload/src/file_watcher.rs b/packages/hot-reload/src/file_watcher.rs index 2509dd789..5b8b76b37 100644 --- a/packages/hot-reload/src/file_watcher.rs +++ b/packages/hot-reload/src/file_watcher.rs @@ -122,7 +122,7 @@ pub fn init(cfg: Config) { } = cfg; if let Ok(crate_dir) = PathBuf::from_str(root_path) { - // try to find the gitingore file + // try to find the gitignore file let gitignore_file_path = crate_dir.join(".gitignore"); let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 6a0e546fa..5e0d0995d 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -74,7 +74,7 @@ macro_rules! impl_attribute_match { $attr:ident $fil:ident: $vil:ident (in $ns:literal), ) => { if $attr == stringify!($fil) { - return Some((stringify!(fil), Some(ns))); + return Some((stringify!(fil), Some($ns))); } }; } @@ -180,14 +180,26 @@ macro_rules! impl_element_match { }; ( - $el:ident $name:ident $namespace:tt { + $el:ident $name:ident $namespace:literal { $( $fil:ident: $vil:ident $extra:tt, )* } ) => { if $el == stringify!($name) { - return Some((stringify!($name), Some(stringify!($namespace)))); + return dbg!(Some((stringify!($name), Some($namespace)))); + } + }; + + ( + $el:ident $name:ident [$_:literal, $namespace:tt] { + $( + $fil:ident: $vil:ident $extra:tt, + )* + } + ) => { + if $el == stringify!($name) { + return dbg!(Some((stringify!($name), Some($namespace)))); } }; } @@ -207,6 +219,8 @@ macro_rules! impl_element_match_attributes { $attr $fil: $vil ($extra), ); )* + + return impl_map_global_attributes!($el $attr $name None); } }; @@ -223,10 +237,41 @@ macro_rules! impl_element_match_attributes { $attr $fil: $vil ($extra), ); )* + + return impl_map_global_attributes!($el $attr $name $namespace); } } } +#[cfg(feature = "hot-reload-context")] +macro_rules! impl_map_global_attributes { + ( + $el:ident $attr:ident $element:ident None + ) => { + map_global_attributes($attr) + }; + + ( + $el:ident $attr:ident $element:ident $namespace:literal + ) => { + if $namespace == "http://www.w3.org/2000/svg" { + map_svg_attributes($attr) + } else { + map_global_attributes($attr) + } + }; + + ( + $el:ident $attr:ident $element:ident [$name:literal, $namespace:tt] + ) => { + if $namespace == "http://www.w3.org/2000/svg" { + map_svg_attributes($attr) + } else { + map_global_attributes($attr) + } + }; +} + macro_rules! builder_constructors { ( $( @@ -254,7 +299,7 @@ macro_rules! builder_constructors { } ); )* - map_global_attributes(attribute).or_else(|| map_svg_attributes(attribute)) + None } fn map_element(element: &str) -> Option<(&'static str, Option<&'static str>)> { From 141554a7861f8c589f246a611f10c332f4e8a711 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 26 Nov 2023 09:23:27 -0600 Subject: [PATCH 41/73] remove logging --- packages/html/src/elements.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 5e0d0995d..d7db36a04 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -187,7 +187,7 @@ macro_rules! impl_element_match { } ) => { if $el == stringify!($name) { - return dbg!(Some((stringify!($name), Some($namespace)))); + return Some((stringify!($name), Some($namespace))); } }; @@ -199,7 +199,7 @@ macro_rules! impl_element_match { } ) => { if $el == stringify!($name) { - return dbg!(Some((stringify!($name), Some($namespace)))); + return Some((stringify!($name), Some($namespace))); } }; } From 18fa1e4831b372ec1cba805de8a1df8626801cd6 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:42:48 -0600 Subject: [PATCH 42/73] Make UseFuture Clone, factor out dependencies field (#1666) --- packages/hooks/src/use_future.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index 67bcdc74e..b9aa20c31 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -31,13 +31,14 @@ where let state = cx.use_hook(move || UseFuture { update: cx.schedule_update(), - needs_regen: Cell::new(true), + needs_regen: Rc::new(Cell::new(true)), state: val.clone(), task: Default::default(), - dependencies: Vec::new(), }); - if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { + let state_dependencies = cx.use_hook(Vec::new); + + if dependencies.clone().apply(state_dependencies) || state.needs_regen.get() { // kill the old one, if it exists if let Some(task) = state.task.take() { cx.remove_future(task); @@ -69,11 +70,11 @@ pub enum FutureState<'a, T> { Regenerating(&'a T), // the old value } +#[derive(Clone)] pub struct UseFuture { update: Arc, - needs_regen: Cell, + needs_regen: Rc>, task: Rc>>, - dependencies: Vec>, state: UseState>, } From 8e4debb22632f1673c7d943f4382b1ca7b6b9fa9 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:45:29 -0600 Subject: [PATCH 43/73] remove #[doc(hidden)] from EventHandler (#1665) * remove #[doc(hidden)] from EventHandler * Make UseFuture Clone, factor out dependencies field * Revert "Make UseFuture Clone, factor out dependencies field" This reverts commit 9ca09e595d1cda3f2047750a923a5bdc49a8fa76. * fix formatting --------- Co-authored-by: Evan Almloff --- packages/core/src/events.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 3b8edb05c..784a8c865 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -107,8 +107,6 @@ impl std::fmt::Debug for Event { } } -#[doc(hidden)] - /// The callback type generated by the `rsx!` macro when an `on` field is specified for components. /// /// This makes it possible to pass `move |evt| {}` style closures into components as property fields. From 694989e82692e17c1712ddb3e79d6198a73fb3c1 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:51:09 -0600 Subject: [PATCH 44/73] Add use_const hook (#1667) * Add use_const hook * cargo fmt * clippy allow * pub fn on get_rc --- packages/hooks/src/lib.rs | 3 ++ packages/hooks/src/use_const.rs | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 packages/hooks/src/use_const.rs diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index a5b92a036..8178d4d3d 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -60,6 +60,9 @@ pub mod computed; mod use_on_destroy; pub use use_on_destroy::*; +mod use_const; +pub use use_const::*; + mod use_context; pub use use_context::*; diff --git a/packages/hooks/src/use_const.rs b/packages/hooks/src/use_const.rs new file mode 100644 index 000000000..47ddb1ed2 --- /dev/null +++ b/packages/hooks/src/use_const.rs @@ -0,0 +1,76 @@ +use std::rc::Rc; + +use dioxus_core::prelude::*; + +/// Store constant state between component renders. +/// +/// UseConst allows you to store state that is initialized once and then remains constant across renders. +/// You can only get an immutable reference after initalization. +/// This can be useful for values that don't need to update reactively, thus can be memoized easily +/// +/// ```rust, ignore +/// struct ComplexData(i32); +/// +/// fn Component(cx: Scope) -> Element { +/// let id = use_const(cx, || ComplexData(100)); +/// +/// cx.render(rsx! { +/// div { "{id.0}" } +/// }) +/// } +/// ``` +#[must_use] +pub fn use_const( + cx: &ScopeState, + initial_state_fn: impl FnOnce() -> T, +) -> &UseConst { + cx.use_hook(|| UseConst { + value: Rc::new(initial_state_fn()), + }) +} + +#[derive(Clone)] +pub struct UseConst { + value: Rc, +} + +impl PartialEq for UseConst { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.value, &other.value) + } +} + +impl core::fmt::Display for UseConst { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.value.fmt(f) + } +} + +impl UseConst { + pub fn get_rc(&self) -> &Rc { + &self.value + } +} + +impl std::ops::Deref for UseConst { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.as_ref() + } +} + +#[test] +fn use_const_makes_sense() { + #[allow(unused)] + + fn app(cx: Scope) -> Element { + let const_val = use_const(cx, || vec![0, 1, 2, 3]); + + assert!(const_val[0] == 0); + + // const_val.remove(0); // Cannot Compile, cannot get mutable reference now + + None + } +} From b4fe3829d18a8a8d6ce1a293da12e93bd94c76f5 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:41:14 -0600 Subject: [PATCH 45/73] Add cargo make tidy/miri (#1670) * Add cargo make tidy/miri * fmt * seperate tidy and tests --- Makefile.toml | 74 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index 6f331b98f..1bfb02217 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -24,12 +24,64 @@ script = [ ] script_runner = "@duckscript" +[tasks.format] +command = "cargo" +args = ["fmt", "--all"] + +[tasks.check] +command = "cargo" +args = ["check", "--workspace", "--examples", "--tests"] + +[tasks.clippy] +command = "cargo" +args = [ + "clippy", + "--workspace", + "--examples", + "--tests", + "--", + "-D", + "warnings", +] + +[tasks.tidy] +category = "Formatting" +dependencies = ["format", "check", "clippy"] +description = "Format and Check workspace" + +[tasks.install-miri] +toolchain = "nightly" +install_crate = { rustup_component_name = "miri", binary = "cargo +nightly miri", test_arg = "--help" } +private = true + +[tasks.miri-native] +command = "cargo" +toolchain = "nightly" +dependencies = ["install-miri"] +args = [ + "miri", + "test", + "--package", + "dioxus-native-core", + "--test", + "miri_native", +] + +[tasks.miri-stress] +command = "cargo" +toolchain = "nightly" +dependencies = ["install-miri"] +args = ["miri", "test", "--package", "dioxus-core", "--test", "miri_stress"] + +[tasks.miri] +dependencies = ["miri-native", "miri-stress"] + [tasks.tests] category = "Testing" dependencies = ["tests-setup"] description = "Run all tests" -env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]} -run_task = {name = ["test-flow", "test-with-browser"], fork = true} +env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] } +run_task = { name = ["test-flow", "test-with-browser"], fork = true } [tasks.build] command = "cargo" @@ -42,10 +94,24 @@ private = true [tasks.test] dependencies = ["build"] command = "cargo" -args = ["test", "--lib", "--bins", "--tests", "--examples", "--workspace", "--exclude", "dioxus-router", "--exclude", "dioxus-desktop"] +args = [ + "test", + "--lib", + "--bins", + "--tests", + "--examples", + "--workspace", + "--exclude", + "dioxus-router", + "--exclude", + "dioxus-desktop", +] private = true [tasks.test-with-browser] -env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router", "**/packages/desktop"] } +env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = [ + "**/packages/router", + "**/packages/desktop", +] } private = true workspace = true From e0fbed7eea90592d2769e8e763b69aea58af0106 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:54:17 -0600 Subject: [PATCH 46/73] Switch outdated tui crate for ratatui (#1671) * Switch outdated tui crate for ratatui * fix query docs whoops --- packages/rink/Cargo.toml | 2 +- packages/rink/src/lib.rs | 6 +++--- packages/rink/src/render.rs | 7 +++---- packages/rink/src/style.rs | 3 ++- packages/rink/src/style_attributes.rs | 8 ++++---- packages/rink/src/widget.rs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/rink/Cargo.toml b/packages/rink/Cargo.toml index ba3d549f8..92d1eae8c 100644 --- a/packages/rink/Cargo.toml +++ b/packages/rink/Cargo.toml @@ -14,7 +14,7 @@ dioxus-html = { workspace = true } dioxus-native-core = { workspace = true, features = ["layout-attributes"] } dioxus-native-core-macro = { workspace = true } -tui = "0.17.0" +ratatui = "0.24.0" crossterm = "0.26.1" anyhow = "1.0.42" tokio = { workspace = true, features = ["full"] } diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index 2eba1b98a..4a7261b55 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -17,6 +17,7 @@ use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; use futures_channel::mpsc::unbounded; use layout::TaffyLayout; use prevent_default::PreventDefault; +use ratatui::{backend::CrosstermBackend, Terminal}; use std::{io, time::Duration}; use std::{ pin::Pin, @@ -26,7 +27,6 @@ use std::{rc::Rc, sync::RwLock}; use style_attributes::StyleModifier; pub use taffy::{geometry::Point, prelude::*}; use tokio::select; -use tui::{backend::CrosstermBackend, Terminal}; use widgets::{register_widgets, RinkWidgetResponder, RinkWidgetTraitObject}; mod config; @@ -180,7 +180,7 @@ pub fn render( if !to_rerender.is_empty() || updated { updated = false; - fn resize(dims: tui::layout::Rect, taffy: &mut Taffy, rdom: &RealDom) { + fn resize(dims: ratatui::layout::Rect, taffy: &mut Taffy, rdom: &RealDom) { let width = screen_to_layout_space(dims.width); let height = screen_to_layout_space(dims.height); let root_node = rdom @@ -222,7 +222,7 @@ pub fn render( } else { let rdom = rdom.read().unwrap(); resize( - tui::layout::Rect { + ratatui::layout::Rect { x: 0, y: 0, width: 1000, diff --git a/packages/rink/src/render.rs b/packages/rink/src/render.rs index 19d642720..4185f3a62 100644 --- a/packages/rink/src/render.rs +++ b/packages/rink/src/render.rs @@ -1,11 +1,10 @@ use dioxus_native_core::{prelude::*, tree::TreeRef}; -use std::io::Stdout; +use ratatui::{layout::Rect, style::Color}; use taffy::{ geometry::Point, prelude::{Dimension, Layout, Size}, Taffy, }; -use tui::{backend::CrosstermBackend, layout::Rect, style::Color}; use crate::{ focus::Focused, @@ -20,7 +19,7 @@ use crate::{ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub(crate) fn render_vnode( - frame: &mut tui::Frame>, + frame: &mut ratatui::Frame, layout: &Taffy, node: NodeRef, cfg: Config, @@ -96,7 +95,7 @@ pub(crate) fn render_vnode( impl RinkWidget for NodeRef<'_> { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { - use tui::symbols::line::*; + use ratatui::symbols::line::*; enum Direction { Left, diff --git a/packages/rink/src/style.rs b/packages/rink/src/style.rs index 857322079..905cc4b34 100644 --- a/packages/rink/src/style.rs +++ b/packages/rink/src/style.rs @@ -1,6 +1,6 @@ use std::{num::ParseFloatError, str::FromStr}; -use tui::style::{Color, Modifier, Style}; +use ratatui::style::{Color, Modifier, Style}; use crate::RenderingMode; @@ -442,6 +442,7 @@ impl RinkStyle { impl From for Style { fn from(val: RinkStyle) -> Self { Style { + underline_color: None, fg: val.fg.map(|c| c.color), bg: val.bg.map(|c| c.color), add_modifier: val.add_modifier, diff --git a/packages/rink/src/style_attributes.rs b/packages/rink/src/style_attributes.rs index 0c87e3f83..d1e8c6bee 100644 --- a/packages/rink/src/style_attributes.rs +++ b/packages/rink/src/style_attributes.rs @@ -187,8 +187,8 @@ pub enum BorderStyle { } impl BorderStyle { - pub fn symbol_set(&self) -> Option { - use tui::symbols::line::*; + pub fn symbol_set(&self) -> Option { + use ratatui::symbols::line::*; const DASHED: Set = Set { horizontal: "╌", vertical: "╎", @@ -570,7 +570,7 @@ fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifier) { } fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { - use tui::style::Modifier; + use ratatui::style::Modifier; match name { "font" => (), "font-family" => (), @@ -593,7 +593,7 @@ fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { } fn apply_text(name: &str, value: &str, style: &mut StyleModifier) { - use tui::style::Modifier; + use ratatui::style::Modifier; match name { "text-align" => todo!(), diff --git a/packages/rink/src/widget.rs b/packages/rink/src/widget.rs index ab9044998..91af5b4f6 100644 --- a/packages/rink/src/widget.rs +++ b/packages/rink/src/widget.rs @@ -1,4 +1,4 @@ -use tui::{ +use ratatui::{ buffer::Buffer, layout::Rect, style::{Color, Modifier}, From be94c69f116ede6bec01373b1a47277256fd2db9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 29 Nov 2023 11:38:28 -0600 Subject: [PATCH 47/73] make optional props accept T or Option --- examples/optional_props.rs | 6 +++++ packages/core-macro/src/props/mod.rs | 33 ++++++++-------------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/examples/optional_props.rs b/examples/optional_props.rs index c5652a2e7..d5cf1e711 100644 --- a/examples/optional_props.rs +++ b/examples/optional_props.rs @@ -12,6 +12,12 @@ fn main() { fn app(cx: Scope) -> Element { cx.render(rsx! { + Button { + a: "asd".to_string(), + c: "asd".to_string(), + d: Some("asd".to_string()), + e: Some("asd".to_string()), + } Button { a: "asd".to_string(), c: "asd".to_string(), diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 6e6c43d66..f8450e36a 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -783,31 +783,16 @@ Finally, call `.build()` to create the instance of `{name}`. None => quote!(), }; - // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of - // nesting is different so we have to do this little dance. - let arg_type = if field.builder_attr.strip_option { - field.type_from_inside_option(false).ok_or_else(|| { - Error::new_spanned( - field_type, - "can't `strip_option` - field is not `Option<...>`", + let arg_type = field_type; + let (arg_type, arg_expr) = + if field.builder_attr.auto_into || field.builder_attr.strip_option { + ( + quote!(impl ::core::convert::Into<#arg_type>), + quote!(#field_name.into()), ) - })? - } else { - field_type - }; - let (arg_type, arg_expr) = if field.builder_attr.auto_into { - ( - quote!(impl ::core::convert::Into<#arg_type>), - quote!(#field_name.into()), - ) - } else { - (quote!(#arg_type), quote!(#field_name)) - }; - let arg_expr = if field.builder_attr.strip_option { - quote!(Some(#arg_expr)) - } else { - arg_expr - }; + } else { + (quote!(#arg_type), quote!(#field_name)) + }; let repeated_fields_error_type_name = syn::Ident::new( &format!( From 533c7bab492d121b79b126458429fc5774c3a39e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 29 Nov 2023 11:39:38 -0600 Subject: [PATCH 48/73] show more cases in the optional props example --- examples/optional_props.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/optional_props.rs b/examples/optional_props.rs index d5cf1e711..8108d437d 100644 --- a/examples/optional_props.rs +++ b/examples/optional_props.rs @@ -20,10 +20,16 @@ fn app(cx: Scope) -> Element { } Button { a: "asd".to_string(), + b: "asd".to_string(), c: "asd".to_string(), d: Some("asd".to_string()), e: "asd".to_string(), } + Button { + a: "asd".to_string(), + c: "asd".to_string(), + d: Some("asd".to_string()), + } }) } From a840e012d81d6b2de5b3ec7fcd91a65552c40a18 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 29 Nov 2023 11:46:18 -0600 Subject: [PATCH 49/73] fix clippy --- packages/core-macro/src/props/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index f8450e36a..d7a580705 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -243,10 +243,6 @@ mod field_info { } .into() } - - pub fn type_from_inside_option(&self, check_option_name: bool) -> Option<&syn::Type> { - type_from_inside_option(self.ty, check_option_name) - } } #[derive(Debug, Default, Clone)] From c8a9a7b1d5cf06c59c90ee54d5ccbe12f06c6b7a Mon Sep 17 00:00:00 2001 From: zhangzhonglai Date: Fri, 1 Dec 2023 21:52:20 +0800 Subject: [PATCH 50/73] docs(desktop): fix wrong example code (#1678) * docs(desktop): fix wrong example code * stop ignoring doctests in dioxus desktop --------- Co-authored-by: ealmloff --- packages/desktop/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index bfefaf547..63a8914f3 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -54,7 +54,7 @@ use wry::{application::window::WindowId, webview::WebContext}; /// /// This function will start a multithreaded Tokio runtime as well the WebView event loop. /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; /// /// fn main() { @@ -77,11 +77,12 @@ pub fn launch(root: Component) { /// /// You can configure the WebView window with a configuration closure /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; +/// use dioxus_desktop::*; /// /// fn main() { -/// dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App"))); +/// dioxus_desktop::launch_cfg(app, Config::default().with_window(WindowBuilder::new().with_title("My App"))); /// } /// /// fn app(cx: Scope) -> Element { @@ -100,8 +101,9 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// /// You can configure the WebView window with a configuration closure /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; +/// use dioxus_desktop::Config; /// /// fn main() { /// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default()); From 0a3b794a1cc3a9c215ca37b0d0510f70850778b5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 1 Dec 2023 15:56:14 -0600 Subject: [PATCH 51/73] add a warning about wry gnu support to dioxus desktop --- packages/desktop/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 63a8914f3..13d52b032 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -19,6 +19,17 @@ mod webview; #[cfg(any(target_os = "ios", target_os = "android"))] mod mobile_shortcut; +// WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently +#[cfg(all(windows, target_env = "gnu"))] +mod wry_gnu_warning { + #[allow(dead_code)] + #[must_use = "GNU windows targets can have issues with Wry. Using the MSVC toolchain is recommended"] + struct WryGnuWarning; + const _: () = { + let dont_use_gnu = WryGnuWarning; + }; +} + use crate::query::QueryResult; pub use cfg::{Config, WindowCloseBehaviour}; pub use desktop_context::DesktopContext; From a4600294c5c5dfc9c130b0db652d7ba2d036a1e8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 1 Dec 2023 22:17:51 -0600 Subject: [PATCH 52/73] allow users to disable the warning in a feature --- packages/desktop/Cargo.toml | 1 + packages/desktop/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index dcb994120..f0ea9e189 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -59,6 +59,7 @@ devtools = ["wry/devtools"] tray = ["wry/tray"] dox = ["wry/dox"] hot-reload = ["dioxus-hot-reload"] +gnu = [] [package.metadata.docs.rs] default-features = false diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 13d52b032..9a5ba6c64 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -20,10 +20,10 @@ mod webview; mod mobile_shortcut; // WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently -#[cfg(all(windows, target_env = "gnu"))] +#[cfg(all(windows, target_env = "gnu", not(feature = "gnu")))] mod wry_gnu_warning { #[allow(dead_code)] - #[must_use = "GNU windows targets can have issues with Wry. Using the MSVC toolchain is recommended"] + #[must_use = "GNU windows targets have some limitations within Wry. Using the MSVC windows toolchain is recommended. If you would like to use continue using GNU, you can read https://github.com/wravery/webview2-rs#cross-compilation and disable this warning by adding the gnu feature to dioxus-desktop in your Cargo.toml"] struct WryGnuWarning; const _: () = { let dont_use_gnu = WryGnuWarning; From a2ca1760c9f679c8904df2d4a9bde9c43a8697a4 Mon Sep 17 00:00:00 2001 From: Felix F Xu <84662027+felixf4xu@users.noreply.github.com> Date: Sun, 3 Dec 2023 09:25:27 +0800 Subject: [PATCH 53/73] Set local_socket_stream to blocking (default) (#1682) --- packages/cli/src/server/desktop/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 978b97d43..98d6e9244 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -121,9 +121,9 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<() let file_map = hot_reload_state.file_map.clone(); let channels = channels.clone(); let aborted = aborted.clone(); - let _ = local_socket_stream.set_nonblocking(true); move || { loop { + //accept() will block the thread when local_socket_stream is in blocking mode (default) match local_socket_stream.accept() { Ok(mut connection) => { // send any templates than have changed before the socket connected From d404ddfccfdacc57ec6043079fa1f07f3a50b947 Mon Sep 17 00:00:00 2001 From: Felix F Xu <84662027+felixf4xu@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:02:30 +0800 Subject: [PATCH 54/73] fix false notification/rebuild for dx hot-reload (#1684) * fix false notification/rebuild for dx hot-reload * Make sure we have permissions and the changed file exists before reading the metadata --------- Co-authored-by: ealmloff --- packages/cli/src/server/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 79a5531b6..eb4021184 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -55,6 +55,16 @@ async fn setup_file_watcher Result + Send + 'static>( break; } + // Workaround for notify and vscode-like editor: + // when edit & save a file in vscode, there will be two notifications, + // the first one is a file with empty content. + // filter the empty file notification to avoid false rebuild during hot-reload + if let Ok(metadata) = fs::metadata(path) { + if metadata.len() == 0 { + continue; + } + } + match rsx_file_map.update_rsx(path, &config.crate_dir) { Ok(UpdateResult::UpdatedRsx(msgs)) => { messages.extend(msgs); From e69a4a7cd84da0ac371d4933f7704493c5a9735a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 07:07:35 -0600 Subject: [PATCH 55/73] Bump JamesIves/github-pages-deploy-action from 4.4.3 to 4.5.0 (#1685) Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.4.3 to 4.5.0. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.4.3...v4.5.0) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs stable.yml | 2 +- .github/workflows/docs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs stable.yml b/.github/workflows/docs stable.yml index 642216f0f..523a24067 100644 --- a/.github/workflows/docs stable.yml +++ b/.github/workflows/docs stable.yml @@ -33,7 +33,7 @@ jobs: # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.4.3 + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: branch: gh-pages # The branch the action should deploy to. folder: docs/nightly # The folder the action should deploy. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 88542aea8..7701209c1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -39,7 +39,7 @@ jobs: # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.4.3 + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: branch: gh-pages # The branch the action should deploy to. folder: docs/nightly # The folder the action should deploy. From 8149868ebc8610c4c2bc6fc41b50d9be4f4bfb40 Mon Sep 17 00:00:00 2001 From: Raul Cesar Teixeira Date: Mon, 4 Dec 2023 21:11:02 -0300 Subject: [PATCH 56/73] Fixed conflict of "--verbose" and "--quiet" flags (#1686) Co-authored-by: Raul Cesar --- packages/cli/src/builder.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 71be8f115..08e636237 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -54,8 +54,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { .arg("build") .arg("--target") .arg("wasm32-unknown-unknown") - .arg("--message-format=json") - .arg("--quiet"); + .arg("--message-format=json"); let cmd = if config.release { cmd.arg("--release") @@ -65,7 +64,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { let cmd = if config.verbose { cmd.arg("--verbose") } else { - cmd + cmd.arg("--quiet") }; let cmd = if config.custom_profile.is_some() { From 04fd2487b3edaf3c8f5cc347562de942fb10504c Mon Sep 17 00:00:00 2001 From: Ben Sully Date: Tue, 5 Dec 2023 20:10:18 +0000 Subject: [PATCH 57/73] feat(fullstack): add `render_handler_with_state` (#1687) * feat(fullstack): add `render_handler_with_state` When using server functions, the current pattern to access state such as database connections is to use `register_server_fns_with_handler` on an Axum router and 'inject' the state into the context provided to the server function. However, this only affects function calls which go via the Axum router; SSR renders bypass this, and therefore don't have access to any state. This commit adds an alternative `render_handler` which accepts some additional state. That state is injected into the context in a similar manner to `register_server_fns_with_handler`. SSR renders can then proceed to run in the same way as HTTP calls. * Change state object to 'inject_state' callback Also add a compiling doctest example. * remove the explicit for<'a> lifetime * remove unused assets_path from render_handler_with_context example --------- Co-authored-by: Evan Almloff --- .../fullstack/src/adapters/axum_adapter.rs | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/fullstack/src/adapters/axum_adapter.rs b/packages/fullstack/src/adapters/axum_adapter.rs index 95d9ee6cd..41a7c92be 100644 --- a/packages/fullstack/src/adapters/axum_adapter.rs +++ b/packages/fullstack/src/adapters/axum_adapter.rs @@ -369,15 +369,65 @@ fn apply_request_parts_to_response( } } -/// SSR renderer handler for Axum -pub async fn render_handler( - State((cfg, ssr_state)): State<(ServeConfig

, SSRState)>, +/// SSR renderer handler for Axum with added context injection. +/// +/// # Example +/// ```rust,no_run +/// #![allow(non_snake_case)] +/// use std::sync::{Arc, Mutex}; +/// +/// use axum::routing::get; +/// use dioxus::prelude::*; +/// use dioxus_fullstack::{axum_adapter::render_handler_with_context, prelude::*}; +/// +/// fn app(cx: Scope) -> Element { +/// render! { +/// "hello!" +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let cfg = ServeConfigBuilder::new(app, ()) +/// .assets_path("dist") +/// .build(); +/// let ssr_state = SSRState::new(&cfg); +/// +/// // This could be any state you want to be accessible from your server +/// // functions using `[DioxusServerContext::get]`. +/// let state = Arc::new(Mutex::new("state".to_string())); +/// +/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); +/// axum::Server::bind(&addr) +/// .serve( +/// axum::Router::new() +/// // Register server functions, etc. +/// // Note you probably want to use `register_server_fns_with_handler` +/// // to inject the context into server functions running outside +/// // of an SSR render context. +/// .fallback(get(render_handler_with_context).with_state(( +/// move |ctx| ctx.insert(state.clone()).unwrap(), +/// cfg, +/// ssr_state, +/// ))) +/// .into_make_service(), +/// ) +/// .await +/// .unwrap(); +/// } +/// ``` +pub async fn render_handler_with_context< + P: Clone + serde::Serialize + Send + Sync + 'static, + F: FnMut(&mut DioxusServerContext), +>( + State((mut inject_context, cfg, ssr_state)): State<(F, ServeConfig

, SSRState)>, request: Request, ) -> impl IntoResponse { let (parts, _) = request.into_parts(); let url = parts.uri.path_and_query().unwrap().to_string(); let parts: Arc> = Arc::new(RwLock::new(parts.into())); - let server_context = DioxusServerContext::new(parts.clone()); + let mut server_context = DioxusServerContext::new(parts.clone()); + inject_context(&mut server_context); match ssr_state.render(url, &cfg, &server_context).await { Ok(rendered) => { @@ -395,6 +445,14 @@ pub async fn render_handler } } +/// SSR renderer handler for Axum +pub async fn render_handler( + State((cfg, ssr_state)): State<(ServeConfig

, SSRState)>, + request: Request, +) -> impl IntoResponse { + render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await +} + fn report_err(e: E) -> Response { Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) From 0c9248688372cdefd3fedb8bc884ee3fcb3ddcca Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 5 Dec 2023 22:57:20 -0800 Subject: [PATCH 58/73] publish generational box --- Cargo.toml | 4 ++-- packages/generational-box/Cargo.toml | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d687d74d0..153866883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,8 +76,8 @@ plasmo = { path = "packages/rink", version = "0.4.0" } dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } -dioxus-signals = { path = "packages/signals" } -generational-box = { path = "packages/generational-box" } +dioxus-signals = { path = "packages/signals", version = "0.1.0" } +generational-box = { path = "packages/generational-box", version = "0.1.0" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" } diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 6c5a5a841..661c9725b 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "generational-box" authors = ["Evan Almloff"] -version = "0.0.0" +version = "0.1.0" edition = "2018" - +description = "A box backed by a generational runtime" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +keywords = ["generational", "box", "memory", "allocator"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] From 6088483fb2cdd27a0c536a6a596f0c986fe23927 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 5 Dec 2023 23:01:10 -0800 Subject: [PATCH 59/73] fix signals import --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 153866883..06e4ca1fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ plasmo = { path = "packages/rink", version = "0.4.0" } dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } -dioxus-signals = { path = "packages/signals", version = "0.1.0" } +dioxus-signals = { path = "packages/signals" } generational-box = { path = "packages/generational-box", version = "0.1.0" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } From 8530f53692ec4ce2df6dcec4fb2af85576d6fdac Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Wed, 6 Dec 2023 04:55:07 -0800 Subject: [PATCH 60/73] Fix typo (#1695) * Fix typo * Fix typo again --- packages/core/src/mutations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 27976c157..f33590e2e 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -91,7 +91,7 @@ pub enum Mutation<'a> { id: ElementId, }, - /// Create an placeholder int he DOM that we will use later. + /// Create a placeholder in the DOM that we will use later. /// /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing CreatePlaceholder { From b965fc23e96fb8a56b5e8e4b601bbd76f28ad74f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 6 Dec 2023 17:01:51 -0600 Subject: [PATCH 61/73] use a temp directory for the hot reloading pipe on desktop, fullstack and liveview --- packages/cli/src/server/desktop/mod.rs | 5 +++-- packages/hot-reload/src/file_watcher.rs | 7 ++++--- packages/hot-reload/src/lib.rs | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 98d6e9244..6373487b3 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -109,7 +109,8 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option } async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()> { - match LocalSocketListener::bind("@dioxusin") { + let path = std::env::temp_dir().join("dioxusin"); + match LocalSocketListener::bind(path) { Ok(local_socket_stream) => { let aborted = Arc::new(Mutex::new(false)); // States @@ -188,7 +189,7 @@ fn clear_paths() { // 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); + let path = std::env::temp_dir().join(path); if path.exists() { let _ = std::fs::remove_file(path); } diff --git a/packages/hot-reload/src/file_watcher.rs b/packages/hot-reload/src/file_watcher.rs index 2509dd789..bbd0a7660 100644 --- a/packages/hot-reload/src/file_watcher.rs +++ b/packages/hot-reload/src/file_watcher.rs @@ -157,16 +157,17 @@ pub fn init(cfg: Config) { // 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"]; + let paths = ["./dioxusin"]; for path in paths { - let path = PathBuf::from(path); + let path = std::env::temp_dir().join(path); if path.exists() { let _ = std::fs::remove_file(path); } } } - match LocalSocketListener::bind("@dioxusin") { + let path = std::env::temp_dir().join("dioxusin"); + match LocalSocketListener::bind(path) { Ok(local_socket_stream) => { let aborted = Arc::new(Mutex::new(false)); diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 68692b72b..07bd70a8d 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -24,7 +24,8 @@ pub enum HotReloadMsg { /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { std::thread::spawn(move || { - if let Ok(socket) = LocalSocketStream::connect("@dioxusin") { + let path = std::env::temp_dir().join("dioxusin"); + if let Ok(socket) = LocalSocketStream::connect(path) { let mut buf_reader = BufReader::new(socket); loop { let mut buf = String::new(); From ab1fab5f50e5e9b487ce2f7e4535d659b1d3e274 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 6 Dec 2023 16:49:05 -0800 Subject: [PATCH 62/73] No publish demos --- examples/openid_connect_demo/Cargo.toml | 1 + examples/query_segments_demo/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/openid_connect_demo/Cargo.toml b/examples/openid_connect_demo/Cargo.toml index 4c5f47061..2a1abe173 100644 --- a/examples/openid_connect_demo/Cargo.toml +++ b/examples/openid_connect_demo/Cargo.toml @@ -2,6 +2,7 @@ name = "openid_auth_demo" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/query_segments_demo/Cargo.toml b/examples/query_segments_demo/Cargo.toml index 4a7d784c7..ee9f49c70 100644 --- a/examples/query_segments_demo/Cargo.toml +++ b/examples/query_segments_demo/Cargo.toml @@ -2,6 +2,7 @@ name = "query_segments_demo" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From d3f6ff703c0f90cd58da8d459be056db29f3c38e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 6 Dec 2023 16:56:35 -0800 Subject: [PATCH 63/73] Release 0.4.3 dioxus@0.4.3 dioxus-autofmt@0.4.3 dioxus-check@0.4.3 dioxus-cli@0.4.3 dioxus-core@0.4.3 dioxus-core-macro@0.4.3 dioxus-desktop@0.4.3 dioxus-fullstack@0.4.3 dioxus-hooks@0.4.3 dioxus-hot-reload@0.4.3 dioxus-html@0.4.3 dioxus-interpreter-js@0.4.3 dioxus-liveview@0.4.3 dioxus-mobile@0.4.3 dioxus-native-core@0.4.3 dioxus-native-core-macro@0.4.3 dioxus-router@0.4.3 dioxus-router-macro@0.4.3 dioxus-rsx@0.4.3 dioxus-signals@0.4.3 dioxus-ssr@0.4.3 dioxus-tui@0.4.3 dioxus-web@0.4.3 dioxus_server_macro@0.4.3 fermi@0.4.3 generational-box@0.4.3 plasmo@0.4.3 rsx-rosetta@0.4.3 Generated by cargo-workspaces --- Cargo.toml | 6 +++--- examples/tailwind/Cargo.toml | 2 +- packages/cli/Cargo.toml | 2 +- packages/generational-box/Cargo.toml | 2 +- packages/signals/Cargo.toml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06e4ca1fa..1772c1f47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ members = [ exclude = ["examples/mobile_demo"] [workspace.package] -version = "0.4.2" +version = "0.4.3" # dependencies that are shared across packages [workspace.dependencies] @@ -77,7 +77,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } dioxus-signals = { path = "packages/signals" } -generational-box = { path = "packages/generational-box", version = "0.1.0" } +generational-box = { path = "packages/generational-box", version = "0.4.3" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" } @@ -99,7 +99,7 @@ prettyplease = { package = "prettier-please", version = "0.2", features = [ # It is not meant to be published, but is used so "cargo run --example XYZ" works properly [package] name = "dioxus-examples" -version = "0.0.0" +version = "0.4.3" authors = ["Jonathan Kelley"] edition = "2021" description = "Top level crate for the Dioxus repository" diff --git a/examples/tailwind/Cargo.toml b/examples/tailwind/Cargo.toml index 5c2fbe666..26ac202f1 100644 --- a/examples/tailwind/Cargo.toml +++ b/examples/tailwind/Cargo.toml @@ -18,4 +18,4 @@ dioxus = { path = "../../packages/dioxus" } dioxus-desktop = { path = "../../packages/desktop" } [target.'cfg(target_arch = "wasm32")'.dependencies] -dioxus-web = { path = "../../packages/web" } \ No newline at end of file +dioxus-web = { path = "../../packages/web" } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index adb0239f2..128133602 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-cli" -version = "0.4.1" +version = "0.4.3" authors = ["Jonathan Kelley"] edition = "2021" description = "CLI tool for developing, testing, and publishing Dioxus apps" diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 661c9725b..f070c1805 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "generational-box" authors = ["Evan Almloff"] -version = "0.1.0" +version = "0.4.3" edition = "2018" description = "A box backed by a generational runtime" license = "MIT OR Apache-2.0" diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 56e19bbd8..34ae671b3 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dioxus-signals" authors = ["Jonathan Kelley"] -version = "0.0.0" +version = "0.4.3" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,4 +20,4 @@ tokio = { version = "1", features = ["full"] } [features] default = [] -serialize = ["serde"] \ No newline at end of file +serialize = ["serde"] From 16982c9f69adcebae9d9f086328bb20b11d6882b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 6 Dec 2023 20:54:50 -0800 Subject: [PATCH 64/73] Add metadata for signals crate --- packages/signals/Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 34ae671b3..c85fb6d7f 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -3,6 +3,12 @@ name = "dioxus-signals" authors = ["Jonathan Kelley"] version = "0.4.3" edition = "2018" +description = "Signals for Dioxus" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "wasm"] +rust-version = "1.60.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 69bf6b9dd7a8872e38a846ea8094d76589d05b21 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 6 Dec 2023 21:27:26 -0800 Subject: [PATCH 65/73] CI: adjust matrix test to install rustfmt during autofmt tests --- .github/workflows/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 037525557..1b1f1ed7b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -124,8 +124,6 @@ jobs: } steps: - - uses: actions/checkout@v4 - - name: install stable uses: dtolnay/rust-toolchain@master with: @@ -141,6 +139,11 @@ jobs: workspaces: core -> ../target save-if: ${{ matrix.features.key == 'all' }} + - name: Install rustfmt + run: rustup component add rustfmt + + - uses: actions/checkout@v4 + - name: test run: | ${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }} From 9a13df2c737b4232bcf76946eff2cbac5d111896 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 7 Dec 2023 07:00:59 -0600 Subject: [PATCH 66/73] move hot reload socket into the target directory --- packages/cli/src/server/desktop/mod.rs | 21 +++++++++++---------- packages/hot-reload/src/file_watcher.rs | 14 ++++++-------- packages/hot-reload/src/lib.rs | 7 +++++-- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 6373487b3..86f9d971a 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -43,8 +43,6 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let hot_reload_tx = broadcast::channel(100).0; - clear_paths(); - Some(HotReloadState { messages: hot_reload_tx.clone(), file_map: file_map.clone(), @@ -109,7 +107,13 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option } async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()> { - let path = std::env::temp_dir().join("dioxusin"); + let metadata = cargo_metadata::MetadataCommand::new() + .no_deps() + .exec() + .unwrap(); + let target_dir = metadata.target_directory.as_std_path(); + let path = target_dir.join("dioxusin"); + clear_paths(&path); match LocalSocketListener::bind(path) { Ok(local_socket_stream) => { let aborted = Arc::new(Mutex::new(false)); @@ -182,17 +186,14 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<() Ok(()) } -fn clear_paths() { +fn clear_paths(file_socket_path: &std::path::Path) { 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::env::temp_dir().join(path); - if path.exists() { - let _ = std::fs::remove_file(path); - } + + if file_socket_path.exists() { + let _ = std::fs::remove_file(file_socket_path); } } } diff --git a/packages/hot-reload/src/file_watcher.rs b/packages/hot-reload/src/file_watcher.rs index bbd0a7660..c83c62ad9 100644 --- a/packages/hot-reload/src/file_watcher.rs +++ b/packages/hot-reload/src/file_watcher.rs @@ -152,22 +152,20 @@ pub fn init(cfg: Config) { } let file_map = Arc::new(Mutex::new(file_map)); + let target_dir = crate_dir.join("target"); + let hot_reload_socket_path = target_dir.join("dioxusin"); + #[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"]; - for path in paths { - let path = std::env::temp_dir().join(path); - if path.exists() { - let _ = std::fs::remove_file(path); - } + if hot_reload_socket_path.exists() { + let _ = std::fs::remove_file(hot_reload_socket_path); } } - let path = std::env::temp_dir().join("dioxusin"); - match LocalSocketListener::bind(path) { + match LocalSocketListener::bind(hot_reload_socket_path) { Ok(local_socket_stream) => { let aborted = Arc::new(Mutex::new(false)); diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 07bd70a8d..46efb8341 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -1,4 +1,7 @@ -use std::io::{BufRead, BufReader}; +use std::{ + io::{BufRead, BufReader}, + path::PathBuf, +}; use dioxus_core::Template; #[cfg(feature = "file_watcher")] @@ -24,7 +27,7 @@ pub enum HotReloadMsg { /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { std::thread::spawn(move || { - let path = std::env::temp_dir().join("dioxusin"); + let path = PathBuf::from("./").join("target").join("dioxusin"); if let Ok(socket) = LocalSocketStream::connect(path) { let mut buf_reader = BufReader::new(socket); loop { From 2bab4eb6c3748ca8803a1332ff57502979da7e55 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 7 Dec 2023 07:06:43 -0600 Subject: [PATCH 67/73] rename read_untracked to peak --- packages/signals/src/signal.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 0ad96349d..88853c473 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -171,7 +171,8 @@ impl Signal { self.inner.origin_scope() } - /// Get the current value of the signal. This will subscribe the current scope to the signal. + /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peak`] instead. + /// /// If the signal has been dropped, this will panic. pub fn read(&self) -> Ref { let inner = self.inner.read(); @@ -201,13 +202,15 @@ impl Signal { } /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** + /// /// If the signal has been dropped, this will panic. - pub fn read_untracked(&self) -> Ref { + pub fn peak(&self) -> Ref { let inner = self.inner.read(); Ref::map(inner, |v| &v.value) } /// Get a mutable reference to the signal's value. + /// /// If the signal has been dropped, this will panic. pub fn write(&self) -> Write<'_, T> { let inner = self.inner.write(); @@ -387,15 +390,18 @@ impl ReadOnlySignal { self.inner.origin_scope() } - /// Get the current value of the signal. This will subscribe the current scope to the signal. + /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peak`] instead. + /// + /// If the signal has been dropped, this will panic. pub fn read(&self) -> Ref { self.inner.read() } /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** + /// /// If the signal has been dropped, this will panic. - pub fn read_untracked(&self) -> Ref { - self.inner.read_untracked() + pub fn peak(&self) -> Ref { + self.inner.peak() } /// Run a closure with a reference to the signal's value. From 7d2bbda53dc94b07cfe0aad8499b3eb408ecce99 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 7 Dec 2023 07:10:53 -0600 Subject: [PATCH 68/73] move gnu warning to build script --- packages/desktop/build.rs | 9 +++++++++ packages/desktop/src/lib.rs | 11 ----------- 2 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 packages/desktop/build.rs diff --git a/packages/desktop/build.rs b/packages/desktop/build.rs new file mode 100644 index 000000000..2061732e8 --- /dev/null +++ b/packages/desktop/build.rs @@ -0,0 +1,9 @@ +fn main() { + // WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently + if std::env::var("CARGO_CFG_WINDOWS").is_ok() + && std::env::var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu" + && !cfg!(feature = "gnu") + { + println!("cargo:warning=GNU windows targets have some limitations within Wry. Using the MSVC windows toolchain is recommended. If you would like to use continue using GNU, you can read https://github.com/wravery/webview2-rs#cross-compilation and disable this warning by adding the gnu feature to dioxus-desktop in your Cargo.toml") + } +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 9a5ba6c64..63a8914f3 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -19,17 +19,6 @@ mod webview; #[cfg(any(target_os = "ios", target_os = "android"))] mod mobile_shortcut; -// WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently -#[cfg(all(windows, target_env = "gnu", not(feature = "gnu")))] -mod wry_gnu_warning { - #[allow(dead_code)] - #[must_use = "GNU windows targets have some limitations within Wry. Using the MSVC windows toolchain is recommended. If you would like to use continue using GNU, you can read https://github.com/wravery/webview2-rs#cross-compilation and disable this warning by adding the gnu feature to dioxus-desktop in your Cargo.toml"] - struct WryGnuWarning; - const _: () = { - let dont_use_gnu = WryGnuWarning; - }; -} - use crate::query::QueryResult; pub use cfg::{Config, WindowCloseBehaviour}; pub use desktop_context::DesktopContext; From 1c2a6fa010fb8c7f209a229b59750951123670a7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 7 Dec 2023 07:19:41 -0600 Subject: [PATCH 69/73] fix signals with debug information --- packages/signals/src/signal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index a5ee6b198..6c4052b98 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -237,9 +237,9 @@ impl Signal { /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. - pub fn peak(&self) -> Ref { + pub fn peak(&self) -> GenerationalRef { let inner = self.inner.read(); - Ref::map(inner, |v| &v.value) + GenerationalRef::map(inner, |v| &v.value) } /// Get a mutable reference to the signal's value. @@ -439,7 +439,7 @@ impl ReadOnlySignal { /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. - pub fn peak(&self) -> Ref { + pub fn peak(&self) -> GenerationalRef { self.inner.peak() } From df57cc7d9c69fe20566cc5a3af39add83ce5e694 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 7 Dec 2023 08:29:14 -0600 Subject: [PATCH 70/73] fix disconnects that happen while a server function is being resolved --- packages/fullstack/src/adapters/mod.rs | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/fullstack/src/adapters/mod.rs b/packages/fullstack/src/adapters/mod.rs index 98d190919..d368b9c73 100644 --- a/packages/fullstack/src/adapters/mod.rs +++ b/packages/fullstack/src/adapters/mod.rs @@ -89,26 +89,26 @@ impl Service for ServerFnHandler { let parts = Arc::new(RwLock::new(parts)); // Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime - let (resp_tx, resp_rx) = tokio::sync::oneshot::channel(); let pool = get_local_pool(); - pool.spawn_pinned({ - let function = function.clone(); - let mut server_context = server_context.clone(); - server_context.parts = parts; - move || async move { - let data = match function.encoding() { - Encoding::Url | Encoding::Cbor => &body, - Encoding::GetJSON | Encoding::GetCBOR => &query, - }; - let server_function_future = function.call((), data); - let server_function_future = - ProvideServerContext::new(server_function_future, server_context.clone()); - let resp = server_function_future.await; - - resp_tx.send(resp).unwrap(); - } - }); - let result = resp_rx.await.unwrap(); + let result = pool + .spawn_pinned({ + let function = function.clone(); + let mut server_context = server_context.clone(); + server_context.parts = parts; + move || async move { + let data = match function.encoding() { + Encoding::Url | Encoding::Cbor => &body, + Encoding::GetJSON | Encoding::GetCBOR => &query, + }; + let server_function_future = function.call((), data); + let server_function_future = ProvideServerContext::new( + server_function_future, + server_context.clone(), + ); + server_function_future.await + } + }) + .await?; let mut res = http::Response::builder(); // Set the headers from the server context From a1550460ce8cda98998d074243c995955cf6557a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 7 Dec 2023 11:37:59 -0600 Subject: [PATCH 71/73] Fix peek spelling --- packages/signals/src/signal.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 6c4052b98..1dc4bd36e 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -203,7 +203,7 @@ impl Signal { self.inner.origin_scope() } - /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peak`] instead. + /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead. /// /// If the signal has been dropped, this will panic. #[track_caller] @@ -237,7 +237,7 @@ impl Signal { /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. - pub fn peak(&self) -> GenerationalRef { + pub fn peek(&self) -> GenerationalRef { let inner = self.inner.read(); GenerationalRef::map(inner, |v| &v.value) } @@ -428,7 +428,7 @@ impl ReadOnlySignal { self.inner.origin_scope() } - /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peak`] instead. + /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead. /// /// If the signal has been dropped, this will panic. #[track_caller] @@ -439,8 +439,8 @@ impl ReadOnlySignal { /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. - pub fn peak(&self) -> GenerationalRef { - self.inner.peak() + pub fn peek(&self) -> GenerationalRef { + self.inner.peek() } /// Run a closure with a reference to the signal's value. From fc31876a573a42ed03549c102bd15b709e60f197 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:35:16 -0600 Subject: [PATCH 72/73] Wasm target check before build (#1689) * Add `rustup show` check for wasm32 target * better place for check * fmt * clippy fmt --- packages/cli/src/builder.rs | 12 ++++++++++++ packages/cli/src/cli/build.rs | 8 ++++---- packages/core/src/diff.rs | 2 +- packages/dioxus-tui/examples/colorpicker.rs | 12 ++++++------ packages/fermi/src/lib.rs | 2 -- packages/router-macro/src/redirect.rs | 1 + packages/rsx/src/lib.rs | 2 +- .../signals/examples/split_subscriptions.rs | 18 +++++------------- packages/web/src/hot_reload.rs | 15 ++++++--------- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 08e636237..f7b6a3071 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -48,6 +48,18 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { // [1] Build the .wasm module log::info!("🚅 Running build command..."); + + let wasm_check_command = std::process::Command::new("rustup") + .args(["show"]) + .output()?; + let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap(); + if !wasm_check_output.contains("wasm32-unknown-unknown") { + log::info!("wasm32-unknown-unknown target not detected, installing.."); + let _ = std::process::Command::new("rustup") + .args(["target", "add", "wasm32-unknown-unknown"]) + .output()?; + } + let cmd = subprocess::Exec::cmd("cargo"); let cmd = cmd .cwd(crate_dir) diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 7fe29f8ce..63a2969e0 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -37,8 +37,8 @@ impl Build { .platform .unwrap_or(crate_config.dioxus_config.application.default_platform); - #[cfg(feature = "plugin")] - let _ = PluginManager::on_build_start(&crate_config, &platform); + // #[cfg(feature = "plugin")] + // let _ = PluginManager::on_build_start(&crate_config, &platform); match platform { Platform::Web => { @@ -66,8 +66,8 @@ impl Build { )?; file.write_all(temp.as_bytes())?; - #[cfg(feature = "plugin")] - let _ = PluginManager::on_build_finish(&crate_config, &platform); + // #[cfg(feature = "plugin")] + // let _ = PluginManager::on_build_finish(&crate_config, &platform); Ok(()) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 74017db0f..2de862218 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -560,7 +560,7 @@ impl<'b> VirtualDom { // If none of the old keys are reused by the new children, then we remove all the remaining old children and // create the new children afresh. if shared_keys.is_empty() { - if old.get(0).is_some() { + if old.first().is_some() { self.remove_nodes(&old[1..]); self.replace(&old[0], new); } else { diff --git a/packages/dioxus-tui/examples/colorpicker.rs b/packages/dioxus-tui/examples/colorpicker.rs index 00f8ef7e0..01cc38cda 100644 --- a/packages/dioxus-tui/examples/colorpicker.rs +++ b/packages/dioxus-tui/examples/colorpicker.rs @@ -15,21 +15,21 @@ fn app(cx: Scope) -> Element { let mapping: DioxusElementToNodeId = cx.consume_context().unwrap(); // disable templates so that every node has an id and can be queried cx.render(rsx! { - div{ + div { width: "100%", background_color: "hsl({hue}, 70%, {brightness}%)", onmousemove: move |evt| { if let RenderReturn::Ready(node) = cx.root_node() { - if let Some(id) = node.root_ids.borrow().get(0).cloned() { + if let Some(id) = node.root_ids.borrow().first().cloned() { let node = tui_query.get(mapping.get_node_id(id).unwrap()); - let Size{width, height} = node.size().unwrap(); + let Size { width, height } = node.size().unwrap(); let pos = evt.inner().element_coordinates(); - hue.set((pos.x as f32/width as f32)*255.0); - brightness.set((pos.y as f32/height as f32)*100.0); + hue.set((pos.x as f32 / width as f32) * 255.0); + brightness.set((pos.y as f32 / height as f32) * 100.0); } } }, - "hsl({hue}, 70%, {brightness}%)", + "hsl({hue}, 70%, {brightness}%)" } }) } diff --git a/packages/fermi/src/lib.rs b/packages/fermi/src/lib.rs index 52bf4cd09..dc5ccf010 100644 --- a/packages/fermi/src/lib.rs +++ b/packages/fermi/src/lib.rs @@ -22,8 +22,6 @@ mod atoms { pub use atom::*; pub use atomfamily::*; pub use atomref::*; - pub use selector::*; - pub use selectorfamily::*; } pub mod hooks { diff --git a/packages/router-macro/src/redirect.rs b/packages/router-macro/src/redirect.rs index c1efb8b4d..2933bbc8e 100644 --- a/packages/router-macro/src/redirect.rs +++ b/packages/router-macro/src/redirect.rs @@ -75,6 +75,7 @@ impl Redirect { let (segments, query) = parse_route_segments( path.span(), + #[allow(clippy::map_identity)] closure_arguments.iter().map(|(name, ty)| (name, ty)), &path.value(), )?; diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index bdf32b272..5db6baee9 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -193,7 +193,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { fn to_tokens(&self, out_tokens: &mut TokenStream2) { let mut context = DynamicContext::default(); - let key = match self.roots.get(0) { + let key = match self.roots.first() { Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(), Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(), _ => None, diff --git a/packages/signals/examples/split_subscriptions.rs b/packages/signals/examples/split_subscriptions.rs index 3eb60ed29..bb286b4b7 100644 --- a/packages/signals/examples/split_subscriptions.rs +++ b/packages/signals/examples/split_subscriptions.rs @@ -22,15 +22,9 @@ fn app(cx: Scope) -> Element { use_context_provider(cx, ApplicationData::default); render! { - div { - ReadsFirst {} - } - div { - ReadsSecond {} - } - div { - ReadsManySignals {} - } + div { ReadsFirst {} } + div { ReadsSecond {} } + div { ReadsManySignals {} } } } @@ -107,16 +101,14 @@ fn ReadsManySignals(cx: Scope) -> Element { } button { onclick: move |_| { - if let Some(first) = data.many_signals.read().get(0) { + if let Some(first) = data.many_signals.read().first() { *first.write() += 1; } }, "Increase First Item" } for signal in data.many_signals { - Child { - count: signal, - } + Child { count: signal } } } } diff --git a/packages/web/src/hot_reload.rs b/packages/web/src/hot_reload.rs index 6256cc935..e8b4e42e4 100644 --- a/packages/web/src/hot_reload.rs +++ b/packages/web/src/hot_reload.rs @@ -5,7 +5,6 @@ use futures_channel::mpsc::UnboundedReceiver; use dioxus_core::Template; pub(crate) fn init() -> UnboundedReceiver> { - use std::convert::TryInto; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; use web_sys::{MessageEvent, WebSocket}; @@ -31,14 +30,12 @@ pub(crate) fn init() -> UnboundedReceiver> { // change the rsx when new data is received let cl = Closure::wrap(Box::new(move |e: MessageEvent| { if let Ok(text) = e.data().dyn_into::() { - let text: Result = text.try_into(); - if let Ok(string) = text { - let val = serde_json::from_str::(&string).unwrap(); - // leak the value - let val: &'static serde_json::Value = Box::leak(Box::new(val)); - let template: Template<'_> = Template::deserialize(val).unwrap(); - tx.unbounded_send(template).unwrap(); - } + let string: String = text.into(); + let val = serde_json::from_str::(&string).unwrap(); + // leak the value + let val: &'static serde_json::Value = Box::leak(Box::new(val)); + let template: Template<'_> = Template::deserialize(val).unwrap(); + tx.unbounded_send(template).unwrap(); } }) as Box); From 6b7545f60a85a3add0259ecc801c03737cc9fddd Mon Sep 17 00:00:00 2001 From: Alex Parrill Date: Thu, 7 Dec 2023 17:13:50 -0500 Subject: [PATCH 73/73] Add loading attribute to img element (#1699) Useful for lazy loading: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#loading --- packages/html/src/elements.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index d7db36a04..a42c01950 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -827,6 +827,7 @@ builder_constructors! { decoding: ImageDecoding DEFAULT, height: usize DEFAULT, ismap: Bool DEFAULT, + loading: String DEFAULT, src: Uri DEFAULT, srcset: String DEFAULT, // FIXME this is much more complicated usemap: String DEFAULT, // FIXME should be a fragment starting with '#'