Browse Source

Merge branch 'master' into cli-config-library

Jonathan Kelley 1 year ago
parent
commit
ea15c6a2eb
43 changed files with 877 additions and 144 deletions
  1. 1 1
      .github/workflows/docs stable.yml
  2. 1 1
      .github/workflows/docs.yml
  3. 1 2
      .github/workflows/miri.yml
  4. 3 3
      Cargo.toml
  5. 70 4
      Makefile.toml
  6. 1 0
      examples/openid_connect_demo/Cargo.toml
  7. 1 0
      examples/query_segments_demo/Cargo.toml
  8. 1 1
      examples/tailwind/Cargo.toml
  9. 1 1
      packages/cli/Cargo.toml
  10. 2 3
      packages/cli/src/builder.rs
  11. 1 1
      packages/cli/src/server/desktop/mod.rs
  12. 10 0
      packages/cli/src/server/mod.rs
  13. 20 1
      packages/core/src/bump_frame.rs
  14. 0 2
      packages/core/src/events.rs
  15. 1 1
      packages/core/src/mutations.rs
  16. 7 1
      packages/core/src/nodes.rs
  17. 6 1
      packages/core/src/scopes.rs
  18. 6 4
      packages/desktop/src/lib.rs
  19. 1 0
      packages/fullstack/examples/axum-hello-world/src/main.rs
  20. 62 4
      packages/fullstack/src/adapters/axum_adapter.rs
  21. 2 0
      packages/fullstack/src/render.rs
  22. 7 2
      packages/generational-box/Cargo.toml
  23. 2 0
      packages/generational-box/README.md
  24. 407 46
      packages/generational-box/src/lib.rs
  25. 3 0
      packages/hooks/src/lib.rs
  26. 76 0
      packages/hooks/src/use_const.rs
  27. 6 5
      packages/hooks/src/use_future.rs
  28. 3 0
      packages/html/src/global_attributes.rs
  29. 1 1
      packages/rink/Cargo.toml
  30. 3 3
      packages/rink/src/lib.rs
  31. 3 4
      packages/rink/src/render.rs
  32. 2 1
      packages/rink/src/style.rs
  33. 4 4
      packages/rink/src/style_attributes.rs
  34. 1 1
      packages/rink/src/widget.rs
  35. 8 2
      packages/signals/Cargo.toml
  36. 1 1
      packages/signals/src/effect.rs
  37. 17 17
      packages/signals/src/impls.rs
  38. 29 8
      packages/signals/src/rt.rs
  39. 58 18
      packages/signals/src/signal.rs
  40. 3 0
      packages/ssr/Cargo.toml
  41. 42 0
      packages/ssr/src/eval.rs
  42. 1 0
      packages/ssr/src/incremental.rs
  43. 2 0
      packages/ssr/src/lib.rs

+ 1 - 1
.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.

+ 1 - 1
.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.

+ 1 - 2
.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

+ 3 - 3
Cargo.toml

