Fix playwright tests (#2695)
This commit is contained in:
1 parent
9479d2376d
commit
bd484842bd
28 files changed
+477
-330
No files matched your search
@@ -104,7 +104,7 @@ server = [
|
||||
"dep:parking_lot",
|
||||
"dioxus-interpreter-js",
|
||||
"dep:clap",
|
||||
"dioxus-cli-config/read-from-args"
|
||||
"dioxus-cli-config/read-from-args",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,8 @@ impl Document for FullstackWebDocument {
|
||||
}
|
||||
WebDocument.create_link(props);
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user