Fix hydration of suspense fragments that contain text nodes (#3287)
* Fix hydration of suspense fragments that contain text nodes * fix release builds * disable link deduplication * restore meta tag deduplication * Add a test for the failed hydration case * make changes to the document trait non-breaking * remove info log from server future hook * restore default implementation - this was also a breaking change * fix semver breakage * clean up server_future * Cleanup unused code * fix clippy --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
57d637184f
commit
d41ab3a756
33 files changed
+644
-222
No files matched your search
@@ -30,7 +30,10 @@ fn format_attributes(attributes: &[(&str, String)]) -> String {
|
||||
formatted
|
||||
}
|
||||
|
||||
fn create_element_in_head(
|
||||
/// Create a new element in the head with javascript through the [`Document::eval`] method
|
||||
///
|
||||
/// This can be used to implement the head element creation logic for most [`Document`] implementations.
|
||||
pub fn create_element_in_head(
|
||||
tag: &str,
|
||||
attributes: &[(&str, String)],
|
||||
children: Option<String>,
|
||||
@@ -74,6 +77,8 @@ pub trait Document: 'static {
|
||||
attributes: &[(&str, String)],
|
||||
contents: Option<String>,
|
||||
) {
|
||||
// This default implementation remains to make the trait compatible with the 0.6 version, but it should not be used
|
||||
// The element should only be created inside an effect so it is not called while the component is suspended
|
||||
self.eval(create_element_in_head(name, attributes, contents));
|
||||
}
|
||||
|
||||
@@ -86,30 +91,13 @@ pub trait Document: 'static {
|
||||
/// Create a new script tag in the head
|
||||
fn create_script(&self, props: ScriptProps) {
|
||||
let attributes = props.attributes();
|
||||
match (&props.src, props.script_contents()) {
|
||||
// The script has inline contents, render it as a script tag
|
||||
(_, Ok(contents)) => self.create_head_element("script", &attributes, Some(contents)),
|
||||
// The script has a src, render it as a script tag without a body
|
||||
(Some(_), _) => self.create_head_element("script", &attributes, None),
|
||||
// The script has neither contents nor src, log an error
|
||||
(None, Err(err)) => err.log("Script"),
|
||||
}
|
||||
self.create_head_element("script", &attributes, props.script_contents().ok());
|
||||
}
|
||||
|
||||
/// Create a new style tag in the head
|
||||
fn create_style(&self, props: StyleProps) {
|
||||
let mut attributes = props.attributes();
|
||||
match (&props.href, props.style_contents()) {
|
||||
// The style has inline contents, render it as a style tag
|
||||
(_, Ok(contents)) => self.create_head_element("style", &attributes, Some(contents)),
|
||||
// The style has a src, render it as a link tag
|
||||
(Some(_), _) => {
|
||||
attributes.push(("type", "text/css".into()));
|
||||
self.create_head_element("link", &attributes, None)
|
||||
}
|
||||
// The style has neither contents nor src, log an error
|
||||
(None, Err(err)) => err.log("Style"),
|
||||
};
|
||||
let attributes = props.attributes();
|
||||
self.create_head_element("style", &attributes, props.style_contents().ok());
|
||||
}
|
||||
|
||||
/// Create a new link tag in the head
|
||||
@@ -117,6 +105,13 @@ pub trait Document: 'static {
|
||||
let attributes = props.attributes();
|
||||
self.create_head_element("link", &attributes, None);
|
||||
}
|
||||
|
||||
/// Check if we should create a new head component at all. If it returns false, the head component will be skipped.
|
||||
///
|
||||
/// This runs once per head component and is used to hydrate head components in fullstack.
|
||||
fn create_head_component(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A document that does nothing
|
||||
@@ -148,4 +143,10 @@ impl Document for NoOpDocument {
|
||||
}
|
||||
Eval::new(owner.insert(Box::new(NoOpEvaluator)))
|
||||
}
|
||||
|
||||
fn set_title(&self, _: String) {}
|
||||
fn create_meta(&self, _: MetaProps) {}
|
||||
fn create_script(&self, _: ScriptProps) {}
|
||||
fn create_style(&self, _: StyleProps) {}
|
||||
fn create_link(&self, _: LinkProps) {}
|
||||
}
|
||||
@@ -25,7 +25,8 @@ pub struct LinkProps {
|
||||
}
|
||||
|
||||
impl LinkProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
/// Get all the attributes for the link tag
|
||||
pub fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(rel) = &self.rel {
|
||||
attributes.push(("rel", rel.clone()));
|
||||
@@ -104,12 +105,18 @@ pub fn Link(props: LinkProps) -> Element {
|
||||
use_update_warning(&props, "Link {}");
|
||||
|
||||
use_hook(|| {
|
||||
let document = document();
|
||||
let mut insert_link = document.create_head_component();
|
||||
if let Some(href) = &props.href {
|
||||
if !should_insert_link(href) {
|
||||
return;
|
||||
insert_link = false;
|
||||
}
|
||||
}
|
||||
let document = document();
|
||||
|
||||
if !insert_link {
|
||||
return;
|
||||
}
|
||||
|
||||
document.create_link(props);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ pub struct MetaProps {
|
||||
}
|
||||
|
||||
impl MetaProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
/// Get all the attributes for the meta tag
|
||||
pub fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(property) = &self.property {
|
||||
attributes.push(("property", property.clone()));
|
||||
|
||||
@@ -22,7 +22,8 @@ pub struct ScriptProps {
|
||||
}
|
||||
|
||||
impl ScriptProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
/// Get all the attributes for the script tag
|
||||
pub fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(defer) = &self.defer {
|
||||
attributes.push(("defer", defer.to_string()));
|
||||
@@ -90,13 +91,24 @@ pub fn Script(props: ScriptProps) -> Element {
|
||||
use_update_warning(&props, "Script {}");
|
||||
|
||||
use_hook(|| {
|
||||
let document = document();
|
||||
let mut insert_script = document.create_head_component();
|
||||
if let Some(src) = &props.src {
|
||||
if !should_insert_script(src) {
|
||||
return;
|
||||
insert_script = false;
|
||||
}
|
||||
}
|
||||
|
||||
let document = document();
|
||||
if !insert_script {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the props are in a valid form - they must either have a source or children
|
||||
if let (None, Err(err)) = (&props.src, props.script_contents()) {
|
||||
// If the script has neither contents nor src, log an error
|
||||
err.log("Script")
|
||||
}
|
||||
|
||||
document.create_script(props);
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ pub struct StyleProps {
|
||||
}
|
||||
|
||||
impl StyleProps {
|
||||
pub(crate) fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
/// Get all the attributes for the style tag
|
||||
pub fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut attributes = Vec::new();
|
||||
if let Some(href) = &self.href {
|
||||
attributes.push(("href", href.clone()));
|
||||
@@ -71,13 +72,44 @@ pub fn Style(props: StyleProps) -> Element {
|
||||
use_update_warning(&props, "Style {}");
|
||||
|
||||
use_hook(|| {
|
||||
let document = document();
|
||||
let mut insert_style = document.create_head_component();
|
||||
if let Some(href) = &props.href {
|
||||
if !should_insert_style(href) {
|
||||
return;
|
||||
insert_style = false;
|
||||
}
|
||||
}
|
||||
let document = document();
|
||||
document.create_style(props);
|
||||
if !insert_style {
|
||||
return;
|
||||
}
|
||||
let mut attributes = props.attributes();
|
||||
match (&props.href, props.style_contents()) {
|
||||
// The style has inline contents, render it as a style tag
|
||||
(_, Ok(_)) => document.create_style(props),
|
||||
// The style has a src, render it as a link tag
|
||||
(Some(_), _) => {
|
||||
attributes.push(("type", "text/css".into()));
|
||||
document.create_link(LinkProps {
|
||||
media: props.media,
|
||||
title: props.title,
|
||||
r#type: Some("text/css".to_string()),
|
||||
additional_attributes: props.additional_attributes,
|
||||
href: props.href,
|
||||
rel: None,
|
||||
disabled: None,
|
||||
r#as: None,
|
||||
sizes: None,
|
||||
crossorigin: None,
|
||||
referrerpolicy: None,
|
||||
fetchpriority: None,
|
||||
hreflang: None,
|
||||
integrity: None,
|
||||
blocking: None,
|
||||
});
|
||||
}
|
||||
// The style has neither contents nor src, log an error
|
||||
(None, Err(err)) => err.log("Style"),
|
||||
};
|
||||
});
|
||||
|
||||
VNode::empty()
|
||||
|
||||
Reference in new issue
Block a user