mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Polled SSR Stream (#2824)
* Switch to pinned channels. * Fix ServerRenderer so it's not blocked until the result is resolved. * Fix tests. * Remove unused SendError. * Implement a stream to be polled alongside rendering. * Update Buffer Size. * Make Send renderer work. * Remove pinned channels. * Unified Naming. * Optimise code. * Restore capacity. * Remove unused profile. * Default to separate resolver. * Reduce allocations on string. * Adjust API. * Remove duplicate trait bound. * Update docs. * Remove capacity setting. * Unsafe? * Separate files. * Adjust inlining. * Fix test. * Update notice. * Update documentation. * Fix tests.
This commit is contained in:
parent
9484249948
commit
278d2ce08e
@ -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"]
|
||||
|
||||
@ -291,11 +291,13 @@ impl<COMP: BaseComponent> Scope<COMP> {
|
||||
|
||||
#[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#"<script type="application/x-yew-comp-state">"#.into());
|
||||
w.write(prepared_state.into());
|
||||
w.write(r#"</script>"#.into());
|
||||
let _ = w.write_str(r#"<script type="application/x-yew-comp-state">"#);
|
||||
let _ = w.write_str(&prepared_state);
|
||||
let _ = w.write_str(r#"</script>"#);
|
||||
}
|
||||
|
||||
if hydratable {
|
||||
|
||||
212
packages/yew/src/platform/fmt/buffer.rs
Normal file
212
packages/yew/src/platform/fmt/buffer.rs
Normal file
@ -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<Rc<()>>,
|
||||
}
|
||||
|
||||
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<UnsafeCell<Inner>>,
|
||||
}
|
||||
|
||||
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<UnsafeCell<Inner>>,
|
||||
}
|
||||
|
||||
impl Stream for BufReader {
|
||||
type Item = String;
|
||||
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
// 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)
|
||||
}
|
||||
70
packages/yew/src/platform/fmt/mod.rs
Normal file
70
packages/yew/src/platform/fmt/mod.rs
Normal file
@ -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<F>
|
||||
where
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
#[pin]
|
||||
resolver: MaybeDone<F>,
|
||||
inner: BufReader,
|
||||
}
|
||||
|
||||
impl<F> BufStream<F>
|
||||
where
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
/// Creates a `BufStream`.
|
||||
pub fn new<C>(f: C) -> Self
|
||||
where
|
||||
C: FnOnce(BufWriter) -> F,
|
||||
{
|
||||
let (w, r) = buffer();
|
||||
let resolver = future::maybe_done(f(w));
|
||||
|
||||
BufStream { inner: r, resolver }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Stream for BufStream<F>
|
||||
where
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
type Item = String;
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
let this = self.project();
|
||||
let _ = this.resolver.poll(cx);
|
||||
|
||||
this.inner.poll_next_unpin(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> FusedStream for BufStream<F>
|
||||
where
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
#[inline]
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.inner.is_terminated()
|
||||
}
|
||||
}
|
||||
@ -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<String>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
/// Creates a Buffer pair.
|
||||
pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream<Item = String>) {
|
||||
let (tx, rx) = mpsc::unbounded_channel::<String>();
|
||||
|
||||
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<Item = Bytes>`. 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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")]
|
||||
|
||||
@ -38,6 +38,9 @@ struct Inner<T> {
|
||||
closed: bool,
|
||||
sender_ctr: usize,
|
||||
items: VecDeque<T>,
|
||||
|
||||
// This type is not send or sync.
|
||||
_marker: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
impl<T> Inner<T> {
|
||||
@ -287,6 +290,7 @@ pub fn unbounded<T>() -> (UnboundedSender<T>, UnboundedReceiver<T>) {
|
||||
|
||||
sender_ctr: 1,
|
||||
items: VecDeque::new(),
|
||||
_marker: PhantomData,
|
||||
}));
|
||||
|
||||
(
|
||||
|
||||
@ -20,6 +20,9 @@ struct Inner<T> {
|
||||
rx_waker: Option<Waker>,
|
||||
closed: bool,
|
||||
item: Option<T>,
|
||||
|
||||
// This type is not send or sync.
|
||||
_marker: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
/// The receiver of a oneshot channel.
|
||||
@ -138,6 +141,8 @@ pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
|
||||
rx_waker: None,
|
||||
closed: false,
|
||||
item: None,
|
||||
|
||||
_marker: PhantomData,
|
||||
}));
|
||||
|
||||
(
|
||||
|
||||
@ -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<Runtime> =
|
||||
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();
|
||||
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
//! A module that provides task synchronisation primitives.
|
||||
|
||||
#[doc(inline)]
|
||||
pub use tokio::sync::oneshot;
|
||||
pub mod mpsc;
|
||||
@ -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};
|
||||
@ -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<COMP>
|
||||
@ -24,7 +26,6 @@ where
|
||||
{
|
||||
props: COMP::Properties,
|
||||
hydratable: bool,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl<COMP> Default for LocalServerRenderer<COMP>
|
||||
@ -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();
|
||||
let s = self.render_stream();
|
||||
futures::pin_mut!(s);
|
||||
|
||||
self.render_to_string(&mut s).await;
|
||||
|
||||
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<Item = String> {
|
||||
let (mut w, r) = io::buffer(self.capacity);
|
||||
|
||||
let scope = Scope::<COMP>::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<dyn Send + FnOnce() -> COMP::Properties>,
|
||||
hydratable: bool,
|
||||
capacity: usize,
|
||||
rt: Option<Runtime>,
|
||||
}
|
||||
|
||||
@ -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::<COMP>::with_props(props)
|
||||
.hydratable(hydratable)
|
||||
.render()
|
||||
.await;
|
||||
|
||||
s
|
||||
let _ = tx.send(s);
|
||||
};
|
||||
|
||||
Self::spawn_rendering_task(rt, create_task);
|
||||
|
||||
rx.await.expect("failed to render application")
|
||||
}
|
||||
|
||||
/// Renders Yew Application to a String.
|
||||
@ -240,25 +232,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders Yew Application into a string Stream.
|
||||
pub fn render_stream(self) -> impl Send + Stream<Item = String> {
|
||||
let Self {
|
||||
create_props,
|
||||
hydratable,
|
||||
capacity,
|
||||
rt,
|
||||
} = self;
|
||||
|
||||
let (mut w, r) = io::buffer(capacity);
|
||||
let create_task = move || async move {
|
||||
let props = create_props();
|
||||
let scope = Scope::<COMP>::new(None);
|
||||
|
||||
scope
|
||||
.render_into_stream(&mut w, props.into(), hydratable)
|
||||
.await;
|
||||
};
|
||||
|
||||
#[inline]
|
||||
fn spawn_rendering_task<F, Fut>(rt: Option<Runtime>, create_task: F)
|
||||
where
|
||||
F: 'static + Send + FnOnce() -> Fut,
|
||||
Fut: Future<Output = ()> + 'static,
|
||||
{
|
||||
match rt {
|
||||
// If a runtime is specified, spawn to the specified runtime.
|
||||
Some(m) => m.spawn_pinned(create_task),
|
||||
@ -269,7 +248,31 @@ where
|
||||
None => Runtime::default().spawn_pinned(create_task),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
r
|
||||
/// Renders Yew Application into a string Stream.
|
||||
pub fn render_stream(self) -> impl Send + Stream<Item = String> {
|
||||
let Self {
|
||||
create_props,
|
||||
hydratable,
|
||||
rt,
|
||||
} = self;
|
||||
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
let create_task = move || async move {
|
||||
let props = create_props();
|
||||
let s = LocalServerRenderer::<COMP>::with_props(props)
|
||||
.hydratable(hydratable)
|
||||
.render_stream();
|
||||
pin_mut!(s);
|
||||
|
||||
while let Some(m) = s.next().await {
|
||||
let _ = tx.unbounded_send(m);
|
||||
}
|
||||
};
|
||||
|
||||
Self::spawn_rendering_task(rt, create_task);
|
||||
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
w.write(self.open_start_mark().into());
|
||||
let _ = w.write_str("<!--");
|
||||
let _ = w.write_str(self.open_start_mark());
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
match self {
|
||||
Self::Component(type_name) => w.write((*type_name).into()),
|
||||
Self::Component(type_name) => {
|
||||
let _ = w.write_str(*type_name);
|
||||
}
|
||||
Self::Suspense => {}
|
||||
}
|
||||
|
||||
w.write(self.end_mark().into());
|
||||
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());
|
||||
w.write(self.close_start_mark().into());
|
||||
let _ = w.write_str("<!--");
|
||||
let _ = w.write_str(self.close_start_mark());
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
match self {
|
||||
Self::Component(type_name) => w.write((*type_name).into()),
|
||||
Self::Component(type_name) => {
|
||||
let _ = w.write_str(*type_name);
|
||||
}
|
||||
Self::Suspense => {}
|
||||
}
|
||||
|
||||
w.write(self.end_mark().into());
|
||||
w.write("-->".into());
|
||||
let _ = w.write_str(self.end_mark());
|
||||
let _ = w.write_str("-->");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<Item = &'a VNode>,
|
||||
{
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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>(
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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("</textarea>".into());
|
||||
let _ = w.write_str("</textarea>");
|
||||
}
|
||||
VTagInner::Other {
|
||||
ref tag,
|
||||
@ -509,9 +511,9 @@ mod feat_ssr {
|
||||
.render_into_stream(w, parent_scope, hydratable)
|
||||
.await;
|
||||
|
||||
w.write(Cow::Borrowed("</"));
|
||||
w.write(Cow::Borrowed(tag));
|
||||
w.write(Cow::Borrowed(">"));
|
||||
let _ = w.write_str("</");
|
||||
let _ = w.write_str(tag);
|
||||
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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
134
tools/Cargo.lock
generated
134
tools/Cargo.lock
generated
@ -23,10 +23,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.58"
|
||||
name = "android_system_properties"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
|
||||
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user