Skip to content

Commit 2a908c1

Browse files
authored
feat: adds SSRMountStyleProvider component (#335)
1 parent 03e9e9c commit 2a908c1

File tree

6 files changed

+200
-14
lines changed

6 files changed

+200
-14
lines changed

examples/ssr_axum/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ http = "1"
2323
console_log = "1"
2424
log = "0.4"
2525
demo = { path = "../../demo", default-features = false }
26+
thaw = { path = "../../thaw" }
2627

2728
[features]
2829
hydrate = ["leptos/hydrate", "demo/hydrate"]

examples/ssr_axum/src/app.rs

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
pub use demo::App;
22
use leptos::prelude::*;
33
use leptos_meta::MetaTags;
4+
use thaw::ssr::SSRMountStyleProvider;
45

56
pub fn shell(options: LeptosOptions) -> impl IntoView {
67
view! {
7-
<!DOCTYPE html>
8-
<html lang="en">
9-
<head>
10-
<meta charset="utf-8"/>
11-
<meta name="viewport" content="width=device-width, initial-scale=1"/>
12-
<AutoReload options=options.clone() />
13-
<HydrationScripts options/>
14-
<MetaTags/>
15-
</head>
16-
<body>
17-
<App/>
18-
</body>
19-
</html>
8+
<SSRMountStyleProvider>
9+
<!DOCTYPE html>
10+
<html lang="en">
11+
<head>
12+
<meta charset="utf-8"/>
13+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
14+
<AutoReload options=options.clone() />
15+
<HydrationScripts options/>
16+
<MetaTags/>
17+
</head>
18+
<body>
19+
<App/>
20+
</body>
21+
</html>
22+
</SSRMountStyleProvider>
2023
}
2124
}

thaw/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,7 @@ pub use time_picker::*;
113113
pub use toast::*;
114114
pub use tooltip::*;
115115
pub use upload::*;
116+
117+
pub mod ssr {
118+
pub use thaw_utils::SSRMountStyleProvider;
119+
}

thaw_utils/src/dom/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ mod element;
22
mod get_scroll_parent;
33
mod mount_style;
44
mod scroll_into_view;
5+
mod ssr_mount_style;
56

67
pub use element::*;
78
pub use get_scroll_parent::{get_scroll_parent_element, get_scroll_parent_node};
89
pub use mount_style::{mount_dynamic_style, mount_style};
910
pub use scroll_into_view::scroll_into_view;
11+
pub use ssr_mount_style::{SSRMountStyleProvider, SSRMountStyleContext};

thaw_utils/src/dom/mount_style.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ pub fn mount_style(id: &str, content: &'static str) {
66
if #[cfg(feature = "ssr")] {
77
use leptos::view;
88
use leptos_meta::Style;
9+
use super::SSRMountStyleContext;
10+
11+
if let Some(context) = SSRMountStyleContext::use_context() {
12+
context.push_style(id, content.to_string());
13+
return;
14+
}
915

1016
let _ = view! {
1117
<Style id=id>
@@ -37,8 +43,14 @@ pub fn mount_dynamic_style<T: Fn() -> String + Send + Sync + 'static>(id: String
3743
let id = format!("thaw-id-{id}");
3844
cfg_if! {
3945
if #[cfg(feature = "ssr")] {
40-
use leptos::view;
46+
use leptos::{view, prelude::untrack};
4147
use leptos_meta::Style;
48+
use super::SSRMountStyleContext;
49+
50+
if let Some(context) = SSRMountStyleContext::use_context() {
51+
context.push_style(id, untrack(f));
52+
return;
53+
}
4254

4355
let _ = view! {
4456
<Style id=id>

thaw_utils/src/dom/ssr_mount_style.rs

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use leptos::{
2+
attr::Attribute,
3+
context::{Provider, ProviderProps},
4+
prelude::*,
5+
tachys::{
6+
hydration::Cursor,
7+
renderer::types,
8+
view::{any_view::AnyViewState, Position, PositionState},
9+
},
10+
};
11+
use std::collections::HashMap;
12+
13+
#[component]
14+
pub fn SSRMountStyleProvider<Chil>(children: TypedChildren<Chil>) -> impl IntoView
15+
where
16+
Chil: IntoView + 'static,
17+
{
18+
let context = SSRMountStyleContext::default();
19+
20+
let children = Provider(
21+
ProviderProps::builder()
22+
.value(context.clone())
23+
.children(children)
24+
.build(),
25+
)
26+
.into_any();
27+
SSRMountStyle { context, children }
28+
}
29+
30+
#[derive(Debug, Clone)]
31+
pub struct SSRMountStyleContext {
32+
styles: ArcStoredValue<HashMap<String, String>>,
33+
}
34+
35+
impl SSRMountStyleContext {
36+
pub fn use_context() -> Option<Self> {
37+
use_context()
38+
}
39+
40+
pub fn push_style(&self, k: String, v: String) {
41+
self.styles.write_value().insert(k, v);
42+
}
43+
44+
fn default() -> Self {
45+
Self {
46+
styles: Default::default(),
47+
}
48+
}
49+
50+
fn html_len(&self) -> usize {
51+
const TEMPLATE_LEN: usize = r#"<style id=""></style>"#.len();
52+
let mut html_len = 0;
53+
let styles = self.styles.write_value();
54+
55+
styles.iter().for_each(|(k, v)| {
56+
html_len += k.len() + v.len() + TEMPLATE_LEN;
57+
});
58+
59+
html_len
60+
}
61+
62+
fn to_html(self) -> String {
63+
let mut styles = self.styles.write_value();
64+
styles
65+
.drain()
66+
.into_iter()
67+
.map(|(k, v)| format!(r#"<style id="{k}">{v}</style>"#))
68+
.collect::<String>()
69+
}
70+
}
71+
72+
pub struct SSRMountStyle {
73+
context: SSRMountStyleContext,
74+
children: AnyView,
75+
}
76+
77+
pub struct SSRMountStyleState {
78+
state: AnyViewState,
79+
}
80+
81+
impl Render for SSRMountStyle {
82+
type State = SSRMountStyleState;
83+
84+
fn build(self) -> Self::State {
85+
let state = self.children.build();
86+
SSRMountStyleState { state }
87+
}
88+
89+
fn rebuild(self, state: &mut Self::State) {
90+
self.children.rebuild(&mut state.state);
91+
}
92+
}
93+
94+
impl AddAnyAttr for SSRMountStyle {
95+
type Output<SomeNewAttr: Attribute> = Self;
96+
97+
fn add_any_attr<NewAttr: Attribute>(self, _attr: NewAttr) -> Self::Output<NewAttr>
98+
where
99+
Self::Output<NewAttr>: RenderHtml,
100+
{
101+
self
102+
}
103+
}
104+
105+
impl RenderHtml for SSRMountStyle {
106+
type AsyncOutput = Self;
107+
108+
const MIN_LENGTH: usize = 0;
109+
110+
fn html_len(&self) -> usize {
111+
self.children.html_len() + self.context.html_len()
112+
}
113+
114+
fn dry_resolve(&mut self) {
115+
self.children.dry_resolve();
116+
}
117+
118+
async fn resolve(self) -> Self::AsyncOutput {
119+
self
120+
}
121+
122+
fn to_html_with_buf(
123+
self,
124+
buf: &mut String,
125+
position: &mut Position,
126+
escape: bool,
127+
mark_branches: bool,
128+
) {
129+
self.children
130+
.to_html_with_buf(buf, position, escape, mark_branches);
131+
132+
let head_loc = buf
133+
.find("<head>")
134+
.expect("you are using SSRMountStyleProvider without a <head> tag");
135+
let marker_loc = buf
136+
.find(r#"<meta name="thaw-ui-style""#)
137+
.unwrap_or(head_loc + 6);
138+
139+
buf.insert_str(marker_loc, &self.context.to_html());
140+
}
141+
142+
fn hydrate<const FROM_SERVER: bool>(
143+
self,
144+
cursor: &Cursor,
145+
position: &PositionState,
146+
) -> Self::State {
147+
let state = self.children.hydrate::<FROM_SERVER>(cursor, position);
148+
SSRMountStyleState { state }
149+
}
150+
}
151+
152+
impl Mountable for SSRMountStyleState {
153+
fn unmount(&mut self) {
154+
self.state.unmount();
155+
}
156+
157+
fn mount(&mut self, parent: &types::Element, marker: Option<&types::Node>) {
158+
self.state.mount(parent, marker);
159+
}
160+
161+
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
162+
self.state.insert_before_this(child)
163+
}
164+
}

0 commit comments

Comments
 (0)