diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs
index d59dcd30730..2d33150d6b4 100644
--- a/packages/yew/src/html/component/scope.rs
+++ b/packages/yew/src/html/component/scope.rs
@@ -260,7 +260,6 @@ impl Scope {
#[cfg(feature = "ssr")]
mod feat_ssr {
- use futures::channel::mpsc::UnboundedSender;
use futures::channel::oneshot;
use super::*;
@@ -268,19 +267,18 @@ mod feat_ssr {
ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
};
use crate::scheduler;
+ use crate::server_renderer::BufWriter;
use crate::virtual_dom::Collectable;
impl Scope {
pub(crate) async fn render_into_stream(
&self,
- tx: &mut UnboundedSender,
+ w: &mut BufWriter,
props: Rc,
hydratable: bool,
) {
- let (html_tx, html_rx) = oneshot::channel();
- let state = ComponentRenderState::Ssr {
- sender: Some(html_tx),
- };
+ let (tx, rx) = oneshot::channel();
+ let state = ComponentRenderState::Ssr { sender: Some(tx) };
scheduler::push_component_create(
self.id,
@@ -298,17 +296,17 @@ mod feat_ssr {
let collectable = Collectable::for_component::();
if hydratable {
- collectable.write_open_tag(tx);
+ collectable.write_open_tag(w);
}
- let html = html_rx.await.unwrap();
+ let html = rx.await.unwrap();
let self_any_scope = AnyScope::from(self.clone());
- html.render_into_stream(tx, &self_any_scope, hydratable)
+ html.render_into_stream(w, &self_any_scope, hydratable)
.await;
if hydratable {
- collectable.write_close_tag(tx);
+ collectable.write_close_tag(w);
}
scheduler::push_component_destroy(Box::new(DestroyRunner {
diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs
index 97dc8eb8c1e..fbb67a44214 100644
--- a/packages/yew/src/server_renderer.rs
+++ b/packages/yew/src/server_renderer.rs
@@ -1,9 +1,66 @@
-use futures::channel::mpsc;
+use std::borrow::Cow;
+
+use futures::channel::mpsc::{self, UnboundedSender};
use futures::stream::{Stream, StreamExt};
use crate::html::{BaseComponent, Scope};
use crate::platform::{run_pinned, spawn_local};
+pub(crate) struct BufWriter {
+ buf: String,
+ tx: UnboundedSender,
+}
+
+impl BufWriter {
+ pub fn new() -> (Self, impl Stream- ) {
+ let (tx, rx) = mpsc::unbounded::();
+
+ let this = Self {
+ buf: String::with_capacity(4096),
+ tx,
+ };
+
+ (this, rx)
+ }
+
+ /// Writes a string into the buffer, optionally drains the buffer.
+ pub fn write(&mut self, s: Cow<'_, str>) {
+ if s.len() > 4096 {
+ // if the next chunk is more than 4096, we drain the buffer and the next
+ // chunk.
+ if !self.buf.is_empty() {
+ let mut buf = String::with_capacity(4096);
+ std::mem::swap(&mut buf, &mut self.buf);
+ let _ = self.tx.unbounded_send(buf);
+ }
+
+ let _ = self.tx.unbounded_send(s.into_owned());
+ } else if self.buf.len() + s.len() < 4096 {
+ // The length of current chunk and the next part is less than 4096, we push
+ // it on to the buffer.
+ self.buf.push_str(&s);
+ } else {
+ // The length of current chunk and the next part is more than 4096, we send
+ // the current buffer and make the next chunk the new buffer.
+ let mut buf = s.into_owned();
+
+ buf.reserve(4096);
+ std::mem::swap(&mut buf, &mut self.buf);
+ let _ = self.tx.unbounded_send(buf);
+ }
+ }
+}
+
+impl Drop for BufWriter {
+ fn drop(&mut self) {
+ if !self.buf.is_empty() {
+ let mut buf = "".to_string();
+ std::mem::swap(&mut buf, &mut self.buf);
+ let _ = self.tx.unbounded_send(buf);
+ }
+ }
+}
+
/// A Yew Server-side Renderer that renders on the current thread.
#[cfg_attr(documenting, doc(cfg(feature = "ssr")))]
#[derive(Debug)]
@@ -82,12 +139,12 @@ where
// Whilst not required to be async here, this function is async to keep the same function
// signature as the ServerRenderer.
pub async fn render_stream(self) -> impl Stream
- {
- let (mut tx, rx) = mpsc::unbounded::();
+ let (mut w, rx) = BufWriter::new();
let scope = Scope::::new(None);
spawn_local(async move {
scope
- .render_into_stream(&mut tx, self.props.into(), self.hydratable)
+ .render_into_stream(&mut w, self.props.into(), self.hydratable)
.await;
});
diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs
index fad826bd973..0eb099172ce 100644
--- a/packages/yew/src/virtual_dom/mod.rs
+++ b/packages/yew/src/virtual_dom/mod.rs
@@ -265,46 +265,36 @@ pub(crate) use feat_ssr_hydration::*;
#[cfg(feature = "ssr")]
mod feat_ssr {
- use futures::channel::mpsc::UnboundedSender;
-
use super::*;
+ use crate::server_renderer::BufWriter;
impl Collectable {
- pub fn write_open_tag(&self, tx: &mut UnboundedSender) {
- //
- let mut w = String::with_capacity(11);
-
- w.push_str("");
-
- let _ = tx.unbounded_send(w);
+ w.write(self.end_mark().into());
+ w.write("-->".into());
}
- pub fn write_close_tag(&self, tx: &mut UnboundedSender) {
- //
- let mut w = String::with_capacity(12);
- w.push_str("");
-
- let _ = tx.unbounded_send(w);
+ w.write(self.end_mark().into());
+ w.write("-->".into());
}
}
}
diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs
index 9716cb2df3a..b8ee8df4da9 100644
--- a/packages/yew/src/virtual_dom/vcomp.rs
+++ b/packages/yew/src/virtual_dom/vcomp.rs
@@ -4,8 +4,6 @@ use std::any::TypeId;
use std::fmt;
use std::rc::Rc;
-#[cfg(feature = "ssr")]
-use futures::channel::mpsc::UnboundedSender;
#[cfg(feature = "ssr")]
use futures::future::{FutureExt, LocalBoxFuture};
#[cfg(feature = "csr")]
@@ -21,6 +19,8 @@ use crate::html::Scoped;
#[cfg(any(feature = "ssr", feature = "csr"))]
use crate::html::{AnyScope, Scope};
use crate::html::{BaseComponent, NodeRef};
+#[cfg(feature = "ssr")]
+use crate::server_renderer::BufWriter;
/// A virtual component.
pub struct VComp {
@@ -71,7 +71,7 @@ pub(crate) trait Mountable {
#[cfg(feature = "ssr")]
fn render_into_stream<'a>(
&'a self,
- w: &'a mut UnboundedSender,
+ w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()>;
@@ -129,7 +129,7 @@ impl Mountable for PropsWrapper {
#[cfg(feature = "ssr")]
fn render_into_stream<'a>(
&'a self,
- tx: &'a mut UnboundedSender,
+ w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()> {
@@ -137,7 +137,7 @@ impl Mountable for PropsWrapper {
async move {
scope
- .render_into_stream(tx, self.props.clone(), hydratable)
+ .render_into_stream(w, self.props.clone(), hydratable)
.await;
}
.boxed_local()
@@ -246,13 +246,13 @@ mod feat_ssr {
#[inline]
pub(crate) async fn render_into_stream(
&self,
- tx: &mut UnboundedSender,
+ w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
self.mountable
.as_ref()
- .render_into_stream(tx, parent_scope, hydratable)
+ .render_into_stream(w, parent_scope, hydratable)
.await;
}
}
diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs
index 99bdfab88c0..3e40a11284b 100644
--- a/packages/yew/src/virtual_dom/vlist.rs
+++ b/packages/yew/src/virtual_dom/vlist.rs
@@ -146,23 +146,23 @@ mod test {
#[cfg(feature = "ssr")]
mod feat_ssr {
- use futures::channel::mpsc::{self, UnboundedSender};
use futures::stream::{FuturesOrdered, StreamExt};
use super::*;
use crate::html::AnyScope;
+ use crate::server_renderer::BufWriter;
impl VList {
pub(crate) async fn render_into_stream(
&self,
- tx: &mut UnboundedSender,
+ w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
if self.children.len() < 2 {
match self.children.first() {
Some(m) => {
- m.render_into_stream(tx, parent_scope, hydratable).await;
+ m.render_into_stream(w, parent_scope, hydratable).await;
}
None => {}
@@ -176,18 +176,19 @@ mod feat_ssr {
.children
.iter()
.map(|m| async move {
- let (mut tx, rx) = mpsc::unbounded();
+ let (mut w, rx) = BufWriter::new();
- m.render_into_stream(&mut tx, parent_scope, hydratable)
- .await;
- drop(tx);
+ m.render_into_stream(&mut w, parent_scope, hydratable).await;
+ drop(w);
- let s: String = rx.collect().await;
- s
+ rx
})
.collect();
- while let Some(m) = children.next().await {
- let _ = tx.unbounded_send(m);
+
+ while let Some(mut rx) = children.next().await {
+ while let Some(next_chunk) = rx.next().await {
+ w.write(next_chunk.into());
+ }
}
}
}
diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs
index 6b5a7ad39e1..40d862f7fc4 100644
--- a/packages/yew/src/virtual_dom/vnode.rs
+++ b/packages/yew/src/virtual_dom/vnode.rs
@@ -147,37 +147,35 @@ impl PartialEq for VNode {
#[cfg(feature = "ssr")]
mod feat_ssr {
- use futures::channel::mpsc::UnboundedSender;
use futures::future::{FutureExt, LocalBoxFuture};
use super::*;
use crate::html::AnyScope;
+ use crate::server_renderer::BufWriter;
impl VNode {
pub(crate) fn render_into_stream<'a>(
&'a self,
- tx: &'a mut UnboundedSender,
+ w: &'a mut BufWriter,
parent_scope: &'a AnyScope,
hydratable: bool,
) -> LocalBoxFuture<'a, ()> {
async fn render_into_stream_(
this: &VNode,
- tx: &mut UnboundedSender,
+ w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
match this {
- VNode::VTag(vtag) => {
- vtag.render_into_stream(tx, parent_scope, hydratable).await
- }
+ VNode::VTag(vtag) => vtag.render_into_stream(w, parent_scope, hydratable).await,
VNode::VText(vtext) => {
- vtext.render_into_stream(tx, parent_scope, hydratable).await
+ vtext.render_into_stream(w, parent_scope, hydratable).await
}
VNode::VComp(vcomp) => {
- vcomp.render_into_stream(tx, parent_scope, hydratable).await
+ vcomp.render_into_stream(w, parent_scope, hydratable).await
}
VNode::VList(vlist) => {
- vlist.render_into_stream(tx, parent_scope, hydratable).await
+ vlist.render_into_stream(w, parent_scope, hydratable).await
}
// We are pretty safe here as it's not possible to get a web_sys::Node without
// DOM support in the first place.
@@ -191,13 +189,13 @@ mod feat_ssr {
VNode::VPortal(_) => {}
VNode::VSuspense(vsuspense) => {
vsuspense
- .render_into_stream(tx, parent_scope, hydratable)
+ .render_into_stream(w, parent_scope, hydratable)
.await
}
}
}
- async move { render_into_stream_(self, tx, parent_scope, hydratable).await }
+ async move { render_into_stream_(self, w, parent_scope, hydratable).await }
.boxed_local()
}
}
diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs
index 7e54428dd2b..1c9479e492f 100644
--- a/packages/yew/src/virtual_dom/vsuspense.rs
+++ b/packages/yew/src/virtual_dom/vsuspense.rs
@@ -26,32 +26,31 @@ impl VSuspense {
#[cfg(feature = "ssr")]
mod feat_ssr {
- use futures::channel::mpsc::UnboundedSender;
-
use super::*;
use crate::html::AnyScope;
+ use crate::server_renderer::BufWriter;
use crate::virtual_dom::Collectable;
impl VSuspense {
pub(crate) async fn render_into_stream(
&self,
- tx: &mut UnboundedSender,
+ w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
let collectable = Collectable::Suspense;
if hydratable {
- collectable.write_open_tag(tx);
+ collectable.write_open_tag(w);
}
// always render children on the server side.
self.children
- .render_into_stream(tx, parent_scope, hydratable)
+ .render_into_stream(w, parent_scope, hydratable)
.await;
if hydratable {
- collectable.write_close_tag(tx);
+ collectable.write_close_tag(w);
}
}
}
diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs
index 503a65ae3c4..6895e72d189 100644
--- a/packages/yew/src/virtual_dom/vtag.rs
+++ b/packages/yew/src/virtual_dom/vtag.rs
@@ -428,12 +428,9 @@ impl PartialEq for VTag {
#[cfg(feature = "ssr")]
mod feat_ssr {
- use std::fmt::Write;
-
- use futures::channel::mpsc::UnboundedSender;
-
use super::*;
use crate::html::AnyScope;
+ use crate::server_renderer::BufWriter;
use crate::virtual_dom::VText;
// Elements that cannot have any child elements.
@@ -445,59 +442,53 @@ mod feat_ssr {
impl VTag {
pub(crate) async fn render_into_stream(
&self,
- tx: &mut UnboundedSender,
+ w: &mut BufWriter,
parent_scope: &AnyScope,
hydratable: bool,
) {
- // Preallocate a String that is big enough for most elements.
- let mut start_tag = String::with_capacity(64);
- start_tag.push('<');
- start_tag.push_str(self.tag());
-
- let write_attr = |w: &mut String, name: &str, val: Option<&str>| {
- match val {
- Some(val) => {
- write!(
- w,
- "{}=\"{}\"",
- name,
- html_escape::encode_double_quoted_attribute(val)
- )
- }
- None => {
- write!(w, " {}", name)
- }
+ w.write("<".into());
+ w.write(self.tag().into());
+
+ let write_attr = |w: &mut BufWriter, name: &str, val: Option<&str>| match val {
+ Some(val) => w.write(
+ format!(
+ "{}=\"{}\"",
+ name,
+ html_escape::encode_double_quoted_attribute(val)
+ )
+ .into(),
+ ),
+ None => {
+ w.write(name.into());
}
- .unwrap();
};
if let VTagInner::Input(_) = self.inner {
if let Some(m) = self.value() {
- write_attr(&mut start_tag, "value", Some(m));
+ write_attr(w, "value", Some(m));
}
if self.checked() {
- write_attr(&mut start_tag, "checked", None);
+ write_attr(w, "checked", None);
}
}
for (k, v) in self.attributes.iter() {
- write_attr(&mut start_tag, k, Some(v));
+ write_attr(w, k, Some(v));
}
- start_tag.push('>');
- let _ = tx.unbounded_send(start_tag);
+ w.write(">".into());
match self.inner {
VTagInner::Input(_) => {}
VTagInner::Textarea { .. } => {
if let Some(m) = self.value() {
VText::new(m.to_owned())
- .render_into_stream(tx, parent_scope, hydratable)
+ .render_into_stream(w, parent_scope, hydratable)
.await;
}
- let _ = tx.unbounded_send("".into());
+ w.write("".into());
}
VTagInner::Other {
ref tag,
@@ -506,10 +497,10 @@ mod feat_ssr {
} => {
if !VOID_ELEMENTS.contains(&tag.as_ref()) {
children
- .render_into_stream(tx, parent_scope, hydratable)
+ .render_into_stream(w, parent_scope, hydratable)
.await;
- let _ = tx.unbounded_send(format!("{}>", tag));
+ w.write(format!("{}>", tag).into());
} else {
// We don't write children of void elements nor closing tags.
debug_assert!(children.is_empty(), "{} cannot have any children!", tag);
diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs
index 9ac1dae7d97..f0bcb2fb684 100644
--- a/packages/yew/src/virtual_dom/vtext.rs
+++ b/packages/yew/src/virtual_dom/vtext.rs
@@ -34,22 +34,22 @@ impl PartialEq for VText {
#[cfg(feature = "ssr")]
mod feat_ssr {
- use futures::channel::mpsc::UnboundedSender;
use super::*;
use crate::html::AnyScope;
+ use crate::server_renderer::BufWriter;
impl VText {
pub(crate) async fn render_into_stream(
&self,
- tx: &mut UnboundedSender,
+ w: &mut BufWriter,
_parent_scope: &AnyScope,
_hydratable: bool,
) {
let mut s = String::with_capacity(self.text.len());
html_escape::encode_text_to_string(&self.text, &mut s);
- let _ = tx.unbounded_send(s);
+ w.write(s.into());
}
}
}