Fix playwright tests (#2695)

This commit is contained in:
Evan Almloff authored and GitHub committed 2024-07-24 12:48:30 -07:00
1 parent 9479d2376d
commit bd484842bd
28 files changed
+477 -330

No files matched your search

+5 -1
View File
@@ -1,4 +1,8 @@
//! This module contains the document providers for the fullstack platform.
#[cfg(feature = "server")]
pub(crate) mod server;
#[cfg(feature = "web")]
#[cfg(feature = "server")]
pub use server::ServerDocument;
#[cfg(all(feature = "web", feature = "document"))]
pub(crate) mod web;
+26 -22
View File
@@ -7,6 +7,10 @@ use std::cell::RefCell;
use dioxus_lib::{html::document::*, prelude::*};
use dioxus_ssr::Renderer;
use generational_box::GenerationalBox;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
static RENDERER: Lazy<RwLock<Renderer>> = Lazy::new(|| RwLock::new(Renderer::new()));
#[derive(Default)]
struct ServerDocumentInner {
@@ -19,35 +23,27 @@ struct ServerDocumentInner {
/// A Document provider that collects all contents injected into the head for SSR rendering.
#[derive(Default)]
pub(crate) struct ServerDocument(RefCell<ServerDocumentInner>);
pub struct ServerDocument(RefCell<ServerDocumentInner>);
impl ServerDocument {
pub(crate) fn render(
&self,
to: &mut impl std::fmt::Write,
renderer: &mut Renderer,
) -> std::fmt::Result {
fn lazy_app(props: Element) -> Element {
props
}
pub(crate) fn title(&self) -> Option<String> {
let myself = self.0.borrow();
myself.title.as_ref().map(|title| {
RENDERER
.write()
.render_element(rsx! { title { "{title}" } })
})
}
pub(crate) fn render(&self, to: &mut impl std::fmt::Write) -> std::fmt::Result {
let myself = self.0.borrow();
let element = rsx! {
if let Some(title) = myself.title.as_ref() {
title { title: "{title}" }
}
{myself.meta.iter().map(|m| rsx! { {m} })}
{myself.link.iter().map(|l| rsx! { {l} })}
{myself.script.iter().map(|s| rsx! { {s} })}
};
let mut dom = VirtualDom::new_with_props(lazy_app, element);
dom.rebuild_in_place();
// We don't hydrate the head, so we can set the pre_render flag to false to save a few bytes
let was_pre_rendering = renderer.pre_render;
renderer.pre_render = false;
renderer.render_to(to, &dom)?;
renderer.pre_render = was_pre_rendering;
RENDERER.write().render_element_to(to, element)?;
Ok(())
}
@@ -65,8 +61,12 @@ impl ServerDocument {
/// Write the head element into the serialized context for hydration
/// We write true if the head element was written to the DOM during server side rendering
pub(crate) fn serialize_for_hydration(&self) {
let serialize = crate::html_storage::serialize_context();
serialize.push(&!self.0.borrow().streaming);
// We only serialize the head elements if the web document feature is enabled
#[cfg(feature = "document")]
{
let serialize = crate::html_storage::serialize_context();
serialize.push(&!self.0.borrow().streaming);
}
}
}
@@ -137,4 +137,8 @@ impl Document for ServerDocument {
}
})
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
+4
View File
@@ -55,4 +55,8 @@ impl Document for FullstackWebDocument {
}
WebDocument.create_link(props);
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
+3 -2
View File
@@ -62,8 +62,9 @@ pub fn launch(
#[cfg(feature = "document")]
let factory = move || {
let mut vdom = factory();
vdom.provide_root_context(std::rc::Rc::new(crate::document::web::FullstackWebDocument)
as std::rc::Rc<dyn dioxus_lib::prelude::document::Document>);
let document = std::rc::Rc::new(crate::document::web::FullstackWebDocument)
as std::rc::Rc<dyn dioxus_lib::prelude::document::Document>;
vdom.provide_root_context(document);
vdom
};
+1 -2
View File
@@ -18,8 +18,7 @@ pub mod launch;
pub use config::*;
#[cfg(feature = "document")]
mod document;
pub mod document;
#[cfg(feature = "server")]
mod render;
#[cfg(feature = "server")]
+63 -38
View File
@@ -7,9 +7,9 @@ use dioxus_ssr::{
};
use futures_channel::mpsc::Sender;
use futures_util::{Stream, StreamExt};
use std::sync::Arc;
use std::sync::RwLock;
use std::{collections::HashMap, future::Future};
use std::{fmt::Write, sync::Arc};
use tokio::task::JoinHandle;
use crate::prelude::*;
@@ -160,9 +160,7 @@ impl SsrRendererPool {
let join_handle = spawn_platform(move || async move {
let mut virtual_dom = virtual_dom_factory();
#[cfg(feature = "document")]
let document = std::rc::Rc::new(crate::document::server::ServerDocument::default());
#[cfg(feature = "document")]
virtual_dom.provide_root_context(document.clone() as std::rc::Rc<dyn Document>);
// poll the future, which may call server_context()
@@ -171,34 +169,11 @@ impl SsrRendererPool {
let mut pre_body = String::new();
if let Err(err) = wrapper.render_head(&mut pre_body) {
if let Err(err) = wrapper.render_head(&mut pre_body, &virtual_dom) {
_ = into.start_send(Err(err));
return;
}
#[cfg(feature = "document")]
{
// Collect any head content from the document provider and inject that into the head
if let Err(err) = document.render(&mut pre_body, &mut renderer) {
_ = into.start_send(Err(err.into()));
return;
}
// Enable a warning when inserting contents into the head during streaming
document.start_streaming();
}
if let Err(err) = wrapper.render_before_body(&mut pre_body) {
_ = into.start_send(Err(err));
return;
}
if let Err(err) = write!(&mut pre_body, "<script>{INITIALIZE_STREAMING_JS}</script>") {
_ = into.start_send(Err(
dioxus_ssr::incremental::IncrementalRendererError::RenderError(err),
));
return;
}
let stream = Arc::new(StreamingRenderer::new(pre_body, into));
let scope_to_mount_mapping = Arc::new(RwLock::new(HashMap::new()));
@@ -237,15 +212,8 @@ impl SsrRendererPool {
// Render the initial frame with loading placeholders
let mut initial_frame = renderer.render(&virtual_dom);
// Collect the initial server data from the root node. For most apps, no use_server_futures will be resolved initially, so this will be full on `None`s.
// Sending down those Nones are still important to tell the client not to run the use_server_futures that are already running on the backend
let resolved_data = serialize_server_data(&virtual_dom, ScopeId::ROOT);
initial_frame.push_str(&format!(
r#"<script>window.initial_dioxus_hydration_data="{resolved_data}";</script>"#,
));
// Along with the initial frame, we render the html after the main element, but before the body tag closes. This should include the script that starts loading the wasm bundle.
if let Err(err) = wrapper.render_after_main(&mut initial_frame) {
if let Err(err) = wrapper.render_after_main(&mut initial_frame, &virtual_dom) {
throw_error!(err);
}
stream.render(initial_frame);
@@ -311,7 +279,7 @@ impl SsrRendererPool {
// If incremental rendering is enabled, add the new render to the cache without the streaming bits
if let Some(incremental) = &self.incremental_cache {
let mut cached_render = String::new();
if let Err(err) = wrapper.render_before_body(&mut cached_render) {
if let Err(err) = wrapper.render_head(&mut cached_render, &virtual_dom) {
throw_error!(err);
}
cached_render.push_str(&post_streaming);
@@ -401,16 +369,48 @@ impl FullstackHTMLTemplate {
pub fn render_head<R: std::fmt::Write>(
&self,
to: &mut R,
virtual_dom: &VirtualDom,
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
let ServeConfig { index, .. } = &self.cfg;
to.write_str(&index.head)?;
let title = {
let document: Option<std::rc::Rc<dyn dioxus_lib::prelude::document::Document>> =
virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context());
let document: Option<&crate::document::server::ServerDocument> = document
.as_ref()
.and_then(|document| document.as_any().downcast_ref());
// Collect any head content from the document provider and inject that into the head
document.and_then(|document| document.title())
};
to.write_str(&index.head_before_title)?;
if let Some(title) = title {
to.write_str(&title)?;
} else {
to.write_str(&index.title)?;
}
to.write_str(&index.head_after_title)?;
let document: Option<std::rc::Rc<dyn dioxus_lib::prelude::document::Document>> =
virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context());
let document: Option<&crate::document::server::ServerDocument> = document
.as_ref()
.and_then(|document| document.as_any().downcast_ref());
if let Some(document) = document {
// Collect any head content from the document provider and inject that into the head
document.render(to)?;
// Enable a warning when inserting contents into the head during streaming
document.start_streaming();
}
self.render_before_body(to)?;
Ok(())
}
/// Render any content before the body of the page.
pub fn render_before_body<R: std::fmt::Write>(
fn render_before_body<R: std::fmt::Write>(
&self,
to: &mut R,
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
@@ -418,6 +418,8 @@ impl FullstackHTMLTemplate {
to.write_str(&index.close_head)?;
write!(to, "<script>{INITIALIZE_STREAMING_JS}</script>")?;
Ok(())
}
@@ -425,9 +427,17 @@ impl FullstackHTMLTemplate {
pub fn render_after_main<R: std::fmt::Write>(
&self,
to: &mut R,
virtual_dom: &VirtualDom,
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
let ServeConfig { index, .. } = &self.cfg;
// Collect the initial server data from the root node. For most apps, no use_server_futures will be resolved initially, so this will be full on `None`s.
// Sending down those Nones are still important to tell the client not to run the use_server_futures that are already running on the backend
let resolved_data = serialize_server_data(virtual_dom, ScopeId::ROOT);
write!(
to,
r#"<script>window.initial_dioxus_hydration_data="{resolved_data}";</script>"#,
)?;
to.write_str(&index.post_main)?;
Ok(())
@@ -444,6 +454,21 @@ impl FullstackHTMLTemplate {
Ok(())
}
/// Wrap a body in the template
pub fn wrap_body<R: std::fmt::Write>(
&self,
to: &mut R,
virtual_dom: &VirtualDom,
body: impl std::fmt::Display,
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
self.render_head(to, virtual_dom)?;
write!(to, "{body}")?;
self.render_after_main(to, virtual_dom)?;
self.render_after_body(to)?;
Ok(())
}
}
fn pre_renderer() -> Renderer {
+19 -2
View File
@@ -118,8 +118,23 @@ fn load_index_html(contents: String, root_id: &'static str) -> IndexHtml {
panic!("Failed to find closing </body> tag after id=\"{root_id}\" in index.html.")
});
// Strip out the head if it exists
let mut head_before_title = String::new();
let mut head_after_title = head;
let mut title = String::new();
if let Some((new_head_before_title, new_title)) = head_after_title.split_once("<title>") {
let (new_title, new_head_after_title) = new_title
.split_once("</title>")
.expect("Failed to find closing </title> tag after <title> in index.html.");
title = format!("<title>{new_title}</title>");
head_before_title = new_head_before_title.to_string();
head_after_title = new_head_after_title.to_string();
}
IndexHtml {
head,
head_before_title,
head_after_title,
title,
close_head,
post_main: post_main.to_string(),
after_closing_body_tag: "</body>".to_string() + after_closing_body_tag,
@@ -128,7 +143,9 @@ fn load_index_html(contents: String, root_id: &'static str) -> IndexHtml {
#[derive(Clone)]
pub(crate) struct IndexHtml {
pub(crate) head: String,
pub(crate) head_before_title: String,
pub(crate) head_after_title: String,
pub(crate) title: String,
pub(crate) close_head: String,
pub(crate) post_main: String,
pub(crate) after_closing_body_tag: String,