@@ -51,7 +51,7 @@ members = [
 exclude = ["examples/mobile_demo"]
 
 [workspace.package]
-version = "0.4.2"
+version = "0.4.3"
 
 # dependencies that are shared across packages
 [workspace.dependencies]
@@ -78,8 +78,8 @@ 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-cli-config = { path = "packages/cli-config", version = "0.4.1" }
+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" }
@@ -101,7 +101,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"

+ 70 - 4
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

+ 1 - 0
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
 

+ 1 - 0
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
 

+ 1 - 1
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" }
+dioxus-web = { path = "../../packages/web" }

+ 1 - 1
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"

+ 2 - 3
packages/cli/src/builder.rs

@@ -57,8 +57,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
         .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")
@@ -68,7 +67,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
     let cmd = if config.verbose {
         cmd.arg("--verbose")
     } else {
-        cmd
+        cmd.arg("--quiet")
     };
 
     let cmd = if config.custom_profile.is_some() {

+ 1 - 1
packages/cli/src/server/desktop/mod.rs

@@ -123,9 +123,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

+ 10 - 0
packages/cli/src/server/mod.rs

@@ -47,6 +47,16 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + 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);

+ 20 - 1
packages/core/src/bump_frame.rs

@@ -1,5 +1,5 @@
 use crate::nodes::RenderReturn;
-use crate::{Attribute, AttributeValue};
+use crate::{Attribute, AttributeValue, VComponent};
 use bumpalo::Bump;
 use std::cell::RefCell;
 use std::cell::{Cell, UnsafeCell};
@@ -7,7 +7,10 @@ use std::cell::{Cell, UnsafeCell};
 pub(crate) struct BumpFrame {
     pub bump: UnsafeCell<Bump>,
     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<Vec<*const Attribute<'static>>>,
+    pub(crate) props_to_drop_before_reset: RefCell<Vec<*const VComponent<'static>>>,
 }
 
 impl BumpFrame {
@@ -17,6 +20,7 @@ impl BumpFrame {
             bump: UnsafeCell::new(bump),
             node: Cell::new(std::ptr::null()),
             attributes_to_drop_before_reset: Default::default(),
+            props_to_drop_before_reset: Default::default(),
         }
     }
 
@@ -41,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| {
@@ -49,9 +57,20 @@ impl BumpFrame {
                 _ = l.take();
             }
         });
+        let mut props = self.props_to_drop_before_reset.borrow_mut();
+        props.drain(..).for_each(|prop| {
+            let prop = unsafe { &*prop };
+            _ = prop.props.borrow_mut().take();
+        });
         unsafe {
             let bump = &mut *self.bump.get();
             bump.reset();
         }
     }
 }
+
+impl Drop for BumpFrame {
+    fn drop(&mut self) {
+        unsafe { self.reset() }
+    }
+}

+ 0 - 2
packages/core/src/events.rs

@@ -107,8 +107,6 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
     }
 }
 
-#[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.

+ 1 - 1
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 {

+ 7 - 1
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(),
         })
     }
@@ -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)

+ 6 - 1
packages/core/src/scopes.rs

@@ -367,12 +367,17 @@ impl<'src> ScopeState {
         }
 
         let mut props = self.borrowed_props.borrow_mut();
+        let mut drop_props = self
+            .previous_frame()
+            .props_to_drop_before_reset
+            .borrow_mut();
         for node in element.dynamic_nodes {
             if let DynamicNode::Component(comp) = node {
+                let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
                 if !comp.static_props {
-                    let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
                     props.push(unbounded);
                 }
+                drop_props.push(unbounded);
             }
         }
 

+ 6 - 4
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());

+ 1 - 0
packages/fullstack/examples/axum-hello-world/src/main.rs

@@ -24,6 +24,7 @@ fn app(cx: Scope<AppProps>) -> Element {
 
     let mut count = use_state(cx, || 0);
     let text = use_state(cx, || "...".to_string());
+    let eval = use_eval(cx);
 
     cx.render(rsx! {
         div {

+ 62 - 4
packages/fullstack/src/adapters/axum_adapter.rs

@@ -369,15 +369,65 @@ fn apply_request_parts_to_response<B>(
     }
 }
 
-/// SSR renderer handler for Axum
-pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
-    State((cfg, ssr_state)): State<(ServeConfig<P>, 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<P>, SSRState)>,
     request: Request<Body>,
 ) -> impl IntoResponse {
     let (parts, _) = request.into_parts();
     let url = parts.uri.path_and_query().unwrap().to_string();
     let parts: Arc<RwLock<http::request::Parts>> = 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<P: Clone + serde::Serialize + Send + Sync + 'static>
     }
 }
 
+/// SSR renderer handler for Axum
+pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
+    State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
+    request: Request<Body>,
+) -> impl IntoResponse {
+    render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await
+}
+
 fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> {
     Response::builder()
         .status(StatusCode::INTERNAL_SERVER_ERROR)

+ 2 - 0
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 =

+ 7 - 2
packages/generational-box/Cargo.toml

@@ -1,9 +1,12 @@
 [package]
 name = "generational-box"
 authors = ["Evan Almloff"]
-version = "0.0.0"
+version = "0.4.3"
 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]
