Fix imports when in SSR mode so we don't run wasm-bindgen code

This commit is contained in:
Greg Johnston 2022-12-12 09:49:15 -05:00
parent fcae17eab7
commit 37ab7b34f9
4 changed files with 152 additions and 20 deletions

View file

@ -33,6 +33,7 @@ hydrate = [
"leptos_server/hydrate",
]
ssr = [
"leptos_dom/ssr",
"leptos_core/ssr",
"leptos_macro/ssr",
"leptos_reactive/ssr",

View file

@ -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"]

View file

@ -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, N>(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();

View file

@ -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,
/// <p>"Hello, world!"</p>
/// });
/// assert_eq!(html, r#"<p>Hello, world!</p>"#);
/// # }}
/// ```
pub fn render_to_string<F, N>(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 `<Suspense/>`,
/// b) the `fallback` for any `<Suspense/>` 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 `<Suspense/>` fallback with its actual data as the resources
/// read under that `<Suspense/>` resolve.
pub fn render_to_stream(view: impl FnOnce(Scope) -> View + 'static) -> impl Stream<Item = String> {
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 `<Suspense/>`,
/// b) the `fallback` for any `<Suspense/>` 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 `<Suspense/>` fallback with its actual data as the resources
/// read under that `<Suspense/>` resolve.
pub fn render_to_stream_with_prefix(
view: impl FnOnce(Scope) -> View + 'static,
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static
) -> impl Stream<Item = String> {
// 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#"<script>
if(__LEPTOS_RESOURCE_RESOLVERS.get({id})) {{
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
}} else {{
__LEPTOS_RESOLVED_RESOURCES.set({id}, {json:?});
}}
</script>"#,
)
}),
// stream HTML for each <Suspense/> as it resolves
fragments.map(|(fragment_id, html)| {
format!(
r#"
<template id="{fragment_id}">{html}</template>
<script>
var frag = document.querySelector(`[data-fragment-id="{fragment_id}"]`);
var tpl = document.getElementById("{fragment_id}");
if(frag) frag.replaceWith(tpl.content.cloneNode(true));
</script>
"#
)
})
);
// HTML for the view function and script to store resources
futures::stream::once(async move {
format!(
r#"
{prefix}
{shell}
<script>
__LEPTOS_PENDING_RESOURCES = {pending_resources};
__LEPTOS_RESOLVED_RESOURCES = new Map();
__LEPTOS_RESOURCE_RESOLVERS = new Map();
</script>
"#
)
})
.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) => {