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::fmt::BufStream;
use crate::platform::{LocalHandle, Runtime};
#[cfg(feature = "ssr")]
pub(crate) mod feat_ssr {
#[derive(Default, Clone, Copy)]
pub(crate) enum VTagKind {
Style,
Script,
#[default]
Other,
}
impl<T: AsRef<str>> From<T> for VTagKind {
fn from(value: T) -> Self {
let value = value.as_ref();
if value.eq_ignore_ascii_case("style") {
Self::Style
} else if value.eq_ignore_ascii_case("script") {
Self::Script
} else {
Self::Other
}
}
}
}
#[cfg(feature = "ssr")]
#[derive(Debug)]
pub struct LocalServerRenderer<COMP>
where
COMP: BaseComponent,
{
props: COMP::Properties,
hydratable: bool,
}
impl<COMP> Default for LocalServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
{
fn default() -> Self {
Self::with_props(COMP::Properties::default())
}
}
impl<COMP> LocalServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
{
pub fn new() -> Self {
Self::default()
}
}
impl<COMP> LocalServerRenderer<COMP>
where
COMP: BaseComponent,
{
pub fn with_props(props: COMP::Properties) -> Self {
Self {
props,
hydratable: true,
}
}
pub fn hydratable(mut self, val: bool) -> Self {
self.hydratable = val;
self
}
pub async fn render(self) -> String {
let s = self.render_stream();
futures::pin_mut!(s);
s.collect().await
}
pub async fn render_to_string(self, w: &mut String) {
let s = self.render_stream();
futures::pin_mut!(s);
while let Some(m) = s.next().await {
w.push_str(&m);
}
}
fn render_stream_inner(self) -> impl Stream<Item = String> {
let scope = Scope::<COMP>::new(None);
let outer_span = tracing::Span::current();
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,
Default::default(),
)
.instrument(render_span)
.await;
})
}
#[allow(clippy::let_with_type_underscore)]
#[tracing::instrument(
level = tracing::Level::DEBUG,
name = "render_stream",
skip(self),
fields(hydratable = self.hydratable),
)]
#[inline(always)]
pub fn render_stream(self) -> impl Stream<Item = String> {
self.render_stream_inner()
}
}
#[cfg(feature = "ssr")]
pub struct ServerRenderer<COMP>
where
COMP: BaseComponent,
{
create_props: Box<dyn Send + FnOnce() -> COMP::Properties>,
hydratable: bool,
rt: Option<Runtime>,
}
impl<COMP> fmt::Debug for ServerRenderer<COMP>
where
COMP: BaseComponent,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ServerRenderer<_>")
}
}
impl<COMP> Default for ServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
{
fn default() -> Self {
Self::with_props(Default::default)
}
}
impl<COMP> ServerRenderer<COMP>
where
COMP: BaseComponent,
COMP::Properties: Default,
{
pub fn new() -> Self {
Self::default()
}
}
impl<COMP> ServerRenderer<COMP>
where
COMP: BaseComponent,
{
pub fn with_props<F>(create_props: F) -> Self
where
F: 'static + Send + FnOnce() -> COMP::Properties,
{
Self {
create_props: Box::new(create_props),
hydratable: true,
rt: None,
}
}
pub fn with_runtime(mut self, rt: Runtime) -> Self {
self.rt = Some(rt);
self
}
pub fn hydratable(mut self, val: bool) -> Self {
self.hydratable = val;
self
}
pub async fn render(self) -> String {
let Self {
create_props,
hydratable,
rt,
} = self;
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;
let _ = tx.send(s);
};
Self::spawn_rendering_task(rt, create_task);
rx.await.expect("failed to render application")
}
pub async fn render_to_string(self, w: &mut String) {
let mut s = self.render_stream();
while let Some(m) = s.next().await {
w.push_str(&m);
}
}
#[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 {
Some(m) => m.spawn_pinned(create_task),
None => match LocalHandle::try_current() {
Some(m) => m.spawn_local(create_task()),
None => Runtime::default().spawn_pinned(create_task),
},
}
}
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
}
}