@@ -15,3 +18,5 @@ rand = "0.8.5"
 [features]
 default = ["check_generation"]
 check_generation = []
+debug_borrows = []
+debug_ownership = []

+ 2 - 0
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();
 

+ 407 - 46
packages/generational-box/src/lib.rs

@@ -2,9 +2,12 @@
 #![warn(missing_docs)]
 
 use std::{
+    any::Any,
     cell::{Cell, Ref, RefCell, RefMut},
-    fmt::Debug,
+    error::Error,
+    fmt::{Debug, Display},
     marker::PhantomData,
+    ops::{Deref, DerefMut},
     rc::Rc,
 };
 
@@ -29,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);
     }
@@ -53,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]
@@ -68,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]
@@ -129,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"))]
@@ -153,6 +159,8 @@ pub struct GenerationalBox<T> {
     raw: MemoryLocation,
     #[cfg(any(debug_assertions, feature = "check_generation"))]
     generation: u32,
+    #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+    created_at: &'static std::panic::Location<'static>,
     _marker: PhantomData<T>,
 }
 
@@ -161,7 +169,7 @@ impl<T: 'static> Debug for GenerationalBox<T> {
         #[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 +183,7 @@ impl<T: 'static> GenerationalBox<T> {
     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 +192,51 @@ impl<T: 'static> GenerationalBox<T> {
     }
 
     /// Try to read the value. Returns None if the value is no longer valid.
-    pub fn try_read(&self) -> Option<Ref<'static, T>> {
-        self.validate()
-            .then(|| {
-                Ref::filter_map(self.raw.data.borrow(), |any| {
-                    any.as_ref()?.downcast_ref::<T>()
-                })
-                .ok()
-            })
-            .flatten()
+    #[track_caller]
+    pub fn try_read(&self) -> Result<GenerationalRef<T>, 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<'static, 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<RefMut<'static, T>> {
-        self.validate()
-            .then(|| {
-                RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
-                    any.as_mut()?.downcast_mut::<T>()
-                })
-                .ok()
-            })
-            .flatten()
+    #[track_caller]
+    pub fn try_write(&self) -> Result<GenerationalRefMut<T>, 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<'static, 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 +244,8 @@ impl<T: 'static> GenerationalBox<T> {
     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 +263,37 @@ impl<T> Clone for GenerationalBox<T> {
 }
 
 #[derive(Clone, Copy)]
-struct MemoryLocation {
-    data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
+struct MemoryLocation(&'static MemoryLocationInner);
+
+struct MemoryLocationInner {
+    data: RefCell<Option<Box<dyn std::any::Any>>>,
     #[cfg(any(debug_assertions, feature = "check_generation"))]
-    generation: &'static Cell<u32>,
+    generation: Cell<u32>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrowed_at: RefCell<Vec<&'static std::panic::Location<'static>>>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrowed_mut_at: Cell<Option<&'static std::panic::Location<'static>>>,
 }
 
 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<T: 'static>(&mut self, value: T) -> GenerationalBox<T> {
-        let mut inner_mut = self.data.borrow_mut();
+    fn replace_with_caller<T: 'static>(
+        &mut self,
+        value: T,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        caller: &'static std::panic::Location<'static>,
+    ) -> GenerationalBox<T> {
+        let mut inner_mut = self.0.data.borrow_mut();
 
         let raw = Box::new(value);
         let old = inner_mut.replace(raw);
@@ -273,10 +301,315 @@ 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 = "debug_ownership"))]
+            created_at: caller,
             _marker: PhantomData,
         }
     }
