Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix hydration of suspense fragments that contain text nodes #3287

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[profile]

[profile.dioxus-wasm]
inherits = "dev"
opt-level = 2

[profile.dioxus-server]
inherits = "dev"

[profile.dioxus-android]
inherits = "dev"
15 changes: 7 additions & 8 deletions packages/core/src/root_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,26 @@ pub(crate) fn RootScopeWrapper(props: RootProps<VComponent>) -> Element {
None,
TEMPLATE,
Box::new([DynamicNode::Component(
fc_to_builder(ErrorBoundary)
.children(Element::Ok(VNode::new(
fc_to_builder(SuspenseBoundary)
jkelleyrtp marked this conversation as resolved.
Show resolved Hide resolved
.fallback(|_| Element::Ok(VNode::placeholder()))
.children(Ok(VNode::new(
None,
TEMPLATE,
Box::new([DynamicNode::Component({
#[allow(unused_imports)]
fc_to_builder(SuspenseBoundary)
.fallback(|_| Element::Ok(VNode::placeholder()))
.children(Ok(VNode::new(
fc_to_builder(ErrorBoundary)
.children(Element::Ok(VNode::new(
None,
TEMPLATE,
Box::new([DynamicNode::Component(props.0)]),
Box::new([]),
)))
.build()
.into_vcomponent(SuspenseBoundary)
.into_vcomponent(ErrorBoundary)
})]),
Box::new([]),
)))
.build()
.into_vcomponent(ErrorBoundary),
.into_vcomponent(SuspenseBoundary),
)]),
Box::new([]),
))
Expand Down
47 changes: 46 additions & 1 deletion packages/desktop/src/document.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use dioxus_document::{Document, Eval, EvalError, Evaluator};
use dioxus_core::prelude::queue_effect;
use dioxus_document::{
create_element_in_head, Document, Eval, EvalError, Evaluator, LinkProps, MetaProps,
ScriptProps, StyleProps,
};
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};

use crate::{query::Query, DesktopContext};
Expand All @@ -7,6 +11,7 @@ use crate::{query::Query, DesktopContext};
pub const NATIVE_EVAL_JS: &str = include_str!("./js/native_eval.js");

/// Represents the desktop-target's provider of evaluators.
#[derive(Clone)]
pub struct DesktopDocument {
pub(crate) desktop_ctx: DesktopContext,
}
Expand All @@ -25,6 +30,46 @@ impl Document for DesktopDocument {
fn set_title(&self, title: String) {
self.desktop_ctx.window.set_title(&title);
}

/// Create a new meta tag in the head
fn create_meta(&self, props: MetaProps) {
let myself = self.clone();
queue_effect(move || {
myself.eval(create_element_in_head("meta", &props.attributes(), None));
jkelleyrtp marked this conversation as resolved.
Show resolved Hide resolved
});
}

/// Create a new script tag in the head
fn create_script(&self, props: ScriptProps) {
let myself = self.clone();
queue_effect(move || {
myself.eval(create_element_in_head(
"script",
&props.attributes(),
props.script_contents().ok(),
));
});
}

/// Create a new style tag in the head
fn create_style(&self, props: StyleProps) {
let myself = self.clone();
queue_effect(move || {
myself.eval(create_element_in_head(
"style",
&props.attributes(),
props.style_contents().ok(),
));
});
}

/// Create a new link tag in the head
fn create_link(&self, props: LinkProps) {
let myself = self.clone();
queue_effect(move || {
myself.eval(create_element_in_head("link", &props.attributes(), None));
});
}
}

/// Represents a desktop-target's JavaScript evaluator.
Expand Down
43 changes: 22 additions & 21 deletions packages/document/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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));
}

Expand All @@ -86,37 +91,27 @@ 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
fn create_link(&self, props: LinkProps) {
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
Expand Down Expand Up @@ -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) {}
}
13 changes: 10 additions & 3 deletions packages/document/src/elements/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down Expand Up @@ -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);
});

Expand Down
3 changes: 2 additions & 1 deletion packages/document/src/elements/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
18 changes: 15 additions & 3 deletions packages/document/src/elements/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down Expand Up @@ -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);
});

Expand Down
40 changes: 36 additions & 4 deletions packages/document/src/elements/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down Expand Up @@ -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()
Expand Down
Loading
Loading