Shift from mutually-exclusive features to a more-gracefully-degrading system of features ordered by preference, clean up some warnings, and use cfg_if for improved readability
This commit is contained in:
parent
19db83c933
commit
8ea73565de
|
@ -160,25 +160,28 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_map_keyed() {
|
||||
create_scope(|cx| {
|
||||
let (rows, set_rows) =
|
||||
create_signal::<Vec<(usize, ReadSignal<i32>, WriteSignal<i32>)>>(cx, vec![]);
|
||||
// we can really only run this in SSR mode, so just ignore if we're in CSR or hydrate
|
||||
if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
create_scope(|cx| {
|
||||
let (rows, set_rows) =
|
||||
create_signal::<Vec<(usize, ReadSignal<i32>, WriteSignal<i32>)>>(cx, vec![]);
|
||||
|
||||
let keyed = map_keyed(
|
||||
cx,
|
||||
rows,
|
||||
|cx, row| {
|
||||
let read = row.1;
|
||||
create_effect(cx, move |_| println!("row value = {}", read.get()));
|
||||
},
|
||||
|row| row.0,
|
||||
);
|
||||
let keyed = map_keyed(
|
||||
cx,
|
||||
rows,
|
||||
|cx, row| {
|
||||
let read = row.1;
|
||||
create_effect(cx, move |_| println!("row value = {}", read.get()));
|
||||
},
|
||||
|row| row.0,
|
||||
);
|
||||
|
||||
create_effect(cx, move |_| println!("keyed = {:#?}", keyed.get()));
|
||||
create_effect(cx, move |_| println!("keyed = {:#?}", keyed.get()));
|
||||
|
||||
let (r, w) = create_signal(cx, 0);
|
||||
set_rows.update(|n| n.push((0, r, w)));
|
||||
})
|
||||
.dispose();
|
||||
let (r, w) = create_signal(cx, 0);
|
||||
set_rows.update(|n| n.push((0, r, w)));
|
||||
})
|
||||
.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@ repository = "https://github.com/gbj/leptos"
|
|||
description = "DOM operations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
futures = { version = "0.3", optional = true }
|
||||
html-escape = { version = "0.2", optional = true }
|
||||
cfg-if = "1"
|
||||
futures = "0.3"
|
||||
html-escape = "0.2"
|
||||
js-sys = "0.3"
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.11" }
|
||||
serde_json = { version = "1", optional = true }
|
||||
serde_json = "1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4.31"
|
||||
log = "0.4"
|
||||
|
@ -51,11 +52,11 @@ features = [
|
|||
"Storage",
|
||||
"Text",
|
||||
"TreeWalker",
|
||||
"Window"
|
||||
"Window",
|
||||
]
|
||||
|
||||
[features]
|
||||
csr = ["leptos_reactive/csr"]
|
||||
hydrate = ["leptos_reactive/hydrate"]
|
||||
ssr = ["leptos_reactive/ssr", "dep:futures", "dep:html-escape", "dep:serde_json"]
|
||||
stable = ["leptos_reactive/stable"]
|
||||
ssr = ["leptos_reactive/ssr"]
|
||||
stable = ["leptos_reactive/stable"]
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use leptos_reactive::Scope;
|
||||
#[cfg(feature = "stable")]
|
||||
use leptos_reactive::{Memo, ReadSignal, RwSignal};
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
use crate::Node;
|
||||
|
||||
|
@ -19,7 +16,7 @@ pub enum Child {
|
|||
}
|
||||
|
||||
impl Child {
|
||||
#[cfg(feature = "ssr")]
|
||||
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
|
||||
pub fn as_child_string(&self) -> String {
|
||||
match self {
|
||||
Child::Null => String::new(),
|
||||
|
@ -83,24 +80,14 @@ impl IntoChild for String {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
impl IntoChild for web_sys::Node {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Node(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
impl IntoChild for web_sys::Text {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Node(self.unchecked_into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
impl IntoChild for web_sys::Element {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Node(self.unchecked_into())
|
||||
impl<T, U> IntoChild for T
|
||||
where
|
||||
T: FnMut() -> U + 'static,
|
||||
U: IntoChild,
|
||||
{
|
||||
fn into_child(mut self, cx: Scope) -> Child {
|
||||
let modified_fn = Rc::new(RefCell::new(move || (self)().into_child(cx)));
|
||||
Child::Fn(modified_fn)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,49 +109,6 @@ impl IntoChild for Vec<Node> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
impl IntoChild for Vec<web_sys::Element> {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Nodes(
|
||||
self.into_iter()
|
||||
.map(|el| el.unchecked_into::<web_sys::Node>())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "stable", not(feature = "ssr")))]
|
||||
impl IntoChild for Memo<Vec<web_sys::Element>> {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
(move || self.get()).into_child(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "stable", not(feature = "ssr")))]
|
||||
impl IntoChild for ReadSignal<Vec<web_sys::Element>> {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
(move || self.get()).into_child(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "stable", not(feature = "ssr")))]
|
||||
impl IntoChild for RwSignal<Vec<web_sys::Element>> {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
(move || self.get()).into_child(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> IntoChild for T
|
||||
where
|
||||
T: FnMut() -> U + 'static,
|
||||
U: IntoChild,
|
||||
{
|
||||
fn into_child(mut self, cx: Scope) -> Child {
|
||||
let modified_fn = Rc::new(RefCell::new(move || (self)().into_child(cx)));
|
||||
Child::Fn(modified_fn)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! child_type {
|
||||
($child_type:ty) => {
|
||||
impl IntoChild for $child_type {
|
||||
|
@ -193,3 +137,62 @@ child_type!(f32);
|
|||
child_type!(f64);
|
||||
child_type!(char);
|
||||
child_type!(bool);
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "hydrate", feature = "csr"))] {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
impl IntoChild for web_sys::Node {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Node(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoChild for web_sys::Text {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Node(self.unchecked_into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoChild for web_sys::Element {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Node(self.unchecked_into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoChild for Vec<web_sys::Element> {
|
||||
fn into_child(self, _cx: Scope) -> Child {
|
||||
Child::Nodes(
|
||||
self.into_iter()
|
||||
.map(|el| el.unchecked_into::<web_sys::Node>())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `stable` feature
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "stable")] {
|
||||
use leptos_reactive::{Memo, ReadSignal, RwSignal};
|
||||
|
||||
impl IntoChild for Memo<Vec<Element>> {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
(move || self.get()).into_child(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoChild for ReadSignal<Vec<Element>> {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
(move || self.get()).into_child(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoChild for RwSignal<Vec<Element>> {
|
||||
fn into_child(self, cx: Scope) -> Child {
|
||||
(move || self.get()).into_child(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,63 +1,61 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
pub mod attribute;
|
||||
pub mod child;
|
||||
pub mod class;
|
||||
pub mod event_delegation;
|
||||
pub mod logging;
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub mod mount;
|
||||
pub mod operations;
|
||||
pub mod property;
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub mod reconcile;
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub mod render;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod render_to_string;
|
||||
|
||||
cfg_if! {
|
||||
// can only include this if we're *only* enabling SSR, as it's the lowest-priority feature
|
||||
// if either `csr` or `hydrate` is enabled, `Element` is a `web_sys::Element` and can't be rendered
|
||||
if #[cfg(not(any(feature = "hydrate", feature = "csr")))] {
|
||||
pub type Element = String;
|
||||
pub type Node = String;
|
||||
|
||||
pub mod render_to_string;
|
||||
pub use render_to_string::*;
|
||||
} else {
|
||||
pub type Element = web_sys::Element;
|
||||
pub type Node = web_sys::Node;
|
||||
|
||||
pub mod mount;
|
||||
pub mod reconcile;
|
||||
pub mod render;
|
||||
|
||||
pub use mount::*;
|
||||
pub use reconcile::*;
|
||||
pub use render::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub use attribute::*;
|
||||
pub use child::*;
|
||||
pub use class::*;
|
||||
pub use logging::*;
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub use mount::*;
|
||||
pub use operations::*;
|
||||
pub use property::*;
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub use render::*;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub use render_to_string::*;
|
||||
|
||||
pub use js_sys;
|
||||
pub use wasm_bindgen;
|
||||
pub use web_sys;
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub type Element = web_sys::Element;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub type Element = String;
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub type Node = web_sys::Node;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub type Node = String;
|
||||
|
||||
use leptos_reactive::Scope;
|
||||
pub use wasm_bindgen::UnwrapThrowExt;
|
||||
|
||||
#[cfg(feature = "csr")]
|
||||
pub fn create_component<F, T>(cx: Scope, f: F) -> T
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
cx.untrack(f)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "csr"))]
|
||||
pub fn create_component<F, T>(cx: Scope, f: F) -> T
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
cx.with_next_context(f)
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "csr")] {
|
||||
cx.untrack(f)
|
||||
} else {
|
||||
cx.with_next_context(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
@ -1,84 +1,6 @@
|
|||
use cfg_if::cfg_if;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use leptos_reactive::*;
|
||||
|
||||
use crate::Element;
|
||||
use futures::{stream::FuturesUnordered, Stream, StreamExt};
|
||||
|
||||
pub fn render_to_stream(view: impl Fn(Scope) -> Element + 'static) -> impl Stream<Item = String> {
|
||||
let ((shell, pending_resources, pending_fragments, serializers), _, disposer) =
|
||||
run_scope_undisposed({
|
||||
move |cx| {
|
||||
// the actual app body/template code
|
||||
// this does NOT contain any of the data being loaded asynchronously in resources
|
||||
let shell = view(cx);
|
||||
|
||||
let resources = cx.all_resources();
|
||||
let pending_resources = serde_json::to_string(&resources).unwrap();
|
||||
|
||||
(
|
||||
shell,
|
||||
pending_resources,
|
||||
cx.pending_fragments(),
|
||||
cx.serialization_resolvers(),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let fragments = FuturesUnordered::new();
|
||||
for (fragment_id, fut) in pending_fragments {
|
||||
fragments.push(async move { (fragment_id, fut.await) })
|
||||
}
|
||||
|
||||
// HTML for the view function and script to store resources
|
||||
futures::stream::once(async move {
|
||||
format!(
|
||||
r#"
|
||||
{shell}
|
||||
<script>
|
||||
__LEPTOS_PENDING_RESOURCES = {pending_resources};
|
||||
__LEPTOS_RESOLVED_RESOURCES = new Map();
|
||||
__LEPTOS_RESOURCE_RESOLVERS = new Map();
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
})
|
||||
// stream data for each Resource as it resolves
|
||||
.chain(serializers.map(|(id, json)| {
|
||||
let id = serde_json::to_string(&id).unwrap();
|
||||
format!(
|
||||
r#"<script>
|
||||
if(__LEPTOS_RESOURCE_RESOLVERS.get({id})) {{
|
||||
console.log("(create_resource) calling resolver");
|
||||
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
|
||||
}} else {{
|
||||
console.log("(create_resource) saving data for resource creation");
|
||||
__LEPTOS_RESOLVED_RESOURCES.set({id}, {json:?});
|
||||
}}
|
||||
</script>"#,
|
||||
)
|
||||
}))
|
||||
// stream HTML for each <Suspense/> as it resolves
|
||||
.chain(fragments.map(|(fragment_id, html)| {
|
||||
format!(
|
||||
r#"
|
||||
<template id="{fragment_id}">{html}</template>
|
||||
<script>
|
||||
var frag = document.querySelector(`[data-fragment-id="{fragment_id}"]`);
|
||||
var tpl = document.getElementById("{fragment_id}");
|
||||
console.log("replace", frag, "with", tpl.content.cloneNode(true));
|
||||
frag.replaceWith(tpl.content.cloneNode(true));
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
}))
|
||||
// dispose of Scope
|
||||
.chain(futures::stream::once(async {
|
||||
disposer.dispose();
|
||||
Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn escape_text(text: &str) -> Cow<'_, str> {
|
||||
html_escape::encode_text(text)
|
||||
}
|
||||
|
@ -86,3 +8,86 @@ pub fn escape_text(text: &str) -> Cow<'_, str> {
|
|||
pub fn escape_attr(text: &str) -> Cow<'_, str> {
|
||||
html_escape::encode_double_quoted_attribute(text)
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos_reactive::*;
|
||||
|
||||
use crate::Element;
|
||||
use futures::{stream::FuturesUnordered, Stream, StreamExt};
|
||||
|
||||
pub fn render_to_stream(view: impl Fn(Scope) -> Element + 'static) -> impl Stream<Item = String> {
|
||||
let ((shell, pending_resources, pending_fragments, serializers), _, disposer) =
|
||||
run_scope_undisposed({
|
||||
move |cx| {
|
||||
// the actual app body/template code
|
||||
// this does NOT contain any of the data being loaded asynchronously in resources
|
||||
let shell = view(cx);
|
||||
|
||||
let resources = cx.all_resources();
|
||||
let pending_resources = serde_json::to_string(&resources).unwrap();
|
||||
|
||||
(
|
||||
shell,
|
||||
pending_resources,
|
||||
cx.pending_fragments(),
|
||||
cx.serialization_resolvers(),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let fragments = FuturesUnordered::new();
|
||||
for (fragment_id, fut) in pending_fragments {
|
||||
fragments.push(async move { (fragment_id, fut.await) })
|
||||
}
|
||||
|
||||
// HTML for the view function and script to store resources
|
||||
futures::stream::once(async move {
|
||||
format!(
|
||||
r#"
|
||||
{shell}
|
||||
<script>
|
||||
__LEPTOS_PENDING_RESOURCES = {pending_resources};
|
||||
__LEPTOS_RESOLVED_RESOURCES = new Map();
|
||||
__LEPTOS_RESOURCE_RESOLVERS = new Map();
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
})
|
||||
// stream data for each Resource as it resolves
|
||||
.chain(serializers.map(|(id, json)| {
|
||||
let id = serde_json::to_string(&id).unwrap();
|
||||
format!(
|
||||
r#"<script>
|
||||
if(__LEPTOS_RESOURCE_RESOLVERS.get({id})) {{
|
||||
console.log("(create_resource) calling resolver");
|
||||
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
|
||||
}} else {{
|
||||
console.log("(create_resource) saving data for resource creation");
|
||||
__LEPTOS_RESOLVED_RESOURCES.set({id}, {json:?});
|
||||
}}
|
||||
</script>"#,
|
||||
)
|
||||
}))
|
||||
// stream HTML for each <Suspense/> as it resolves
|
||||
.chain(fragments.map(|(fragment_id, html)| {
|
||||
format!(
|
||||
r#"
|
||||
<template id="{fragment_id}">{html}</template>
|
||||
<script>
|
||||
var frag = document.querySelector(`[data-fragment-id="{fragment_id}"]`);
|
||||
var tpl = document.getElementById("{fragment_id}");
|
||||
console.log("replace", frag, "with", tpl.content.cloneNode(true));
|
||||
frag.replaceWith(tpl.content.cloneNode(true));
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
}))
|
||||
// dispose of Scope
|
||||
.chain(futures::stream::once(async {
|
||||
disposer.dispose();
|
||||
Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,17 @@ pub(crate) enum Mode {
|
|||
|
||||
impl Default for Mode {
|
||||
fn default() -> Self {
|
||||
if cfg!(feature = "ssr") {
|
||||
Mode::Ssr
|
||||
} else if cfg!(feature = "hydrate") {
|
||||
// what's the deal with this order of priority?
|
||||
// basically, it's fine for the server to compile wasm-bindgen, but it will panic if it runs it
|
||||
// for the sake of testing, we need to fall back to `ssr` if no flags are enabled
|
||||
// if you have `hydrate` enabled, you definitely want that rather than `csr`
|
||||
// if you have both `csr` and `ssr` we assume you want the browser
|
||||
if cfg!(feature = "hydrate") {
|
||||
Mode::Hydrate
|
||||
} else if cfg!(feature = "csr") {
|
||||
Mode::Client
|
||||
} else {
|
||||
panic!("one of the features leptos/ssr, leptos/hydrate, or leptos/csr needs to be set")
|
||||
Mode::Ssr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,35 +39,46 @@ mod server;
|
|||
/// same rules as HTML, with the following differences:
|
||||
/// 1. Text content should be provided as a Rust string, i.e., double-quoted:
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// view! { cx, <p>"Here’s some text"</p> }
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! { cx, <p>"Here’s some text"</p> };
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 2. Self-closing tags need an explicit `/` as in XML/XHTML
|
||||
/// ```rust,compile_fail
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// // ❌ not like this
|
||||
/// view! { cx, <input type="text" name="name"> }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// // ✅ add that slash
|
||||
/// view! { cx, <input type="text" name="name" /> }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 3. Components (functions annotated with `#[component]`) can be inserted as camel-cased tags
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use typed_builder::TypedBuilder;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use typed_builder::TypedBuilder; use leptos_dom::wasm_bindgen::JsCast; use leptos_dom as leptos; use leptos_dom::Marker;
|
||||
/// # #[derive(TypedBuilder)] struct CounterProps { initial_value: i32 }
|
||||
/// # fn Counter(cx: Scope, props: CounterProps) -> Element { view! { cx, <p></p>} }
|
||||
/// # run_scope(|cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! { cx, <div><Counter initial_value=3 /></div> }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
|
@ -73,8 +87,9 @@ mod server;
|
|||
/// *(“Signal” here means `Fn() -> T` where `T` is the appropriate type for that node: a `String` in case
|
||||
/// of text nodes, a `bool` for `class:` attributes, etc.)*
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast; use leptos_dom as leptos; use leptos_dom::Marker;
|
||||
/// # run_scope(|cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// view! {
|
||||
|
@ -85,13 +100,16 @@ mod server;
|
|||
/// "Double Count: " {move || count() % 2} // or derive a signal inline
|
||||
/// </div>
|
||||
/// }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 5. Event handlers can be added with `on:` attributes
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <button on:click=|ev: web_sys::Event| {
|
||||
|
@ -100,14 +118,17 @@ mod server;
|
|||
/// "Click me"
|
||||
/// </button>
|
||||
/// }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 6. DOM properties can be set with `prop:` attributes, which take any primitive type or `JsValue` (or a signal
|
||||
/// that returns a primitive or JsValue).
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
///
|
||||
/// view! {
|
||||
|
@ -120,22 +141,28 @@ mod server;
|
|||
/// on:click=move |ev| set_name(event_target_value(&ev)) // `event_target_value` is a useful little Leptos helper
|
||||
/// />
|
||||
/// }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 7. Classes can be toggled with `class:` attributes, which take a `bool` (or a signal that returns a `bool`).
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
|
||||
/// # run_scope(|cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// view! { cx, <div class:hidden={move || count() < 3}>"Now you see me, now you don’t."</div> }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// Here’s a simple example that shows off several of these features, put together
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*;
|
||||
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::*; use leptos_dom as leptos; use leptos_dom::Marker; use leptos_dom::wasm_bindgen::JsCast;
|
||||
///
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// pub fn SimpleCounter(cx: Scope) -> Element {
|
||||
/// // create a reactive signal with the initial value
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
|
@ -157,6 +184,8 @@ mod server;
|
|||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Credit to Dioxus: https://github.com/DioxusLabs/dioxus/blob/master/packages/core-macro/src/Server.rs
|
||||
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use proc_macro2::{TokenStream as TokenStream2};
|
||||
use quote::{quote};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
*, token::Type,
|
||||
*
|
||||
};
|
||||
|
||||
pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Result<TokenStream2> {
|
||||
|
|
|
@ -10,19 +10,25 @@ description = "Reactive system for the Leptos web framework."
|
|||
[dependencies]
|
||||
log = "0.4"
|
||||
slotmap = { version = "1", features = ["serde"] }
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde-lite = { version = "0.3", optional = true }
|
||||
futures = { version = "0.3" }
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
js-sys = "0.3"
|
||||
miniserde = { version = "0.1", optional = true }
|
||||
serde-wasm-bindgen = { version = "0.4", optional = true }
|
||||
serde_json = { version = "1" }
|
||||
base64 = { version = "0.13", optional = true }
|
||||
serde-wasm-bindgen = "0.4"
|
||||
serde_json = "1"
|
||||
base64 = "0.13"
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", features = ["rt"], optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
web-sys = { version = "0.3", optional = true, features = ["Element"] }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = { version = "0.3", features = [
|
||||
"DocumentFragment",
|
||||
"Element",
|
||||
"HtmlTemplateElement",
|
||||
"NodeList",
|
||||
"Window",
|
||||
] }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -30,17 +36,10 @@ tokio-test = "0.4"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
csr = ["dep:wasm-bindgen", "dep:wasm-bindgen-futures", "dep:web-sys"]
|
||||
hydrate = [
|
||||
"dep:base64",
|
||||
"dep:js-sys",
|
||||
"dep:serde-wasm-bindgen",
|
||||
"dep:wasm-bindgen",
|
||||
"dep:wasm-bindgen-futures",
|
||||
"dep:web-sys",
|
||||
]
|
||||
ssr = ["dep:base64", "dep:tokio"]
|
||||
csr = []
|
||||
hydrate = []
|
||||
ssr = ["dep:tokio"]
|
||||
stable = []
|
||||
serde = ["dep:serde"]
|
||||
serde = []
|
||||
serde-lite = ["dep:serde-lite"]
|
||||
miniserde = ["dep:miniserde"]
|
||||
miniserde = ["dep:miniserde"]
|
||||
|
|
|
@ -1,28 +1,18 @@
|
|||
#[cfg(any(feature = "hydrate", feature = "ssr"))]
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
use std::collections::HashSet;
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
#[cfg(any(feature = "hydrate"))]
|
||||
use crate::ResourceId;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SharedContext {
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub completed: Vec<web_sys::Element>,
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub events: Vec<()>,
|
||||
pub context: Option<HydrationContext>,
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub registry: HashMap<String, web_sys::Element>,
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub pending_resources: HashSet<ResourceId>,
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub resolved_resources: HashMap<ResourceId, String>,
|
||||
#[cfg(feature = "ssr")]
|
||||
pub pending_fragments: HashMap<String, Pin<Box<dyn Future<Output = String>>>>,
|
||||
}
|
||||
|
||||
|
@ -32,7 +22,6 @@ impl std::fmt::Debug for SharedContext {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hydrate", not(feature = "ssr")))]
|
||||
impl PartialEq for SharedContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.completed == other.completed
|
||||
|
@ -44,24 +33,10 @@ impl PartialEq for SharedContext {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
impl PartialEq for SharedContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.context == other.context
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "ssr", feature = "hydrate")))]
|
||||
impl PartialEq for SharedContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.context == other.context
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for SharedContext {}
|
||||
|
||||
impl SharedContext {
|
||||
#[cfg(all(feature = "hydrate", not(feature = "ssr")))]
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub fn new_with_registry(registry: HashMap<String, web_sys::Element>) -> Self {
|
||||
let pending_resources = js_sys::Reflect::get(
|
||||
&web_sys::window().unwrap(),
|
||||
|
@ -91,6 +66,7 @@ impl SharedContext {
|
|||
registry,
|
||||
pending_resources,
|
||||
resolved_resources,
|
||||
pending_fragments: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ where
|
|||
.try_with(|n| f(n.as_ref().expect("Memo is missing its initial value")))
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub(crate) fn subscribe(&self) {
|
||||
self.0.subscribe()
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ use crate::{
|
|||
/// let (how_many_cats, set_how_many_cats) = create_signal(cx, 1);
|
||||
///
|
||||
/// // create a resource that will refetch whenever `how_many_cats` changes
|
||||
/// # // `csr`, `hydrate`, and `ssr` all have issues here
|
||||
/// # // because we're not running in a browser or in Tokio. Let's just ignore it.
|
||||
/// # if false {
|
||||
/// let cats = create_resource(cx, how_many_cats, fetch_cat_picture_urls);
|
||||
///
|
||||
/// // when we read the signal, it contains either
|
||||
|
@ -52,6 +55,7 @@ use crate::{
|
|||
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
|
||||
/// set_how_many_cats(2);
|
||||
/// assert_eq!(cats(), Some(vec!["2".to_string()]));
|
||||
/// # }
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
pub fn create_resource<S, T, Fu>(
|
||||
|
@ -160,8 +164,10 @@ where
|
|||
/// ComplicatedUnserializableStruct { }
|
||||
/// }
|
||||
///
|
||||
/// // create the resource that will
|
||||
/// // create the resource; it will run but not be serialized
|
||||
/// # if cfg!(not(any(feature = "csr", feature = "hydrate"))) {
|
||||
/// let result = create_local_resource(cx, move || (), |_| setup_complicated_struct());
|
||||
/// # }
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
pub fn create_local_resource<S, T, Fu>(
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
EffectId, Memo, ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId,
|
||||
ScopeProperty, SignalId, WriteSignal,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use slotmap::{SecondaryMap, SlotMap, SparseSecondaryMap};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
|
@ -13,6 +14,15 @@ use std::{
|
|||
rc::Rc,
|
||||
};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use std::{future::Future, pin::Pin};
|
||||
use futures::stream::FuturesUnordered;
|
||||
|
||||
pub(crate) type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Runtime {
|
||||
pub shared_context: RefCell<Option<SharedContext>>,
|
||||
|
@ -177,7 +187,7 @@ impl Runtime {
|
|||
.insert(AnyResource::Serializable(state))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hydrate", not(feature = "ssr")))]
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub fn start_hydration(&self, element: &web_sys::Element) {
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
|
||||
|
@ -245,13 +255,11 @@ impl Runtime {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "ssr"))]
|
||||
#[cfg(feature = "ssr")]
|
||||
pub(crate) fn serialization_resolvers(
|
||||
&self,
|
||||
) -> futures::stream::futures_unordered::FuturesUnordered<
|
||||
std::pin::Pin<Box<dyn futures::Future<Output = (ResourceId, String)>>>,
|
||||
> {
|
||||
let f = futures::stream::futures_unordered::FuturesUnordered::new();
|
||||
) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
|
||||
let f = FuturesUnordered::new();
|
||||
for (id, resource) in self.resources.borrow().iter() {
|
||||
if let AnyResource::Serializable(resource) = resource {
|
||||
f.push(resource.to_serialization_resolver(id));
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
use crate::SuspenseContext;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
use crate::{hydration::SharedContext, EffectId, ResourceId, Runtime, SignalId};
|
||||
use std::fmt::Debug;
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::{collections::HashMap, future::Future, pin::Pin};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use crate::{PinnedFuture, SuspenseContext};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use std::{collections::HashMap, future::Future, pin::Pin};
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "Scope will leak memory if the disposer function is never called"]
|
||||
/// Creates a child reactive scope and runs the function within it. This is useful for applications
|
||||
|
@ -184,58 +190,58 @@ impl ScopeDisposer {
|
|||
}
|
||||
|
||||
impl Scope {
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub fn is_hydrating(&self) -> bool {
|
||||
self.runtime.shared_context.borrow().is_some()
|
||||
}
|
||||
// hydration-specific code
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
pub fn is_hydrating(&self) -> bool {
|
||||
self.runtime.shared_context.borrow().is_some()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hydrate", not(feature = "ssr")))]
|
||||
pub fn start_hydration(&self, element: &web_sys::Element) {
|
||||
self.runtime.start_hydration(element);
|
||||
}
|
||||
pub fn start_hydration(&self, element: &web_sys::Element) {
|
||||
self.runtime.start_hydration(element);
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub fn end_hydration(&self) {
|
||||
self.runtime.end_hydration();
|
||||
}
|
||||
pub fn end_hydration(&self) {
|
||||
self.runtime.end_hydration();
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub fn get_next_element(&self, template: &web_sys::Element) -> web_sys::Element {
|
||||
//log::debug!("get_next_element");
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
pub fn get_next_element(&self, template: &web_sys::Element) -> web_sys::Element {
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
|
||||
let cloned_template = |t: &web_sys::Element| {
|
||||
let t = t
|
||||
.unchecked_ref::<web_sys::HtmlTemplateElement>()
|
||||
.content()
|
||||
.clone_node_with_deep(true)
|
||||
.expect_throw("(get_next_element) could not clone template")
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.first_element_child()
|
||||
.expect_throw("(get_next_element) could not get first child of template");
|
||||
t
|
||||
};
|
||||
let cloned_template = |t: &web_sys::Element| {
|
||||
let t = t
|
||||
.unchecked_ref::<web_sys::HtmlTemplateElement>()
|
||||
.content()
|
||||
.clone_node_with_deep(true)
|
||||
.expect_throw("(get_next_element) could not clone template")
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.first_element_child()
|
||||
.expect_throw("(get_next_element) could not get first child of template");
|
||||
t
|
||||
};
|
||||
|
||||
if let Some(ref mut shared_context) = &mut *self.runtime.shared_context.borrow_mut() {
|
||||
if shared_context.context.is_some() {
|
||||
let key = shared_context.next_hydration_key();
|
||||
let node = shared_context.registry.remove(&key.to_string());
|
||||
if let Some(ref mut shared_context) = &mut *self.runtime.shared_context.borrow_mut() {
|
||||
if shared_context.context.is_some() {
|
||||
let key = shared_context.next_hydration_key();
|
||||
let node = shared_context.registry.remove(&key);
|
||||
|
||||
//log::debug!("(hy) searching for {key}");
|
||||
//log::debug!("(hy) searching for {key}");
|
||||
|
||||
if let Some(node) = node {
|
||||
//log::debug!("(hy) found {key}");
|
||||
shared_context.completed.push(node.clone());
|
||||
node
|
||||
if let Some(node) = node {
|
||||
//log::debug!("(hy) found {key}");
|
||||
shared_context.completed.push(node.clone());
|
||||
node
|
||||
} else {
|
||||
//log::debug!("(hy) did NOT find {key}");
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
//log::debug!("(hy) did NOT find {key}");
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
cloned_template(template)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,62 +333,58 @@ impl Scope {
|
|||
self.runtime.all_resources()
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn serialization_resolvers(
|
||||
&self,
|
||||
) -> futures::stream::futures_unordered::FuturesUnordered<
|
||||
std::pin::Pin<Box<dyn futures::Future<Output = (ResourceId, String)>>>,
|
||||
> {
|
||||
self.runtime.serialization_resolvers()
|
||||
}
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
pub fn serialization_resolvers(&self) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
|
||||
self.runtime.serialization_resolvers()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn current_fragment_key(&self) -> String {
|
||||
self.runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|context| context.current_fragment_key())
|
||||
.unwrap_or_else(|| String::from("0f"))
|
||||
}
|
||||
pub fn current_fragment_key(&self) -> String {
|
||||
self.runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|context| context.current_fragment_key())
|
||||
.unwrap_or_else(|| String::from("0f"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn register_suspense(
|
||||
&self,
|
||||
context: SuspenseContext,
|
||||
key: &str,
|
||||
resolver: impl FnOnce() -> String + 'static,
|
||||
) {
|
||||
use crate::create_isomorphic_effect;
|
||||
use futures::StreamExt;
|
||||
pub fn register_suspense(
|
||||
&self,
|
||||
context: SuspenseContext,
|
||||
key: &str,
|
||||
resolver: impl FnOnce() -> String + 'static,
|
||||
) {
|
||||
use crate::create_isomorphic_effect;
|
||||
use futures::StreamExt;
|
||||
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
let (mut tx, mut rx) = futures::channel::mpsc::channel::<()>(1);
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
let (mut tx, mut rx) = futures::channel::mpsc::channel::<()>(1);
|
||||
|
||||
create_isomorphic_effect(*self, move |_| {
|
||||
let pending = context.pending_resources.try_with(|n| *n).unwrap_or(0);
|
||||
if pending == 0 {
|
||||
_ = tx.try_send(());
|
||||
create_isomorphic_effect(*self, move |_| {
|
||||
let pending = context.pending_resources.try_with(|n| *n).unwrap_or(0);
|
||||
if pending == 0 {
|
||||
_ = tx.try_send(());
|
||||
}
|
||||
});
|
||||
|
||||
shared_context.pending_fragments.insert(
|
||||
key.to_string(),
|
||||
Box::pin(async move {
|
||||
rx.next().await;
|
||||
resolver()
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shared_context.pending_fragments.insert(
|
||||
key.to_string(),
|
||||
Box::pin(async move {
|
||||
rx.next().await;
|
||||
resolver()
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn pending_fragments(&self) -> HashMap<String, Pin<Box<dyn Future<Output = String>>>> {
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
std::mem::replace(&mut shared_context.pending_fragments, HashMap::new())
|
||||
} else {
|
||||
HashMap::new()
|
||||
pub fn pending_fragments(&self) -> HashMap<String, Pin<Box<dyn Future<Output = String>>>> {
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
std::mem::take(&mut shared_context.pending_fragments)
|
||||
} else {
|
||||
HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSig
|
|||
/// because it reduces them from `O(n)` to `O(1)`.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::{create_effect, create_scope, create_selector, create_signal};
|
||||
/// # use leptos_reactive::{create_isomorphic_effect, create_scope, create_selector, create_signal};
|
||||
/// # use std::rc::Rc;
|
||||
/// # use std::cell::RefCell;
|
||||
/// # create_scope(|cx| {
|
||||
|
@ -19,7 +19,7 @@ use crate::{create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSig
|
|||
/// let is_selected = create_selector(cx, a);
|
||||
/// let total_notifications = Rc::new(RefCell::new(0));
|
||||
/// let not = Rc::clone(&total_notifications);
|
||||
/// create_effect(cx, {let is_selected = is_selected.clone(); move |_| {
|
||||
/// create_isomorphic_effect(cx, {let is_selected = is_selected.clone(); move |_| {
|
||||
/// if is_selected(5) {
|
||||
/// *not.borrow_mut() += 1;
|
||||
/// }
|
||||
|
@ -91,7 +91,7 @@ where
|
|||
let (read, _) = subs
|
||||
.entry(key.clone())
|
||||
.or_insert_with(|| create_signal(cx, false));
|
||||
_ = read.try_with(|n| n.clone());
|
||||
_ = read.try_with(|n| *n);
|
||||
f(&key, v.borrow().as_ref().unwrap())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use cfg_if::cfg_if;
|
||||
use std::rc::Rc;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -18,82 +19,63 @@ where
|
|||
fn from_json(json: &str) -> Result<Self, SerializationError>;
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "serde",
|
||||
not(feature = "miniserde"),
|
||||
not(feature = "serde-lite")
|
||||
))]
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
cfg_if! {
|
||||
// prefer miniserde if it's chosen
|
||||
if #[cfg(feature = "miniserde")] {
|
||||
use miniserde::{json, Deserialize, Serialize};
|
||||
|
||||
impl<T> Serializable for T
|
||||
where
|
||||
T: Serialize + Deserialize,
|
||||
{
|
||||
fn to_json(&self) -> Result<String, SerializationError> {
|
||||
Ok(json::to_string(&self))
|
||||
}
|
||||
|
||||
fn from_json(json: &str) -> Result<Self, SerializationError> {
|
||||
json::from_str(&json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "serde",
|
||||
not(feature = "miniserde"),
|
||||
not(feature = "serde-lite")
|
||||
))]
|
||||
impl<T> Serializable for T
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn to_json(&self) -> Result<String, SerializationError> {
|
||||
serde_json::to_string(&self).map_err(|e| SerializationError::Serialize(Rc::new(e)))
|
||||
}
|
||||
// use serde-lite if enabled
|
||||
else if #[cfg(feature = "serde-lite")] {
|
||||
use serde_lite::{Deserialize, Serialize};
|
||||
|
||||
fn from_json(json: &str) -> Result<Self, SerializationError> {
|
||||
serde_json::from_str(&json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "serde-lite",
|
||||
not(feature = "serde"),
|
||||
not(feature = "miniserde")
|
||||
))]
|
||||
use serde_lite::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(all(
|
||||
feature = "serde-lite",
|
||||
not(feature = "serde"),
|
||||
not(feature = "miniserde")
|
||||
))]
|
||||
impl<T> Serializable for T
|
||||
where
|
||||
T: Serialize + Deserialize,
|
||||
{
|
||||
fn to_json(&self) -> Result<String, SerializationError> {
|
||||
let intermediate = self
|
||||
.serialize()
|
||||
.map_err(|e| SerializationError::Serialize(Rc::new(e)))?;
|
||||
serde_json::to_string(&intermediate).map_err(|e| SerializationError::Serialize(Rc::new(e)))
|
||||
}
|
||||
|
||||
fn from_json(json: &str) -> Result<Self, SerializationError> {
|
||||
let intermediate =
|
||||
serde_json::from_str(&json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))?;
|
||||
Self::deserialize(&intermediate).map_err(|e| SerializationError::Deserialize(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "miniserde",
|
||||
not(feature = "serde-lite"),
|
||||
not(feature = "serde")
|
||||
))]
|
||||
use miniserde::{json, Deserialize, Serialize};
|
||||
|
||||
#[cfg(all(
|
||||
feature = "miniserde",
|
||||
not(feature = "serde-lite"),
|
||||
not(feature = "serde")
|
||||
))]
|
||||
impl<T> Serializable for T
|
||||
where
|
||||
T: Serialize + Deserialize,
|
||||
{
|
||||
fn to_json(&self) -> Result<String, SerializationError> {
|
||||
Ok(json::to_string(&self))
|
||||
}
|
||||
|
||||
fn from_json(json: &str) -> Result<Self, SerializationError> {
|
||||
json::from_str(&json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))
|
||||
impl<T> Serializable for T
|
||||
where
|
||||
T: Serialize + Deserialize,
|
||||
{
|
||||
fn to_json(&self) -> Result<String, SerializationError> {
|
||||
let intermediate = self
|
||||
.serialize()
|
||||
.map_err(|e| SerializationError::Serialize(Rc::new(e)))?;
|
||||
serde_json::to_string(&intermediate).map_err(|e| SerializationError::Serialize(Rc::new(e)))
|
||||
}
|
||||
|
||||
fn from_json(json: &str) -> Result<Self, SerializationError> {
|
||||
let intermediate =
|
||||
serde_json::from_str(&json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))?;
|
||||
Self::deserialize(&intermediate).map_err(|e| SerializationError::Deserialize(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// otherwise, or if serde is chosen, default to serde
|
||||
else {
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
impl<T> Serializable for T
|
||||
where
|
||||
T: DeserializeOwned + Serialize,
|
||||
{
|
||||
fn to_json(&self) -> Result<String, SerializationError> {
|
||||
serde_json::to_string(&self).map_err(|e| SerializationError::Serialize(Rc::new(e)))
|
||||
}
|
||||
|
||||
fn from_json(json: &str) -> Result<Self, SerializationError> {
|
||||
serde_json::from_str(json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,6 +150,7 @@ where
|
|||
self.id.with_no_subscription(self.runtime, f)
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub(crate) fn subscribe(&self) {
|
||||
self.id.subscribe(self.runtime);
|
||||
}
|
||||
|
@ -477,14 +478,6 @@ where
|
|||
self.id.with(self.runtime, f)
|
||||
}
|
||||
|
||||
pub(crate) fn with_no_subscription<U>(&self, f: impl FnOnce(&T) -> U) -> U {
|
||||
self.id.with_no_subscription(self.runtime, f)
|
||||
}
|
||||
|
||||
pub(crate) fn subscribe(&self) {
|
||||
self.id.subscribe(self.runtime);
|
||||
}
|
||||
|
||||
/// Clones and returns the current value of the signal, and subscribes
|
||||
/// the running effect to this signal.
|
||||
/// ```
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
use cfg_if::cfg_if;
|
||||
use std::future::Future;
|
||||
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn queue_microtask(task: impl FnOnce()) {
|
||||
task();
|
||||
}
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
microtask(wasm_bindgen::closure::Closure::once_into_js(task));
|
||||
}
|
||||
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
microtask(wasm_bindgen::closure::Closure::once_into_js(task));
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(
|
||||
inline_js = "export function microtask(f) { queueMicrotask(f); }"
|
||||
)]
|
||||
extern "C" {
|
||||
fn microtask(task: wasm_bindgen::JsValue);
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(
|
||||
inline_js = "export function microtask(f) { queueMicrotask(f); }"
|
||||
)]
|
||||
extern "C" {
|
||||
fn microtask(task: wasm_bindgen::JsValue);
|
||||
}
|
||||
} else {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
pub fn queue_microtask(task: impl FnOnce()) {
|
||||
task();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_local<F>(fut: F)
|
||||
|
@ -29,11 +33,12 @@ where
|
|||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
wasm_bindgen_futures::spawn_local(fut)
|
||||
}
|
||||
else if #[cfg(any(test, doctest))] {
|
||||
tokio_test::block_on(fut);
|
||||
} else if #[cfg(feature = "ssr")] {
|
||||
tokio::task::spawn_local(fut);
|
||||
} else if #[cfg(any(test, doctest))] {
|
||||
tokio_test::block_on(fut);
|
||||
} else {
|
||||
} else {
|
||||
futures::executor::block_on(fut)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue