diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 5652a613b74..2c88b320790 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -31,9 +31,8 @@ implicit-clone = { version = "0.3", features = ["map"] } base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } -tokio = { version = "1.19", features = ["sync"] } -tokio-stream = { version = "0.1.9", features = ["sync"] } tracing = "0.1.36" +pin-project = "1.0.11" [dependencies.web-sys] version = "^0.3.59" @@ -77,8 +76,10 @@ wasm-bindgen-futures = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] num_cpus = { version = "1.13", optional = true } -tokio-util = { version = "0.7", features = ["rt"], optional = true } once_cell = "1" +tokio = { version = "1.19", features = ["rt", "time"], optional = true } +tokio-stream = { version = "0.1", features = ["time"], optional = true } +tokio-util = { version = "0.7", features = ["rt"], optional = true } [dev-dependencies] wasm-bindgen-test = "0.3" @@ -95,7 +96,7 @@ features = [ ] [features] -tokio = ["tokio/rt", "tokio/time", "dep:num_cpus", "dep:tokio-util"] +tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util", "dep:tokio-stream"] ssr = ["dep:html-escape", "dep:base64ct", "dep:bincode"] csr = [] hydration = ["csr", "dep:bincode"] diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 89645698e28..274c4bd792f 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -291,11 +291,13 @@ impl Scope { #[cfg(feature = "ssr")] mod feat_ssr { + use std::fmt::Write; + use super::*; use crate::html::component::lifecycle::{ ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner, }; - use crate::platform::io::BufWriter; + use crate::platform::fmt::BufWriter; use crate::platform::pinned::oneshot; use crate::scheduler; use crate::virtual_dom::Collectable; @@ -342,9 +344,9 @@ mod feat_ssr { .await; if let Some(prepared_state) = self.get_component().unwrap().prepare_state() { - w.write(r#""#.into()); + let _ = w.write_str(r#""#); } if hydratable { diff --git a/packages/yew/src/platform/fmt/buffer.rs b/packages/yew/src/platform/fmt/buffer.rs new file mode 100644 index 00000000000..28b88c8396a --- /dev/null +++ b/packages/yew/src/platform/fmt/buffer.rs @@ -0,0 +1,212 @@ +use std::cell::UnsafeCell; +use std::fmt::{self, Write}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::task::{Poll, Waker}; + +use futures::stream::{FusedStream, Stream}; + +static BUF_SIZE: usize = 1024; + +enum BufStreamState { + Ready, + Pending(Waker), + Done, +} + +struct Inner { + buf: String, + state: BufStreamState, + + // This type is not send or sync. + _marker: PhantomData>, +} + +impl Inner { + #[inline] + const fn new() -> Self { + Self { + buf: String::new(), + state: BufStreamState::Ready, + _marker: PhantomData, + } + } + + #[inline] + fn wake(&mut self) { + if let BufStreamState::Pending(ref waker) = self.state { + waker.wake_by_ref(); + self.state = BufStreamState::Ready; + } + } + + #[inline] + fn buf_reserve(&mut self) { + if self.buf.is_empty() { + self.buf.reserve(BUF_SIZE); + } + } +} + +impl Write for Inner { + fn write_str(&mut self, s: &str) -> fmt::Result { + if s.is_empty() { + return Ok(()); + } + + self.wake(); + if s.len() < BUF_SIZE { + self.buf_reserve(); + } + + self.buf.write_str(s) + } + + fn write_char(&mut self, c: char) -> fmt::Result { + self.wake(); + self.buf_reserve(); + + self.buf.write_char(c) + } + + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { + self.wake(); + self.buf_reserve(); + + self.buf.write_fmt(args) + } +} + +/// An asynchronous [`String`] writer. +/// +/// This type implements [`fmt::Write`] and can be used with [`write!`] and [`writeln!`]. +pub(crate) struct BufWriter { + inner: Rc>, +} + +impl Write for BufWriter { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + // SAFETY: + // + // We can acquire a mutable reference without checking as: + // + // - This type is !Sync and !Send. + // - This function is not used by any other functions that has access to the inner type. + // - The mutable reference is dropped at the end of this function. + let inner = unsafe { &mut *self.inner.get() }; + + inner.write_str(s) + } + + #[inline] + fn write_char(&mut self, c: char) -> fmt::Result { + // SAFETY: + // + // We can acquire a mutable reference without checking as: + // + // - This type is !Sync and !Send. + // - This function is not used by any other functions that has access to the inner type. + // - The mutable reference is dropped at the end of this function. + let inner = unsafe { &mut *self.inner.get() }; + + inner.write_char(c) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { + // SAFETY: + // + // We can acquire a mutable reference without checking as: + // + // - This type is !Sync and !Send. + // - This function is not used by any other functions that has access to the inner type. + // - The mutable reference is dropped at the end of this function. + let inner = unsafe { &mut *self.inner.get() }; + + inner.write_fmt(args) + } +} + +impl Drop for BufWriter { + fn drop(&mut self) { + // SAFETY: + // + // We can acquire a mutable reference without checking as: + // + // - This type is !Sync and !Send. + // - This function is not used by any other functions that has access to the inner type. + // - The mutable reference is dropped at the end of this function. + let inner = unsafe { &mut *self.inner.get() }; + + inner.wake(); + inner.state = BufStreamState::Done; + } +} + +/// An asynchronous [`String`] reader. +pub(crate) struct BufReader { + inner: Rc>, +} + +impl Stream for BufReader { + type Item = String; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + // SAFETY: + // + // We can acquire a mutable reference without checking as: + // + // - This type is !Sync and !Send. + // - This function is not used by any other functions that has access to the inner type. + // - The mutable reference is dropped at the end of this function. + let inner = unsafe { &mut *self.inner.get() }; + + if !inner.buf.is_empty() { + let buf = std::mem::take(&mut inner.buf); + return Poll::Ready(Some(buf)); + } + + if let BufStreamState::Done = inner.state { + return Poll::Ready(None); + } + + inner.state = BufStreamState::Pending(cx.waker().clone()); + Poll::Pending + } +} + +impl FusedStream for BufReader { + fn is_terminated(&self) -> bool { + // SAFETY: + // + // We can acquire a mutable reference without checking as: + // + // - This type is !Sync and !Send. + // - This function is not used by any other functions that has access to the inner type. + // - The mutable reference is dropped at the end of this function. + let inner = unsafe { &*self.inner.get() }; + + matches!( + (&inner.state, inner.buf.is_empty()), + (BufStreamState::Done, true) + ) + } +} + +/// Creates an asynchronous buffer that operates over String. +pub(crate) fn buffer() -> (BufWriter, BufReader) { + let inner = Rc::new(UnsafeCell::new(Inner::new())); + + let w = { + let inner = inner.clone(); + BufWriter { inner } + }; + + let r = BufReader { inner }; + + (w, r) +} diff --git a/packages/yew/src/platform/fmt/mod.rs b/packages/yew/src/platform/fmt/mod.rs new file mode 100644 index 00000000000..9bba8a61213 --- /dev/null +++ b/packages/yew/src/platform/fmt/mod.rs @@ -0,0 +1,70 @@ +//! Asynchronous utilities to work with `String`s. + +use std::future::Future; + +use futures::future::{self, MaybeDone}; +use futures::stream::{FusedStream, Stream}; +use futures::StreamExt; +use pin_project::pin_project; + +mod buffer; + +pub(crate) use buffer::{buffer, BufReader, BufWriter}; + +/// A buffered asynchronous [`String`] [`Stream`]. +/// +/// A BufStream combines a BufWriter - BufReader pair and a resolving future that writes to the +/// buffer and polls the future alongside the buffer. +#[pin_project] +pub(crate) struct BufStream +where + F: Future, +{ + #[pin] + resolver: MaybeDone, + inner: BufReader, +} + +impl BufStream +where + F: Future, +{ + /// Creates a `BufStream`. + pub fn new(f: C) -> Self + where + C: FnOnce(BufWriter) -> F, + { + let (w, r) = buffer(); + let resolver = future::maybe_done(f(w)); + + BufStream { inner: r, resolver } + } +} + +impl Stream for BufStream +where + F: Future, +{ + type Item = String; + + #[inline] + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let this = self.project(); + let _ = this.resolver.poll(cx); + + this.inner.poll_next_unpin(cx) + } +} + +impl FusedStream for BufStream +where + F: Future, +{ + #[inline] + fn is_terminated(&self) -> bool { + self.inner.is_terminated() + } +} diff --git a/packages/yew/src/platform/io.rs b/packages/yew/src/platform/io.rs deleted file mode 100644 index 14e0e11d56e..00000000000 --- a/packages/yew/src/platform/io.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! This module contains types for I/O functionality. - -// This module should remain private until impl trait type alias becomes available so -// `BufReader` can be produced with an existential type. - -use std::borrow::Cow; - -use futures::stream::Stream; - -use crate::platform::sync::mpsc::{self, UnboundedReceiverStream, UnboundedSender}; - -// Same as std::io::BufWriter and futures::io::BufWriter. -pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; - -/// A [`futures::io::BufWriter`], but operates over string and yields into a Stream. -pub(crate) struct BufWriter { - buf: String, - tx: UnboundedSender, - capacity: usize, -} - -/// Creates a Buffer pair. -pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) { - let (tx, rx) = mpsc::unbounded_channel::(); - - let tx = BufWriter { - buf: String::with_capacity(capacity), - tx, - capacity, - }; - - (tx, UnboundedReceiverStream::new(rx)) -} - -// Implementation Notes: -// -// When jemalloc is used and a reasonable buffer length is chosen, -// performance of this buffer is related to the number of allocations -// instead of the amount of memory that is allocated. -// -// A Bytes-based implementation is also tested, and yielded a similar performance to String-based -// buffer. -// -// Having a String-based buffer avoids unsafe / cost of conversion between String and Bytes -// when text based content is needed (e.g.: post-processing). -// -// `Bytes::from` can be used to convert a `String` to `Bytes` if web server asks for an -// `impl Stream`. This conversion incurs no memory allocation. -// -// Yielding the output with a Stream provides a couple advantages: -// -// 1. All child components of a VList can have their own buffer and be rendered concurrently. -// 2. If a fixed buffer is used, the rendering process can become blocked if the buffer is filled. -// Using a stream avoids this side effect and allows the renderer to finish rendering -// without being actively polled. -impl BufWriter { - #[inline] - pub fn capacity(&self) -> usize { - self.capacity - } - - fn drain(&mut self) { - let _ = self.tx.send(self.buf.drain(..).collect()); - self.buf.reserve(self.capacity); - } - - /// Returns `True` if the internal buffer has capacity to fit a string of certain length. - #[inline] - fn has_capacity_of(&self, next_part_len: usize) -> bool { - self.buf.capacity() >= self.buf.len() + next_part_len - } - - /// Writes a string into the buffer, optionally drains the buffer. - pub fn write(&mut self, s: Cow<'_, str>) { - if !self.has_capacity_of(s.len()) { - // There isn't enough capacity, we drain the buffer. - self.drain(); - } - - if self.has_capacity_of(s.len()) { - // The next part is going to fit into the buffer, we push it onto the buffer. - self.buf.push_str(&s); - } else { - // if the next part is more than buffer size, we send the next part. - - // We don't need to drain the buffer here as the result of self.has_capacity_of() only - // changes if the buffer was drained. If the buffer capacity didn't change, - // then it means self.has_capacity_of() has returned true the first time which will be - // guaranteed to be matched by the left hand side of this implementation. - let _ = self.tx.send(s.into_owned()); - } - } -} - -impl Drop for BufWriter { - fn drop(&mut self) { - if !self.buf.is_empty() { - let mut buf = String::new(); - std::mem::swap(&mut buf, &mut self.buf); - let _ = self.tx.send(buf); - } - } -} diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 4ebdb57c101..65b803e3d88 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -45,10 +45,9 @@ use std::io::Result; use std::marker::PhantomData; #[cfg(feature = "ssr")] -pub(crate) mod io; +pub(crate) mod fmt; pub mod pinned; -pub mod sync; pub mod time; #[cfg(target_arch = "wasm32")] diff --git a/packages/yew/src/platform/pinned/mpsc.rs b/packages/yew/src/platform/pinned/mpsc.rs index 0aa6b95823c..ef920ac0cfb 100644 --- a/packages/yew/src/platform/pinned/mpsc.rs +++ b/packages/yew/src/platform/pinned/mpsc.rs @@ -38,6 +38,9 @@ struct Inner { closed: bool, sender_ctr: usize, items: VecDeque, + + // This type is not send or sync. + _marker: PhantomData>, } impl Inner { @@ -287,6 +290,7 @@ pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { sender_ctr: 1, items: VecDeque::new(), + _marker: PhantomData, })); ( diff --git a/packages/yew/src/platform/pinned/oneshot.rs b/packages/yew/src/platform/pinned/oneshot.rs index db35fc4d115..84db209d926 100644 --- a/packages/yew/src/platform/pinned/oneshot.rs +++ b/packages/yew/src/platform/pinned/oneshot.rs @@ -20,6 +20,9 @@ struct Inner { rx_waker: Option, closed: bool, item: Option, + + // This type is not send or sync. + _marker: PhantomData>, } /// The receiver of a oneshot channel. @@ -138,6 +141,8 @@ pub fn channel() -> (Sender, Receiver) { rx_waker: None, closed: false, item: None, + + _marker: PhantomData, })); ( diff --git a/packages/yew/src/platform/rt_tokio/mod.rs b/packages/yew/src/platform/rt_tokio/mod.rs index 6be2c92b0aa..166826eb0bf 100644 --- a/packages/yew/src/platform/rt_tokio/mod.rs +++ b/packages/yew/src/platform/rt_tokio/mod.rs @@ -108,27 +108,38 @@ impl Runtime { #[cfg(test)] mod tests { + use std::sync::Arc; use std::time::Duration; use futures::channel::oneshot; + use once_cell::sync::Lazy; + use tokio::sync::Barrier; use tokio::test; use tokio::time::timeout; use super::*; + static RUNTIME_2: Lazy = + Lazy::new(|| Runtime::new(2).expect("failed to create runtime.")); + #[test] async fn test_spawn_pinned_least_busy() { - let runtime = Runtime::new(2).expect("failed to create runtime."); - let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); - runtime.spawn_pinned(move || async move { - tx1.send(std::thread::current().id()) - .expect("failed to send!"); - }); + let bar = Arc::new(Barrier::new(2)); - runtime.spawn_pinned(move || async move { + { + let bar = bar.clone(); + RUNTIME_2.spawn_pinned(move || async move { + bar.wait().await; + tx1.send(std::thread::current().id()) + .expect("failed to send!"); + }); + } + + RUNTIME_2.spawn_pinned(move || async move { + bar.wait().await; tx2.send(std::thread::current().id()) .expect("failed to send!"); }); @@ -148,7 +159,7 @@ mod tests { #[test] async fn test_spawn_local_within_send() { - let runtime = Runtime::new(1).expect("failed to create runtime."); + let runtime = Runtime::default(); let (tx, rx) = oneshot::channel(); diff --git a/packages/yew/src/platform/sync/mod.rs b/packages/yew/src/platform/sync/mod.rs deleted file mode 100644 index 63c99dec41a..00000000000 --- a/packages/yew/src/platform/sync/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! A module that provides task synchronisation primitives. - -#[doc(inline)] -pub use tokio::sync::oneshot; -pub mod mpsc; diff --git a/packages/yew/src/platform/sync/mpsc.rs b/packages/yew/src/platform/sync/mpsc.rs deleted file mode 100644 index de09d342bc9..00000000000 --- a/packages/yew/src/platform/sync/mpsc.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! A multi-producer, single-receiver channel. - -#[doc(inline)] -pub use tokio::sync::mpsc::*; -#[doc(inline)] -pub use tokio_stream::wrappers::{ReceiverStream, UnboundedReceiverStream}; diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 8007ff96a43..b716bc9a5b4 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,11 +1,13 @@ use std::fmt; +use std::future::Future; +use futures::pin_mut; use futures::stream::{Stream, StreamExt}; use tracing::Instrument; use crate::html::{BaseComponent, Scope}; -use crate::platform::io::{self, DEFAULT_BUF_SIZE}; -use crate::platform::{spawn_local, LocalHandle, Runtime}; +use crate::platform::fmt::BufStream; +use crate::platform::{LocalHandle, Runtime}; /// A Yew Server-side Renderer that renders on the current thread. /// @@ -13,9 +15,9 @@ use crate::platform::{spawn_local, LocalHandle, Runtime}; /// /// This renderer does not spawn its own runtime and can only be used when: /// -/// - `wasm-bindgen` is selected as the backend of Yew runtime. +/// - `wasm-bindgen-futures` is selected as the backend of Yew runtime. /// - running within a [`Runtime`](crate::platform::Runtime). -/// - running within a tokio [`LocalSet`](tokio::task::LocalSet). +/// - running within a tokio [`LocalSet`](struct@tokio::task::LocalSet). #[cfg(feature = "ssr")] #[derive(Debug)] pub struct LocalServerRenderer @@ -24,7 +26,6 @@ where { props: COMP::Properties, hydratable: bool, - capacity: usize, } impl Default for LocalServerRenderer @@ -57,19 +58,9 @@ where Self { props, hydratable: true, - capacity: DEFAULT_BUF_SIZE, } } - /// Sets the capacity of renderer buffer. - /// - /// Default: `8192` - pub fn capacity(mut self, capacity: usize) -> Self { - self.capacity = capacity; - - self - } - /// Sets whether an the rendered result is hydratable. /// /// Defaults to `true`. @@ -84,16 +75,16 @@ where /// Renders Yew Application. pub async fn render(self) -> String { - let mut s = String::new(); - - self.render_to_string(&mut s).await; + let s = self.render_stream(); + futures::pin_mut!(s); - s + s.collect().await } /// Renders Yew Application to a String. pub async fn render_to_string(self, w: &mut String) { - let mut s = self.render_stream(); + let s = self.render_stream(); + futures::pin_mut!(s); while let Some(m) = s.next().await { w.push_str(&m); @@ -105,29 +96,26 @@ where level = tracing::Level::DEBUG, name = "render", skip(self), - fields(hydratable = self.hydratable, capacity = self.capacity), + fields(hydratable = self.hydratable), )] pub fn render_stream(self) -> impl Stream { - let (mut w, r) = io::buffer(self.capacity); - let scope = Scope::::new(None); + let outer_span = tracing::Span::current(); - spawn_local(async move { + BufStream::new(move |mut w| async move { let render_span = tracing::debug_span!("render_stream_item"); render_span.follows_from(outer_span); scope .render_into_stream(&mut w, self.props.into(), self.hydratable) .instrument(render_span) .await; - }); - - r + }) } } /// A Yew Server-side Renderer. /// -/// This renderer spawns the rendering task to an internal worker pool and receives result when +/// This renderer spawns the rendering task to a Yew [`Runtime`]. and receives result when /// the rendering process has finished. /// /// See [`yew::platform`] for more information. @@ -138,7 +126,6 @@ where { create_props: Box COMP::Properties>, hydratable: bool, - capacity: usize, rt: Option, } @@ -189,7 +176,6 @@ where Self { create_props: Box::new(create_props), hydratable: true, - capacity: DEFAULT_BUF_SIZE, rt: None, } } @@ -201,15 +187,6 @@ where self } - /// Sets the capacity of renderer buffer. - /// - /// Default: `8192` - pub fn capacity(mut self, capacity: usize) -> Self { - self.capacity = capacity; - - self - } - /// Sets whether an the rendered result is hydratable. /// /// Defaults to `true`. @@ -224,11 +201,26 @@ where /// Renders Yew Application. pub async fn render(self) -> String { - let mut s = String::new(); + let Self { + create_props, + hydratable, + rt, + } = self; - self.render_to_string(&mut s).await; + let (tx, rx) = futures::channel::oneshot::channel(); + let create_task = move || async move { + let props = create_props(); + let s = LocalServerRenderer::::with_props(props) + .hydratable(hydratable) + .render() + .await; + + let _ = tx.send(s); + }; + + Self::spawn_rendering_task(rt, create_task); - s + rx.await.expect("failed to render application") } /// Renders Yew Application to a String. @@ -240,36 +232,47 @@ where } } + #[inline] + fn spawn_rendering_task(rt: Option, create_task: F) + where + F: 'static + Send + FnOnce() -> Fut, + Fut: Future + 'static, + { + match rt { + // If a runtime is specified, spawn to the specified runtime. + Some(m) => m.spawn_pinned(create_task), + None => match LocalHandle::try_current() { + // If within a Yew Runtime, spawn to the current runtime. + Some(m) => m.spawn_local(create_task()), + // Outside of Yew Runtime, spawn to the default runtime. + None => Runtime::default().spawn_pinned(create_task), + }, + } + } + /// Renders Yew Application into a string Stream. pub fn render_stream(self) -> impl Send + Stream { let Self { create_props, hydratable, - capacity, rt, } = self; - let (mut w, r) = io::buffer(capacity); + let (tx, rx) = futures::channel::mpsc::unbounded(); let create_task = move || async move { let props = create_props(); - let scope = Scope::::new(None); - - scope - .render_into_stream(&mut w, props.into(), hydratable) - .await; + let s = LocalServerRenderer::::with_props(props) + .hydratable(hydratable) + .render_stream(); + pin_mut!(s); + + while let Some(m) = s.next().await { + let _ = tx.unbounded_send(m); + } }; - match rt { - // If a runtime is specified, spawn to the specified runtime. - Some(m) => m.spawn_pinned(create_task), - None => match LocalHandle::try_current() { - // If within a Yew Runtime, spawn to the current runtime. - Some(m) => m.spawn_local(create_task()), - // Outside of Yew Runtime, spawn to the default runtime. - None => Runtime::default().spawn_pinned(create_task), - }, - } + Self::spawn_rendering_task(rt, create_task); - r + rx } } diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index f24a2db6931..218444a688b 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -111,36 +111,42 @@ pub(crate) use feat_ssr_hydration::*; #[cfg(feature = "ssr")] mod feat_ssr { + use std::fmt::Write; + use super::*; - use crate::platform::io::BufWriter; + use crate::platform::fmt::BufWriter; impl Collectable { pub(crate) fn write_open_tag(&self, w: &mut BufWriter) { - w.write("".into()); + let _ = w.write_str(self.end_mark()); + let _ = w.write_str("-->"); } pub(crate) fn write_close_tag(&self, w: &mut BufWriter) { - w.write("".into()); + let _ = w.write_str(self.end_mark()); + let _ = w.write_str("-->"); } } } diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 02f96beb414..7d16b16cffe 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -20,7 +20,7 @@ use crate::html::{AnyScope, Scope}; #[cfg(feature = "csr")] use crate::html::{NodeRef, Scoped}; #[cfg(feature = "ssr")] -use crate::platform::io::BufWriter; +use crate::platform::fmt::BufWriter; /// A virtual component. pub struct VComp { diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 3f0af242d13..0dc2e25f00a 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -156,6 +156,7 @@ mod test { #[cfg(feature = "ssr")] mod feat_ssr { + use std::fmt::Write; use std::task::Poll; use futures::stream::StreamExt; @@ -163,7 +164,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::platform::io::{self, BufWriter}; + use crate::platform::fmt::{self, BufWriter}; impl VList { pub(crate) async fn render_into_stream( @@ -186,7 +187,6 @@ mod feat_ssr { ) where I: Iterator, { - let buf_capacity = w.capacity(); let mut w = w; while let Some(m) = children.next() { let child_fur = async move { @@ -203,7 +203,7 @@ mod feat_ssr { match poll!(child_fur.as_mut()) { Poll::Pending => { - let (mut next_w, next_r) = io::buffer(buf_capacity); + let (mut next_w, next_r) = fmt::buffer(); // Move buf writer into an async block for it to be dropped at // the end of the future. let rest_render_fur = async move { @@ -223,7 +223,7 @@ mod feat_ssr { pin_mut!(next_r); while let Some(m) = next_r.next().await { - w.write(m.into()); + let _ = w.write_str(m.as_str()); } }; diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 2624e4a772d..6fcd7039823 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -153,7 +153,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::platform::io::BufWriter; + use crate::platform::fmt::BufWriter; impl VNode { pub(crate) fn render_into_stream<'a>( diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index c618a5dcd46..63247b102b8 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -28,7 +28,7 @@ impl VSuspense { mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::platform::io::BufWriter; + use crate::platform::fmt::BufWriter; use crate::virtual_dom::Collectable; impl VSuspense { diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index a4e423a4f59..4e4e5fc88f8 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -440,9 +440,11 @@ impl PartialEq for VTag { #[cfg(feature = "ssr")] mod feat_ssr { + use std::fmt::Write; + use super::*; use crate::html::AnyScope; - use crate::platform::io::BufWriter; + use crate::platform::fmt::BufWriter; use crate::virtual_dom::VText; // Elements that cannot have any child elements. @@ -458,17 +460,17 @@ mod feat_ssr { parent_scope: &AnyScope, hydratable: bool, ) { - w.write("<".into()); - w.write(self.tag().into()); + let _ = w.write_str("<"); + let _ = w.write_str(self.tag()); let write_attr = |w: &mut BufWriter, name: &str, val: Option<&str>| { - w.write(" ".into()); - w.write(name.into()); + let _ = w.write_str(" "); + let _ = w.write_str(name); if let Some(m) = val { - w.write("=\"".into()); - w.write(html_escape::encode_double_quoted_attribute(m)); - w.write("\"".into()); + let _ = w.write_str("=\""); + let _ = w.write_str(&*html_escape::encode_double_quoted_attribute(m)); + let _ = w.write_str("\""); } }; @@ -486,7 +488,7 @@ mod feat_ssr { write_attr(w, k, Some(v)); } - w.write(">".into()); + let _ = w.write_str(">"); match self.inner { VTagInner::Input(_) => {} @@ -497,7 +499,7 @@ mod feat_ssr { .await; } - w.write("".into()); + let _ = w.write_str(""); } VTagInner::Other { ref tag, @@ -509,9 +511,9 @@ mod feat_ssr { .render_into_stream(w, parent_scope, hydratable) .await; - w.write(Cow::Borrowed("")); + let _ = w.write_str(""); } 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 5ee6df06bfe..a84d1ea437f 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -35,9 +35,11 @@ impl PartialEq for VText { #[cfg(feature = "ssr")] mod feat_ssr { + use std::fmt::Write; + use super::*; use crate::html::AnyScope; - use crate::platform::io::BufWriter; + use crate::platform::fmt::BufWriter; impl VText { pub(crate) async fn render_into_stream( @@ -47,7 +49,7 @@ mod feat_ssr { _hydratable: bool, ) { let s = html_escape::encode_text(&self.text); - w.write(s); + let _ = w.write_str(&*s); } } } diff --git a/tools/Cargo.lock b/tools/Cargo.lock index b4c8ba12311..12eb52d61da 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -22,11 +22,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" [[package]] name = "anymap2" @@ -119,9 +128,9 @@ checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "bytes" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -156,22 +165,24 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time", + "wasm-bindgen", "winapi", ] [[package]] name = "clap" -version = "3.2.13" +version = "3.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2bd7a1eb07da9ac757c923f69373deb7bc2ba5efc951b873bcb5e693992dca" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" dependencies = [ "atty", "bitflags", @@ -186,9 +197,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.7" +version = "3.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" dependencies = [ "heck", "proc-macro-error", @@ -290,9 +301,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -789,6 +800,19 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf7d67cf4a22adc5be66e75ebdf769b3f2ea032041437a7061f97a63dad4b" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "idna" version = "0.2.3" @@ -851,9 +875,9 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jemalloc-sys" @@ -924,9 +948,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" [[package]] name = "libgit2-sys" @@ -944,9 +968,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] name = "libssh2-sys" @@ -1228,9 +1252,9 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "prettyplease" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6ffbe862780245013cb1c0a48c4e44b7d665548088f91f6b90876d0625e4c2" +checksum = "697ae720ee02011f439e0701db107ffe2916d83f718342d65d7f8bf7b8a5fee9" dependencies = [ "proc-macro2", "syn", @@ -1262,9 +1286,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] @@ -1280,9 +1304,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -1319,9 +1343,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1406,15 +1430,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c8ad4f0c00e1eb5bc7614d236a7f1300e3dbd76b68cac8e06fb00b015ad8d8" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "schannel" @@ -1463,15 +1487,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" [[package]] name = "serde" -version = "1.0.139" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] @@ -1490,9 +1514,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", @@ -1501,9 +1525,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "itoa", "ryu", @@ -1573,9 +1597,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", "proc-macro2", @@ -1586,9 +1610,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -1657,18 +1681,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -1703,9 +1727,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ "autocfg", "bytes", @@ -1752,7 +1776,6 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", ] [[package]] @@ -1823,9 +1846,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" @@ -1923,9 +1946,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" dependencies = [ "cfg-if", "js-sys", @@ -2122,6 +2145,7 @@ dependencies = [ "js-sys", "num_cpus", "once_cell", + "pin-project", "serde", "slab", "thiserror",