From 37ab7b34f992943ddbcd61ace466400c88e16f02 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Mon, 12 Dec 2022 09:49:15 -0500 Subject: [PATCH] Fix imports when in SSR mode so we don't run wasm-bindgen code --- leptos/Cargo.toml | 1 + leptos_dom/Cargo.toml | 2 +- leptos_dom/src/lib.rs | 20 +----- leptos_dom/src/ssr.rs | 149 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 20 deletions(-) diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index c3f9c57ee..3537f8015 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -33,6 +33,7 @@ hydrate = [ "leptos_server/hydrate", ] ssr = [ + "leptos_dom/ssr", "leptos_core/ssr", "leptos_macro/ssr", "leptos_reactive/ssr", diff --git a/leptos_dom/Cargo.toml b/leptos_dom/Cargo.toml index a1639186d..b298279db 100644 --- a/leptos_dom/Cargo.toml +++ b/leptos_dom/Cargo.toml @@ -67,7 +67,7 @@ features = [ ] [features] -default = ["web"] +default = [] web = ["leptos_reactive/csr", "leptos/csr"] ssr = ["leptos_reactive/ssr", "leptos/ssr"] stable = ["leptos_reactive/stable"] diff --git a/leptos_dom/src/lib.rs b/leptos_dom/src/lib.rs index cc33ad7d6..382f7893a 100644 --- a/leptos_dom/src/lib.rs +++ b/leptos_dom/src/lib.rs @@ -24,12 +24,15 @@ pub use components::*; pub use events::typed as ev; pub use helpers::*; pub use html::*; +pub use js_sys; use hydration::HydrationCtx; use leptos_reactive::Scope; pub use logging::*; pub use node_ref::*; #[cfg(not(all(target_arch = "wasm32", feature = "web")))] use smallvec::SmallVec; +#[cfg(not(all(target_arch = "wasm32", feature = "web")))] +pub use ssr::*; #[cfg(all(target_arch = "wasm32", feature = "web"))] use std::cell::LazyCell; use std::{borrow::Cow, fmt}; @@ -507,23 +510,6 @@ where std::mem::forget(disposer); } -#[cfg(not(all(target_arch = "wasm32", feature = "web")))] -/// Runs the given function and renders it's result to a string. -pub fn render_to_string(f: F) -> String -where - F: FnOnce(Scope) -> N + 'static, - N: IntoView, -{ - let runtime = leptos_reactive::create_runtime(); - - let view = leptos_reactive::run_scope(runtime, |cx| f(cx).into_view(cx)); - - HydrationCtx::reset_id(); - runtime.dispose(); - - view.render_to_string().into_owned() -} - thread_local! { pub(crate) static WINDOW: web_sys::Window = web_sys::window().unwrap_throw(); diff --git a/leptos_dom/src/ssr.rs b/leptos_dom/src/ssr.rs index 913b2a992..f0c863ce9 100644 --- a/leptos_dom/src/ssr.rs +++ b/leptos_dom/src/ssr.rs @@ -1,13 +1,158 @@ #![cfg(not(all(target_arch = "wasm32", feature = "web")))] -use crate::{CoreComponent, HydrationCtx, View}; +use crate::{CoreComponent, HydrationCtx, IntoView, View}; use cfg_if::cfg_if; +use futures::{Stream, StreamExt, stream::FuturesUnordered}; use itertools::Itertools; use std::borrow::Cow; +use leptos_reactive::*; + +#[cfg(not(all(target_arch = "wasm32", feature = "web")))] +/// Renders the given function to a static HTML string. +/// +/// ``` +/// # cfg_if::cfg_if! { if #[cfg(not(any(feature = "csr", feature = "hydrate")))] { +/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; +/// let html = render_to_string(|cx| view! { cx, +///

"Hello, world!"

+/// }); +/// assert_eq!(html, r#"

Hello, world!

"#); +/// # }} +/// ``` +pub fn render_to_string(f: F) -> String +where + F: FnOnce(Scope) -> N + 'static, + N: IntoView, +{ + let runtime = leptos_reactive::create_runtime(); + + let view = leptos_reactive::run_scope(runtime, |cx| f(cx).into_view(cx)); + + HydrationCtx::reset_id(); + runtime.dispose(); + + view.render_to_string().into_owned() +} + +/// Renders a function to a stream of HTML strings. +/// +/// This renders: +/// 1) the application shell +/// a) HTML for everything that is not under a ``, +/// b) the `fallback` for any `` component that is not already resolved, and +/// c) JavaScript necessary to receive streaming [Resource](leptos_reactive::Resource) data. +/// 2) streaming [Resource](leptos_reactive::Resource) data. Resources begin loading on the +/// server and are sent down to the browser to resolve. On the browser, if the app sees that +/// it is waiting for a resource to resolve from the server, it doesn't run it initially. +/// 3) HTML fragments to replace each `` fallback with its actual data as the resources +/// read under that `` resolve. +pub fn render_to_stream(view: impl FnOnce(Scope) -> View + 'static) -> impl Stream { + render_to_stream_with_prefix(view, |_| "".into()) +} + +/// Renders a function to a stream of HTML strings. After the `view` runs, the `prefix` will run with +/// the same scope. This can be used to generate additional HTML that has access to the same `Scope`. +/// +/// This renders: +/// 1) the prefix +/// 2) the application shell +/// a) HTML for everything that is not under a ``, +/// b) the `fallback` for any `` component that is not already resolved, and +/// c) JavaScript necessary to receive streaming [Resource](leptos_reactive::Resource) data. +/// 3) streaming [Resource](leptos_reactive::Resource) data. Resources begin loading on the +/// server and are sent down to the browser to resolve. On the browser, if the app sees that +/// it is waiting for a resource to resolve from the server, it doesn't run it initially. +/// 4) HTML fragments to replace each `` fallback with its actual data as the resources +/// read under that `` resolve. +pub fn render_to_stream_with_prefix( + view: impl FnOnce(Scope) -> View + 'static, + prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static +) -> impl Stream { + // create the runtime + let runtime = create_runtime(); + + let ((shell, prefix, pending_resources, pending_fragments, serializers), _, disposer) = + run_scope_undisposed(runtime, { + move |cx| { + // the actual app body/template code + // this does NOT contain any of the data being loaded asynchronously in resources + let shell = view(cx).render_to_string(); + + let resources = cx.all_resources(); + let pending_resources = serde_json::to_string(&resources).unwrap(); + let prefix = prefix(cx); + + ( + shell, + prefix, + pending_resources, + cx.pending_fragments(), + cx.serialization_resolvers(), + ) + } + }); + + let fragments = FuturesUnordered::new(); + for (fragment_id, fut) in pending_fragments { + fragments.push(async move { (fragment_id, fut.await) }) + } + + // resources and fragments + let resources_and_fragments = futures::stream::select( + // stream data for each Resource as it resolves + serializers.map(|(id, json)| { + let id = serde_json::to_string(&id).unwrap(); + format!( + r#""#, + ) + }), + // stream HTML for each as it resolves + fragments.map(|(fragment_id, html)| { + format!( + r#" + + + "# + ) + }) + ); + + // HTML for the view function and script to store resources + futures::stream::once(async move { + format!( + r#" + {prefix} + {shell} + + "# + ) + }) + .chain(resources_and_fragments) + // dispose of Scope and Runtime + .chain(futures::stream::once(async move { + disposer.dispose(); + //runtime.dispose(); + Default::default() + })) +} impl View { /// Consumes the node and renders it into an HTML string. - pub(crate) fn render_to_string(self) -> Cow<'static, str> { + pub fn render_to_string(self) -> Cow<'static, str> { match self { View::Text(node) => node.content, View::Component(node) => {