+
+    #[track_caller]
+    fn try_borrow<T: Any>(
+        &self,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        created_at: &'static std::panic::Location<'static>,
+    ) -> Result<GenerationalRef<T>, 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::<T>()) {
+                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_ownership"))]
+                    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<T: Any>(
+        &self,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        created_at: &'static std::panic::Location<'static>,
+    ) -> Result<GenerationalRefMut<T>, 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::<T>()) {
+                    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_ownership"))]
+                        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_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_ownership"))]
+        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<T: 'static> {
+    inner: Ref<'static, T>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrow: GenerationalRefBorrowInfo,
+}
+
+impl<T: 'static> GenerationalRef<T> {
+    /// Map one ref type to another.
+    pub fn map<U, F>(orig: GenerationalRef<T>, f: F) -> GenerationalRef<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<U, F>(orig: GenerationalRef<T>, f: F) -> Option<GenerationalRef<U>>
+    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<T: 'static> Deref for GenerationalRef<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<T: 'static> {
+    inner: RefMut<'static, T>,
+    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+    borrow: GenerationalRefMutBorrowInfo,
+}
+
+impl<T: 'static> GenerationalRefMut<T> {
+    /// Map one ref type to another.
+    pub fn map<U, F>(orig: GenerationalRefMut<T>, f: F) -> GenerationalRefMut<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<U, F>(orig: GenerationalRefMut<T>, f: F) -> Option<GenerationalRefMut<U>>
+    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<T: 'static> Deref for GenerationalRefMut<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        self.inner.deref()
+    }
+}
+
+impl<T: 'static> DerefMut for GenerationalRefMut<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 +638,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 +668,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<T: 'static>(&self, value: T) -> GenerationalBox<T> {
         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<T: 'static>(
+        &self,
+        value: T,
+        #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+        caller: &'static std::panic::Location<'static>,
+    ) -> GenerationalBox<T> {
+        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 +703,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 = "debug_ownership"))]
+            created_at: std::panic::Location::caller(),
             _marker: PhantomData,
         }
     }

+ 3 - 0
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::*;
 

+ 76 - 0
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<T: 'static>(
+    cx: &ScopeState,
+    initial_state_fn: impl FnOnce() -> T,
+) -> &UseConst<T> {
+    cx.use_hook(|| UseConst {
+        value: Rc::new(initial_state_fn()),
+    })
+}
+
+#[derive(Clone)]
+pub struct UseConst<T> {
+    value: Rc<T>,
+}
+
+impl<T> PartialEq for UseConst<T> {
+    fn eq(&self, other: &Self) -> bool {
+        Rc::ptr_eq(&self.value, &other.value)
+    }
+}
+
+impl<T: core::fmt::Display> core::fmt::Display for UseConst<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.value.fmt(f)
+    }
+}
+
+impl<T> UseConst<T> {
+    pub fn get_rc(&self) -> &Rc<T> {
+        &self.value
+    }
+}
+
+impl<T> std::ops::Deref for UseConst<T> {
+    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
+    }
+}

+ 6 - 5
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<T: 'static> {
     update: Arc<dyn Fn()>,
-    needs_regen: Cell<bool>,
+    needs_regen: Rc<Cell<bool>>,
     task: Rc<Cell<Option<TaskId>>>,
-    dependencies: Vec<Box<dyn Any>>,
     state: UseState<Option<T>>,
 }
 

+ 3 - 0
packages/html/src/global_attributes.rs

