Implement classes in SSR macro path
This commit is contained in:
parent
3ef64bd372
commit
01013b00e5
|
@ -16,9 +16,13 @@ leptos_macro = { path = "../leptos_macro", default-features = false, version = "
|
|||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.18" }
|
||||
leptos_server = { path = "../leptos_server", default-features = false, version = "0.0.18" }
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = ".", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["csr", "serde"]
|
||||
csr = [
|
||||
"leptos/csr",
|
||||
"leptos_core/csr",
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/csr",
|
||||
|
@ -26,6 +30,7 @@ csr = [
|
|||
"leptos_server/csr",
|
||||
]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"leptos_core/hydrate",
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/hydrate",
|
||||
|
@ -33,6 +38,7 @@ hydrate = [
|
|||
"leptos_server/hydrate",
|
||||
]
|
||||
ssr = [
|
||||
"leptos/ssr",
|
||||
"leptos_dom/ssr",
|
||||
"leptos_core/ssr",
|
||||
"leptos_macro/ssr",
|
||||
|
@ -40,6 +46,7 @@ ssr = [
|
|||
"leptos_server/ssr",
|
||||
]
|
||||
stable = [
|
||||
"leptos/stable",
|
||||
"leptos_core/stable",
|
||||
"leptos_dom/stable",
|
||||
"leptos_macro/stable",
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
//! use leptos::*;
|
||||
//!
|
||||
//! #[component]
|
||||
//! pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
|
||||
//! pub fn SimpleCounter(cx: Scope, initial_value: i32) -> impl IntoView {
|
||||
//! // create a reactive signal with the initial value
|
||||
//! let (value, set_value) = create_signal(cx, initial_value);
|
||||
//!
|
||||
|
@ -116,7 +116,7 @@
|
|||
//! # if false { // can't run in doctests
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
|
||||
//! fn SimpleCounter(cx: Scope, initial_value: i32) -> impl IntoView {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn simple_ssr_test() {
|
||||
use leptos_dom::*;
|
||||
use leptos_macro::view;
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
use leptos::*;
|
||||
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let (value, set_value) = create_signal(cx, 0);
|
||||
|
@ -11,13 +9,13 @@ fn simple_ssr_test() {
|
|||
cx,
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1)>"+1"</button>
|
||||
</div>
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
rendered.into_view(cx).render_to_string(cx),
|
||||
r#"<div data-hk="0-0"><button>-1</button><span>Value: <!--#-->0<!--/-->!</span><button>+1</button></div>"# //r#"<div data-hk="0" id="hydrated" data-hk="0"><button>-1</button><span>Value: <!--#-->0<!--/-->!</span><button>+1</button></div>"#
|
||||
);
|
||||
});
|
||||
|
@ -26,20 +24,16 @@ fn simple_ssr_test() {
|
|||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn ssr_test_with_components() {
|
||||
use leptos_core as leptos;
|
||||
use leptos_core::Prop;
|
||||
use leptos_dom::*;
|
||||
use leptos_macro::*;
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal, Scope};
|
||||
use leptos::*;
|
||||
|
||||
#[component]
|
||||
fn Counter(cx: Scope, initial_value: i32) -> Element {
|
||||
fn Counter(cx: Scope, initial_value: i32) -> impl IntoView {
|
||||
let (value, set_value) = create_signal(cx, initial_value);
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1)>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
|
@ -55,7 +49,7 @@ fn ssr_test_with_components() {
|
|||
};
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
rendered.into_view(cx).render_to_string(cx),
|
||||
"<div data-hk=\"0-0\" class=\"counters\"><!--#--><div data-hk=\"0-2-0\"><button>-1</button><span>Value: <!--#-->1<!--/-->!</span><button>+1</button></div><!--/--><!--#--><div data-hk=\"0-3-0\"><button>-1</button><span>Value: <!--#-->2<!--/-->!</span><button>+1</button></div><!--/--></div>"
|
||||
);
|
||||
});
|
||||
|
@ -64,19 +58,17 @@ fn ssr_test_with_components() {
|
|||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
#[test]
|
||||
fn test_classes() {
|
||||
use leptos_dom::*;
|
||||
use leptos_macro::view;
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
use leptos::*;
|
||||
|
||||
_ = create_scope(create_runtime(), |cx| {
|
||||
let (value, set_value) = create_signal(cx, 5);
|
||||
let rendered = view! {
|
||||
cx,
|
||||
<div class="my big" class:a={move || value() > 10} class:red=true class:car={move || value() > 1}></div>
|
||||
<div class="my big" class:a={move || value.get() > 10} class:red=true class:car={move || value.get() > 1}></div>
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rendered,
|
||||
rendered.into_view(cx).render_to_string(cx),
|
||||
r#"<div data-hk="0-0" class="my big red car"></div>"#
|
||||
);
|
||||
});
|
||||
|
|
|
@ -28,6 +28,7 @@ use hydration::HydrationCtx;
|
|||
pub use js_sys;
|
||||
use leptos_reactive::Scope;
|
||||
pub use logging::*;
|
||||
pub use macro_helpers::{IntoClass, IntoAttribute, IntoProperty};
|
||||
pub use node_ref::*;
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
use smallvec::SmallVec;
|
||||
|
|
|
@ -21,7 +21,8 @@ pub enum Attribute {
|
|||
}
|
||||
|
||||
impl Attribute {
|
||||
/// Converts the attribute to its HTML value at that moment so it can be rendered on the server.
|
||||
/// Converts the attribute to its HTML value at that moment, including the attribute name,
|
||||
/// so it can be rendered on the server.
|
||||
pub fn as_value_string(&self, attr_name: &'static str) -> String {
|
||||
match self {
|
||||
Attribute::String(value) => format!("{attr_name}=\"{value}\""),
|
||||
|
@ -45,6 +46,28 @@ impl Attribute {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the attribute to its HTML value at that moment, not including
|
||||
/// the attribute name, so it can be rendered on the server.
|
||||
pub fn as_nameless_value_string(&self) -> String {
|
||||
match self {
|
||||
Attribute::String(value) => value.to_string(),
|
||||
Attribute::Fn(_, f) => {
|
||||
let mut value = f();
|
||||
while let Attribute::Fn(_, f) = value {
|
||||
value = f();
|
||||
}
|
||||
value.as_nameless_value_string()
|
||||
}
|
||||
Attribute::Option(_, value) => value
|
||||
.as_ref()
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_default(),
|
||||
Attribute::Bool(_) => {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Attribute {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod into_attribute;
|
||||
mod into_class;
|
||||
mod into_property;
|
||||
pub(crate) use into_attribute::*;
|
||||
pub(crate) use into_class::*;
|
||||
pub(crate) use into_property::*;
|
||||
pub use into_attribute::*;
|
||||
pub use into_class::*;
|
||||
pub use into_property::*;
|
||||
|
|
|
@ -34,7 +34,7 @@ leptos = { path = "../leptos", version = "0.0" }
|
|||
default = ["ssr"]
|
||||
csr = ["leptos_dom/web", "leptos_reactive/csr"]
|
||||
hydrate = ["leptos_dom/web", "leptos_reactive/hydrate"]
|
||||
ssr = ["leptos_reactive/ssr"]
|
||||
ssr = ["leptos_dom/ssr", "leptos_reactive/ssr"]
|
||||
stable = ["leptos_dom/stable", "leptos_reactive/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
|
|
@ -18,11 +18,6 @@ pub(crate) enum Mode {
|
|||
|
||||
impl Default for Mode {
|
||||
fn default() -> Self {
|
||||
// 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") || cfg!(feature = "csr") || cfg!(feature = "web") {
|
||||
Mode::Client
|
||||
} else {
|
||||
|
@ -234,8 +229,8 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
&proc_macro2::Ident::new(&cx.to_string(), cx.span().into()),
|
||||
&nodes,
|
||||
// swap to Mode::default() to use faster SSR templating
|
||||
Mode::Client
|
||||
//Mode::default(),
|
||||
//Mode::Client
|
||||
Mode::default(),
|
||||
),
|
||||
Err(error) => error.to_compile_error(),
|
||||
}
|
||||
|
|
|
@ -255,7 +255,7 @@ fn element_to_tokens_ssr(cx: &Ident, node: &NodeElement, template: &mut String,
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: handle classes
|
||||
set_class_attribute_ssr(cx, node, template, holes);
|
||||
|
||||
if is_self_closing(node) {
|
||||
template.push_str("/>");
|
||||
|
@ -343,10 +343,12 @@ fn attribute_to_tokens_ssr(cx: &Ident, node: &NodeAttribute, template: &mut Stri
|
|||
// ignore classes: we'll handle these separately
|
||||
} else {
|
||||
let name = name.replacen("attr:", "", 1);
|
||||
template.push(' ');
|
||||
template.push_str(&name);
|
||||
|
||||
if let Some(value) = node.value.as_ref() {
|
||||
if name != "class" {
|
||||
template.push(' ');
|
||||
template.push_str(&name);
|
||||
|
||||
if let Some(value) = node.value.as_ref() {
|
||||
if let Some(value) = value_to_string(value) {
|
||||
template.push_str(&value);
|
||||
} else {
|
||||
|
@ -358,10 +360,99 @@ fn attribute_to_tokens_ssr(cx: &Ident, node: &NodeAttribute, template: &mut Stri
|
|||
})
|
||||
}
|
||||
template.push('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_class_attribute_ssr(cx: &Ident, node: &NodeElement, template: &mut String, holes: &mut Vec<TokenStream>) {
|
||||
let static_class_attr = node.attributes
|
||||
.iter()
|
||||
.filter_map(|a| if let Node::Attribute(a) = a {
|
||||
if a.key.to_string() == "class" {
|
||||
if let Some(value) = a.value.as_ref().and_then(|v| value_to_string(v)) {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
let dyn_class_attr = node.attributes
|
||||
.iter()
|
||||
.filter_map(|a| if let Node::Attribute(a) = a {
|
||||
if a.key.to_string() == "class" {
|
||||
if a.value.as_ref().and_then(value_to_string).is_some() {
|
||||
None
|
||||
} else {
|
||||
Some((a.key.span(), &a.value))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let class_attrs = node.attributes
|
||||
.iter()
|
||||
.filter_map(|node| {
|
||||
if let Node::Attribute(node) = node {
|
||||
let name = node.key.to_string();
|
||||
if name.starts_with("class:") || name.starts_with("class-") {
|
||||
let name = if name.starts_with("class:") {
|
||||
name.replacen("class:", "", 1)
|
||||
} else if name.starts_with("class-") {
|
||||
name.replacen("class-", "", 1)
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let value = node.value.as_ref().expect("class: attributes need values").as_ref();
|
||||
let span = node.key.span();
|
||||
Some((span, name, value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !static_class_attr.is_empty() || !dyn_class_attr.is_empty() || !class_attrs.is_empty() {
|
||||
template.push_str(" class=\"");
|
||||
|
||||
template.push_str(&static_class_attr);
|
||||
|
||||
for (span, value) in dyn_class_attr {
|
||||
if let Some(value) = value {
|
||||
template.push_str(" {}");
|
||||
let value = value.as_ref();
|
||||
holes.push(quote_spanned! {
|
||||
span => leptos::escape_attr(&(cx, #value).into_attribute(#cx).as_nameless_value_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (span, name, value) in &class_attrs {
|
||||
template.push_str(" {}");
|
||||
holes.push(quote_spanned! {
|
||||
*span => (cx, #value).into_class(#cx).as_value_string(#name),
|
||||
});
|
||||
}
|
||||
|
||||
template.push('"');
|
||||
}
|
||||
}
|
||||
|
||||
fn fragment_to_tokens(
|
||||
cx: &Ident,
|
||||
span: Span,
|
||||
|
|
Loading…
Reference in New Issue