render.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. //! A shared pool of renderers for efficient server side rendering.
  2. use crate::render::dioxus_core::NoOpMutations;
  3. use crate::server_context::SERVER_CONTEXT;
  4. use dioxus_lib::prelude::VirtualDom;
  5. use dioxus_ssr::{
  6. incremental::{IncrementalRendererConfig, RenderFreshness, WrapBody},
  7. Renderer,
  8. };
  9. use serde::Serialize;
  10. use std::future::Future;
  11. use std::sync::Arc;
  12. use std::sync::RwLock;
  13. use tokio::task::{spawn_blocking, JoinHandle};
  14. use crate::prelude::*;
  15. use dioxus_lib::prelude::*;
  16. fn spawn_platform<Fut>(f: impl FnOnce() -> Fut + Send + 'static) -> JoinHandle<Fut::Output>
  17. where
  18. Fut: Future + 'static,
  19. Fut::Output: Send + 'static,
  20. {
  21. #[cfg(not(target_arch = "wasm32"))]
  22. {
  23. spawn_blocking(move || {
  24. tokio::runtime::Runtime::new()
  25. .expect("couldn't spawn runtime")
  26. .block_on(f())
  27. })
  28. }
  29. #[cfg(target_arch = "wasm32")]
  30. {
  31. tokio::task::spawn_local(f())
  32. }
  33. }
  34. enum SsrRendererPool {
  35. Renderer(RwLock<Vec<Renderer>>),
  36. Incremental(RwLock<Vec<dioxus_ssr::incremental::IncrementalRenderer>>),
  37. }
  38. impl SsrRendererPool {
  39. async fn render_to(
  40. &self,
  41. cfg: &ServeConfig,
  42. route: String,
  43. virtual_dom_factory: impl FnOnce() -> VirtualDom + Send + Sync + 'static,
  44. server_context: &DioxusServerContext,
  45. ) -> Result<(RenderFreshness, String), dioxus_ssr::incremental::IncrementalRendererError> {
  46. let wrapper = FullstackRenderer {
  47. serialized_props: None,
  48. cfg: cfg.clone(),
  49. server_context: server_context.clone(),
  50. };
  51. match self {
  52. Self::Renderer(pool) => {
  53. let server_context = Box::new(server_context.clone());
  54. let mut renderer = pool.write().unwrap().pop().unwrap_or_else(pre_renderer);
  55. let (tx, rx) = tokio::sync::oneshot::channel();
  56. spawn_platform(move || async move {
  57. let mut vdom = virtual_dom_factory();
  58. let mut to = WriteBuffer { buffer: Vec::new() };
  59. // before polling the future, we need to set the context
  60. let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(server_context));
  61. // poll the future, which may call server_context()
  62. tracing::info!("Rebuilding vdom");
  63. let _ = vdom.rebuild(&mut NoOpMutations);
  64. vdom.wait_for_suspense().await;
  65. tracing::info!("Suspense resolved");
  66. // after polling the future, we need to restore the context
  67. SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
  68. if let Err(err) = wrapper.render_before_body(&mut *to) {
  69. let _ = tx.send(Err(err));
  70. return;
  71. }
  72. if let Err(err) = renderer.render_to(&mut to, &vdom) {
  73. let _ = tx.send(Err(
  74. dioxus_ssr::incremental::IncrementalRendererError::RenderError(err),
  75. ));
  76. return;
  77. }
  78. if let Err(err) = wrapper.render_after_body(&mut *to) {
  79. let _ = tx.send(Err(err));
  80. return;
  81. }
  82. match String::from_utf8(to.buffer) {
  83. Ok(html) => {
  84. let _ = tx.send(Ok((renderer, RenderFreshness::now(None), html)));
  85. }
  86. Err(err) => {
  87. dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(err));
  88. }
  89. }
  90. });
  91. let (renderer, freshness, html) = rx.await.unwrap()?;
  92. pool.write().unwrap().push(renderer);
  93. Ok((freshness, html))
  94. }
  95. Self::Incremental(pool) => {
  96. let mut renderer =
  97. pool.write().unwrap().pop().unwrap_or_else(|| {
  98. incremental_pre_renderer(cfg.incremental.as_ref().unwrap())
  99. });
  100. let (tx, rx) = tokio::sync::oneshot::channel();
  101. let server_context = server_context.clone();
  102. spawn_platform(move || async move {
  103. let mut to = WriteBuffer { buffer: Vec::new() };
  104. match renderer
  105. .render(
  106. route,
  107. virtual_dom_factory,
  108. &mut *to,
  109. |vdom| {
  110. Box::pin(async move {
  111. // before polling the future, we need to set the context
  112. let prev_context = SERVER_CONTEXT
  113. .with(|ctx| ctx.replace(Box::new(server_context)));
  114. // poll the future, which may call server_context()
  115. tracing::info!("Rebuilding vdom");
  116. let _ = vdom.rebuild(&mut NoOpMutations);
  117. vdom.wait_for_suspense().await;
  118. tracing::info!("Suspense resolved");
  119. // after polling the future, we need to restore the context
  120. SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
  121. })
  122. },
  123. &wrapper,
  124. )
  125. .await
  126. {
  127. Ok(freshness) => {
  128. match String::from_utf8(to.buffer).map_err(|err| {
  129. dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(
  130. err,
  131. ))
  132. }) {
  133. Ok(html) => {
  134. let _ = tx.send(Ok((freshness, html)));
  135. }
  136. Err(err) => {
  137. let _ = tx.send(Err(err));
  138. }
  139. }
  140. }
  141. Err(err) => {
  142. let _ = tx.send(Err(err));
  143. }
  144. }
  145. });
  146. let (freshness, html) = rx.await.unwrap()?;
  147. Ok((freshness, html))
  148. }
  149. }
  150. }
  151. }
  152. /// State used in server side rendering. This utilizes a pool of [`dioxus_ssr::Renderer`]s to cache static templates between renders.
  153. #[derive(Clone)]
  154. pub struct SSRState {
  155. // We keep a pool of renderers to avoid re-creating them on every request. They are boxed to make them very cheap to move
  156. renderers: Arc<SsrRendererPool>,
  157. }
  158. impl SSRState {
  159. /// Create a new [`SSRState`].
  160. pub fn new(cfg: &ServeConfig) -> Self {
  161. if cfg.incremental.is_some() {
  162. return Self {
  163. renderers: Arc::new(SsrRendererPool::Incremental(RwLock::new(vec![
  164. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  165. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  166. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  167. incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
  168. ]))),
  169. };
  170. }
  171. Self {
  172. renderers: Arc::new(SsrRendererPool::Renderer(RwLock::new(vec![
  173. pre_renderer(),
  174. pre_renderer(),
  175. pre_renderer(),
  176. pre_renderer(),
  177. ]))),
  178. }
  179. }
  180. /// Render the application to HTML.
  181. pub fn render<'a>(
  182. &'a self,
  183. route: String,
  184. cfg: &'a ServeConfig,
  185. virtual_dom_factory: impl FnOnce() -> VirtualDom + Send + Sync + 'static,
  186. server_context: &'a DioxusServerContext,
  187. ) -> impl std::future::Future<
  188. Output = Result<RenderResponse, dioxus_ssr::incremental::IncrementalRendererError>,
  189. > + Send
  190. + 'a {
  191. async move {
  192. let ServeConfig { .. } = cfg;
  193. let (freshness, html) = self
  194. .renderers
  195. .render_to(cfg, route, virtual_dom_factory, server_context)
  196. .await?;
  197. Ok(RenderResponse { html, freshness })
  198. }
  199. }
  200. }
  201. struct FullstackRenderer {
  202. serialized_props: Option<String>,
  203. cfg: ServeConfig,
  204. server_context: DioxusServerContext,
  205. }
  206. impl dioxus_ssr::incremental::WrapBody for FullstackRenderer {
  207. fn render_before_body<R: std::io::Write>(
  208. &self,
  209. to: &mut R,
  210. ) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
  211. let ServeConfig { index, .. } = &self.cfg;
  212. to.write_all(index.pre_main.as_bytes())?;
  213. Ok(())
  214. }
  215. fn render_after_body<R: std::io::Write>(
  216. &self,
  217. to: &mut R,
  218. ) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
  219. // serialize the props
  220. // TODO: restore props serialization
  221. // crate::html_storage::serialize::encode_props_in_element(&self.cfg.props, to).map_err(
  222. // |err| dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(err)),
  223. // )?;
  224. // serialize the server state
  225. crate::html_storage::serialize::encode_in_element(
  226. &*self.server_context.html_data().map_err(|_| {
  227. dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new({
  228. #[derive(Debug)]
  229. struct HTMLDataReadError;
  230. impl std::fmt::Display for HTMLDataReadError {
  231. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  232. f.write_str(
  233. "Failed to read the server data to serialize it into the HTML",
  234. )
  235. }
  236. }
  237. impl std::error::Error for HTMLDataReadError {}
  238. HTMLDataReadError
  239. }))
  240. })?,
  241. to,
  242. )
  243. .map_err(|err| dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(err)))?;
  244. #[cfg(all(debug_assertions, feature = "hot-reload"))]
  245. {
  246. // In debug mode, we need to add a script to the page that will reload the page if the websocket disconnects to make full recompile hot reloads work
  247. let disconnect_js = r#"(function () {
  248. const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  249. const url = protocol + '//' + window.location.host + '/_dioxus/disconnect';
  250. const poll_interval = 1000;
  251. const reload_upon_connect = () => {
  252. console.log('Disconnected from server. Attempting to reconnect...');
  253. window.setTimeout(
  254. () => {
  255. // Try to reconnect to the websocket
  256. const ws = new WebSocket(url);
  257. ws.onopen = () => {
  258. // If we reconnect, reload the page
  259. window.location.reload();
  260. }
  261. // Otherwise, try again in a second
  262. reload_upon_connect();
  263. },
  264. poll_interval);
  265. };
  266. // on initial page load connect to the disconnect ws
  267. const ws = new WebSocket(url);
  268. // if we disconnect, start polling
  269. ws.onclose = reload_upon_connect;
  270. })()"#;
  271. to.write_all(r#"<script>"#.as_bytes())?;
  272. to.write_all(disconnect_js.as_bytes())?;
  273. to.write_all(r#"</script>"#.as_bytes())?;
  274. }
  275. let ServeConfig { index, .. } = &self.cfg;
  276. to.write_all(index.post_main.as_bytes())?;
  277. Ok(())
  278. }
  279. }
  280. /// A rendered response from the server.
  281. #[derive(Debug)]
  282. pub struct RenderResponse {
  283. pub(crate) html: String,
  284. pub(crate) freshness: RenderFreshness,
  285. }
  286. impl RenderResponse {
  287. /// Get the rendered HTML.
  288. pub fn html(&self) -> &str {
  289. &self.html
  290. }
  291. /// Get the freshness of the rendered HTML.
  292. pub fn freshness(&self) -> RenderFreshness {
  293. self.freshness
  294. }
  295. }
  296. fn pre_renderer() -> Renderer {
  297. let mut renderer = Renderer::default();
  298. renderer.pre_render = true;
  299. renderer.into()
  300. }
  301. fn incremental_pre_renderer(
  302. cfg: &IncrementalRendererConfig,
  303. ) -> dioxus_ssr::incremental::IncrementalRenderer {
  304. let mut renderer = cfg.clone().build();
  305. renderer.renderer_mut().pre_render = true;
  306. renderer
  307. }
  308. struct WriteBuffer {
  309. buffer: Vec<u8>,
  310. }
  311. impl std::fmt::Write for WriteBuffer {
  312. fn write_str(&mut self, s: &str) -> std::fmt::Result {
  313. self.buffer.extend_from_slice(s.as_bytes());
  314. Ok(())
  315. }
  316. }
  317. impl std::ops::Deref for WriteBuffer {
  318. type Target = Vec<u8>;
  319. fn deref(&self) -> &Self::Target {
  320. &self.buffer
  321. }
  322. }
  323. impl std::ops::DerefMut for WriteBuffer {
  324. fn deref_mut(&mut self) -> &mut Self::Target {
  325. &mut self.buffer
  326. }
  327. }