@@ -269,6 +269,9 @@ trait_methods! {
     /// <https://developer.mozilla.org/en-US/docs/Web/CSS/azimuth>
     azimuth: "azimuth", "style";
 
+    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter>
+    backdrop_filter: "backdrop-filter", "style";
+
     /// <https://developer.mozilla.org/en-US/docs/Web/CSS/backface-visibility>
     backface_visibility: "backface-visibility", "style";
 

+ 1 - 1
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"] }

+ 3 - 3
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<R: Driver>(
 
                 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<R: Driver>(
                     } else {
                         let rdom = rdom.read().unwrap();
                         resize(
-                            tui::layout::Rect {
+                            ratatui::layout::Rect {
                                 x: 0,
                                 y: 0,
                                 width: 1000,

+ 3 - 4
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<CrosstermBackend<Stdout>>,
+    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,

+ 2 - 1
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<RinkStyle> 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,

+ 4 - 4
packages/rink/src/style_attributes.rs

@@ -187,8 +187,8 @@ pub enum BorderStyle {
 }
 
 impl BorderStyle {
-    pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
-        use tui::symbols::line::*;
+    pub fn symbol_set(&self) -> Option<ratatui::symbols::line::Set> {
+        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!(),

+ 1 - 1
packages/rink/src/widget.rs

@@ -1,4 +1,4 @@
-use tui::{
+use ratatui::{
     buffer::Buffer,
     layout::Rect,
     style::{Color, Modifier},

+ 8 - 2
packages/signals/Cargo.toml

@@ -1,8 +1,14 @@
 [package]
 name = "dioxus-signals"
 authors = ["Jonathan Kelley"]
-version = "0.0.0"
+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
 
@@ -20,4 +26,4 @@ tokio = { version = "1", features = ["full"] }
 
 [features]
 default = []
-serialize = ["serde"]
+serialize = ["serde"]

+ 1 - 1
packages/signals/src/effect.rs

@@ -100,7 +100,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() {
             {
                 self.effect_stack.effects.write().push(*self);
             }

+ 17 - 17
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<T: 'static> $ty<Vec<T>> {
             /// Read a value from the inner vector.
-            pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
-                Ref::filter_map(self.read(), |v| v.get(index)).ok()
+            pub fn get(&self, index: usize) -> Option<GenerationalRef<T>> {
+                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<'_, T>> {
-                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<T>> {
+                GenerationalRef::filter_map(self.read(), |v| v.as_ref())
             }
         }
     };
@@ -182,19 +182,19 @@ 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 +238,15 @@ impl<T: Clone + 'static> IntoIterator for CopyValue<Vec<T>> {
 
 impl<T: 'static> CopyValue<Vec<T>> {
     /// Write to an element in the inner vector.
-    pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> {
-        RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok()
+    pub fn get_mut(&self, index: usize) -> Option<GenerationalRefMut<T>> {
+        GenerationalRefMut::filter_map(self.write(), |v| v.get_mut(index))
     }
 }
 
 impl<T: 'static> CopyValue<Option<T>> {
     /// Deref the inner value mutably.
-    pub fn as_mut(&self) -> Option<RefMut<'_, T>> {
-        RefMut::filter_map(self.write(), |v| v.as_mut()).ok()
+    pub fn as_mut(&self) -> Option<GenerationalRefMut<T>> {
+        GenerationalRefMut::filter_map(self.write(), |v| v.as_mut())
     }
 }
 
@@ -281,14 +281,14 @@ impl<T: Clone + 'static> IntoIterator for Signal<Vec<T>> {
 
 impl<T: 'static> Signal<Vec<T>> {
     /// Returns a reference to an element or `None` if out of bounds.
-    pub fn get_mut(&self, index: usize) -> Option<Write<'_, T, Vec<T>>> {
+    pub fn get_mut(&self, index: usize) -> Option<Write<T, Vec<T>>> {
         Write::filter_map(self.write(), |v| v.get_mut(index))
     }
 }
 
 impl<T: 'static> Signal<Option<T>> {
     /// Returns a reference to an element or `None` if out of bounds.
-    pub fn as_mut(&self) -> Option<Write<'_, T, Option<T>>> {
+    pub fn as_mut(&self) -> Option<Write<T, Option<T>>> {
         Write::filter_map(self.write(), |v| v.as_mut())
     }
 }

+ 29 - 8
packages/signals/src/rt.rs

@@ -1,5 +1,3 @@
-use std::cell::{Ref, RefMut};
-
 use std::mem::MaybeUninit;
 use std::ops::Deref;
 use std::rc::Rc;
@@ -7,7 +5,9 @@ use std::rc::Rc;
 use dioxus_core::prelude::*;
 use dioxus_core::ScopeId;
 
-use generational_box::{GenerationalBox, Owner, Store};
+use generational_box::{
+    BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, Store,
+};
 
 use crate::Effect;
 
@@ -83,6 +83,7 @@ impl<T: 'static> CopyValue<T> {
     /// 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();
 
@@ -92,6 +93,22 @@ impl<T: 'static> CopyValue<T> {
         }
     }
 
+    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,
+                #[cfg(debug_assertions)]
+                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);
@@ -117,22 +134,26 @@ impl<T: 'static> CopyValue<T> {
     }
 
     /// Try to read the value. If the value has been dropped, this will return None.
-    pub fn try_read(&self) -> Option<Ref<'_, T>> {
+    #[track_caller]
+    pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
         self.value.try_read()
     }
 
     /// Read the value. If the value has been dropped, this will panic.
-    pub fn read(&self) -> Ref<'static, 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<RefMut<'static, T>> {
+    #[track_caller]
+    pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
         self.value.try_write()
     }
 
     /// Write the value. If the value has been dropped, this will panic.
-    pub fn write(&self) -> RefMut<'static, T> {
+    #[track_caller]
+    pub fn write(&self) -> GenerationalRefMut<T> {
         self.value.write()
     }
 
@@ -168,7 +189,7 @@ impl<T: 'static> PartialEq for CopyValue<T> {
 }
 
 impl<T> Deref for CopyValue<T> {
-    type Target = dyn Fn() -> Ref<'static, T>;
+    type Target = dyn Fn() -> GenerationalRef<T>;
 
     fn deref(&self) -> &Self::Target {
         // https://github.com/dtolnay/case-studies/tree/master/callable-types

+ 58 - 18
packages/signals/src/signal.rs

@@ -1,5 +1,5 @@
 use std::{
-    cell::{Ref, RefCell, RefMut},
+    cell::RefCell,
     mem::MaybeUninit,
     ops::{Deref, DerefMut},
     rc::Rc,
@@ -10,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::{get_effect_stack, CopyValue, Effect, EffectStack};
 
@@ -44,9 +45,19 @@ use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
 ///     }
 /// }
 /// ```
+#[track_caller]
 #[must_use]
 pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
-    *cx.use_hook(|| Signal::new(f()))
+    #[cfg(debug_assertions)]
+    let caller = std::panic::Location::caller();
+
+    *cx.use_hook(|| {
+        Signal::new_with_caller(
+            f(),
+            #[cfg(debug_assertions)]
+            caller,
+        )
+    })
 }
 
 #[derive(Clone)]
@@ -138,6 +149,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign
 
 impl<T: 'static> Signal<T> {
     /// 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 {
@@ -150,6 +162,26 @@ impl<T: 'static> Signal<T> {
         }
     }
 
+    /// 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 std::panic::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,
+                    effect_stack: get_effect_stack(),
+                },
+                #[cfg(debug_assertions)]
+                caller,
+            ),
+        }
+    }
+
     /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope.
     pub fn new_in_scope(value: T, owner: ScopeId) -> Self {
         Self {
@@ -173,7 +205,8 @@ impl<T: 'static> Signal<T> {
 
     /// 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<T> {
+    #[track_caller]
+    pub fn read(&self) -> GenerationalRef<T> {
         let inner = self.inner.read();
         if let Some(effect) = inner.effect_stack.current() {
             let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
@@ -197,14 +230,15 @@ impl<T: 'static> Signal<T> {
                 }
             }
         }
-        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.
-    pub fn write(&self) -> Write<'_, T> {
+    #[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 },
@@ -240,12 +274,14 @@ impl<T: 'static> Signal<T> {
     }
 
     /// 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<O>(&self, f: impl FnOnce(&T) -> O) -> O {
         let write = self.read();
         f(&*write)
