dioxus_fullstack/document/
server.rs1use std::cell::RefCell;
6
7use dioxus_lib::{document::*, prelude::*};
8use dioxus_ssr::Renderer;
9use once_cell::sync::Lazy;
10use parking_lot::RwLock;
11
12static RENDERER: Lazy<RwLock<Renderer>> = Lazy::new(|| RwLock::new(Renderer::new()));
13
14#[derive(Default)]
15struct ServerDocumentInner {
16 streaming: bool,
17 title: Option<String>,
18 meta: Vec<Element>,
19 link: Vec<Element>,
20 script: Vec<Element>,
21}
22
23#[derive(Default)]
25pub struct ServerDocument(RefCell<ServerDocumentInner>);
26
27impl ServerDocument {
28 pub(crate) fn title(&self) -> Option<String> {
29 let myself = self.0.borrow();
30 myself.title.as_ref().map(|title| {
31 RENDERER
32 .write()
33 .render_element(rsx! { title { "{title}" } })
34 })
35 }
36
37 pub(crate) fn render(&self, to: &mut impl std::fmt::Write) -> std::fmt::Result {
38 let myself = self.0.borrow();
39 let element = rsx! {
40 {myself.meta.iter().map(|m| rsx! { {m} })}
41 {myself.link.iter().map(|l| rsx! { {l} })}
42 {myself.script.iter().map(|s| rsx! { {s} })}
43 };
44
45 RENDERER.write().render_element_to(to, element)?;
46
47 Ok(())
48 }
49
50 pub(crate) fn start_streaming(&self) {
51 self.0.borrow_mut().streaming = true;
52 }
53
54 pub(crate) fn warn_if_streaming(&self) {
55 if self.0.borrow().streaming {
56 tracing::warn!("Attempted to insert content into the head after the initial streaming frame. Inserting content into the head only works during the initial render of SSR outside before resolving any suspense boundaries.");
57 }
58 }
59
60 #[track_caller]
63 pub(crate) fn serialize_for_hydration(&self) {
64 #[cfg(feature = "document")]
66 {
67 let serialize = crate::html_storage::serialize_context();
68 serialize.push(&!self.0.borrow().streaming, std::panic::Location::caller());
69 }
70 }
71}
72
73impl Document for ServerDocument {
74 fn eval(&self, js: String) -> Eval {
75 NoOpDocument.eval(js)
76 }
77
78 fn set_title(&self, title: String) {
79 self.warn_if_streaming();
80 self.0.borrow_mut().title = Some(title);
81 }
82
83 fn create_meta(&self, props: MetaProps) {
84 self.0.borrow_mut().meta.push(rsx! {
85 meta {
86 name: props.name,
87 charset: props.charset,
88 http_equiv: props.http_equiv,
89 content: props.content,
90 property: props.property,
91 ..props.additional_attributes,
92 }
93 });
94 }
95
96 fn create_script(&self, props: ScriptProps) {
97 let children = props.script_contents().ok();
98 self.0.borrow_mut().script.push(rsx! {
99 script {
100 src: props.src,
101 defer: props.defer,
102 crossorigin: props.crossorigin,
103 fetchpriority: props.fetchpriority,
104 integrity: props.integrity,
105 nomodule: props.nomodule,
106 nonce: props.nonce,
107 referrerpolicy: props.referrerpolicy,
108 r#type: props.r#type,
109 ..props.additional_attributes,
110 {children}
111 }
112 });
113 }
114
115 fn create_style(&self, props: StyleProps) {
116 let contents = props.style_contents().ok();
117 self.0.borrow_mut().script.push(rsx! {
118 style {
119 media: props.media,
120 nonce: props.nonce,
121 title: props.title,
122 ..props.additional_attributes,
123 {contents}
124 }
125 })
126 }
127
128 fn create_link(&self, props: LinkProps) {
129 self.0.borrow_mut().link.push(rsx! {
130 link {
131 rel: props.rel,
132 media: props.media,
133 title: props.title,
134 disabled: props.disabled,
135 r#as: props.r#as,
136 sizes: props.sizes,
137 href: props.href,
138 crossorigin: props.crossorigin,
139 referrerpolicy: props.referrerpolicy,
140 fetchpriority: props.fetchpriority,
141 hreflang: props.hreflang,
142 integrity: props.integrity,
143 r#type: props.r#type,
144 blocking: props.blocking,
145 ..props.additional_attributes,
146 }
147 })
148 }
149
150 fn create_head_component(&self) -> bool {
151 self.warn_if_streaming();
152 self.serialize_for_hydration();
153 true
154 }
155}