@@ -253,6 +289,7 @@ impl<T: 'static> Signal<T> {
 
     /// 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<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
         let mut write = self.write();
         f(&mut *write)
@@ -262,6 +299,7 @@ impl<T: 'static> Signal<T> {
 impl<T: Clone + 'static> Signal<T> {
     /// 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()
     }
@@ -281,7 +319,7 @@ impl<T: 'static> PartialEq for Signal<T> {
 }
 
 impl<T> Deref for Signal<T> {
-    type Target = dyn Fn() -> Ref<'static, T>;
+    type Target = dyn Fn() -> GenerationalRef<T>;
 
     fn deref(&self) -> &Self::Target {
         // https://github.com/dtolnay/case-studies/tree/master/callable-types
@@ -324,17 +362,17 @@ impl<T: 'static> Drop for SignalSubscriberDrop<T> {
 }
 
 /// A mutable reference to a signal's value.
-pub struct Write<'a, T: 'static, I: 'static = T> {
-    write: RefMut<'a, T>,
+pub struct Write<T: 'static, I: 'static = T> {
+    write: GenerationalRefMut<T>,
     signal: SignalSubscriberDrop<I>,
 }
 
-impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
+impl<T: 'static, I: 'static> Write<T, I> {
     /// Map the mutable reference to the signal's value to a new type.
-    pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> {
+    pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<O, I> {
         let Self { write, signal } = myself;
         Write {
-            write: RefMut::map(write, f),
+            write: GenerationalRefMut::map(write, f),
             signal,
         }
     }
@@ -343,14 +381,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
     pub fn filter_map<O>(
         myself: Self,
         f: impl FnOnce(&mut T) -> Option<&mut O>,
-    ) -> Option<Write<'a, O, I>> {
+    ) -> Option<Write<O, I>> {
         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 })
     }
 }
 
-impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> {
+impl<T: 'static, I: 'static> Deref for Write<T, I> {
     type Target = T;
 
     fn deref(&self) -> &Self::Target {
@@ -358,7 +396,7 @@ impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> {
     }
 }
 
-impl<T, I> DerefMut for Write<'_, T, I> {
+impl<T, I> DerefMut for Write<T, I> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.write
     }
@@ -381,11 +419,13 @@ impl<T: 'static> ReadOnlySignal<T> {
     }
 
     /// Get the current value of the signal. This will subscribe the current scope to the signal.
-    pub fn read(&self) -> Ref<T> {
+    #[track_caller]
+    pub fn read(&self) -> GenerationalRef<T> {
         self.inner.read()
     }
 
     /// Run a closure with a reference to the signal's value.
+    #[track_caller]
     pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
         self.inner.with(f)
     }
@@ -405,7 +445,7 @@ impl<T: 'static> PartialEq for ReadOnlySignal<T> {
 }
 
 impl<T> Deref for ReadOnlySignal<T> {
-    type Target = dyn Fn() -> Ref<'static, T>;
+    type Target = dyn Fn() -> GenerationalRef<T>;
 
     fn deref(&self) -> &Self::Target {
         // https://github.com/dtolnay/case-studies/tree/master/callable-types

+ 3 - 0
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 }

+ 42 - 0
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<dyn EvalProvider> = 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<Rc<dyn Evaluator>, EvalError> {
+        Ok(Rc::new(SSREvaluator) as Rc<dyn Evaluator + 'static>)
+    }
+}
+
+/// 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<serde_json::Value, EvalError> {
+        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<serde_json::Value, EvalError> {
+        std::future::pending::<()>().await;
+        unreachable!()
+    }
+}

+ 1 - 0
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)?;

+ 2 - 0
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)