Update other packages to handle new thread-local reactives

This commit is contained in:
Greg Johnston 2022-11-21 21:46:07 -05:00
parent 739e7db49d
commit 6c521226e3
6 changed files with 62 additions and 53 deletions

View File

@ -50,8 +50,8 @@ pub fn handle_server_fns() -> Route {
if let Some(server_fn) = server_fn_by_path(path.as_str()) {
let body: &[u8] = &body;
// TODO this leaks a runtime once per invocation
let (cx, disposer) = raw_scope_and_disposer();
let runtime = create_runtime();
let (cx, disposer) = raw_scope_and_disposer(runtime);
// provide HttpRequest as context in server scope
provide_context(cx, req.clone());
@ -60,6 +60,7 @@ pub fn handle_server_fns() -> Route {
Ok(serialized) => {
// clean up the scope, which we only needed to run the server fn
disposer.dispose();
runtime.dispose();
// if this is Accept: application/json then send a serialized JSON response
if let Some("application/json") = accept_header {

View File

@ -55,8 +55,8 @@ pub async fn handle_server_fns(
let body: &[u8] = &body;
let res = if let Some(server_fn) = server_fn_by_path(path.as_str()) {
// TODO this leaks a runtime once per invocation
let (cx, disposer) = raw_scope_and_disposer();
let runtime = create_runtime();
let (cx, disposer) = raw_scope_and_disposer(runtime);
// provide request as context in server scope
provide_context(cx, Arc::new(req));
@ -65,6 +65,7 @@ pub async fn handle_server_fns(
Ok(serialized) => {
// clean up the scope, which we only needed to run the server fn
disposer.dispose();
runtime.dispose();
// if this is Accept: application/json then send a serialized JSON response
let accept_header =

View File

@ -3,9 +3,9 @@
fn simple_ssr_test() {
use leptos_dom::*;
use leptos_macro::view;
use leptos_reactive::{create_scope, create_signal};
use leptos_reactive::{create_runtime, create_scope, create_signal};
_ = create_scope(|cx| {
_ = create_scope(create_runtime(), |cx| {
let (value, set_value) = create_signal(cx, 0);
let rendered = view! {
cx,
@ -45,7 +45,7 @@ fn ssr_test_with_components() {
}
}
_ = create_scope(|cx| {
_ = create_scope(create_runtime(), |cx| {
let rendered = view! {
cx,
<div class="counters">
@ -66,9 +66,9 @@ fn ssr_test_with_components() {
fn test_classes() {
use leptos_dom::*;
use leptos_macro::view;
use leptos_reactive::{create_scope, create_signal};
use leptos_reactive::{create_runtime, create_scope, create_signal};
_ = create_scope(|cx| {
_ = create_scope(create_runtime(), |cx| {
let (value, set_value) = create_signal(cx, 5);
let rendered = view! {
cx,

View File

@ -71,11 +71,11 @@ where
F: Fn(Scope) -> T + 'static,
T: Mountable,
{
use leptos_reactive::create_scope;
use leptos_reactive::{create_runtime, create_scope};
// running "mount" intentionally leaks the memory,
// as the "mount" has no parent that can clean it up
let _ = create_scope(move |cx| {
// this is not a leak
// CSR and hydrate mode define a single, thread-local Runtime
let _ = create_scope(create_runtime(), move |cx| {
(f(cx)).mount(&parent);
});
}
@ -100,9 +100,11 @@ where
F: Fn(Scope) -> T + 'static,
T: Mountable,
{
// running "hydrate" intentionally leaks the memory,
// as the "hydrate" has no parent that can clean it up
let _ = leptos_reactive::create_scope(move |cx| {
use leptos_reactive::create_runtime;
// this is not a leak
// CSR and hydrate mode define a single, thread-local Runtime
let _ = leptos_reactive::create_scope(create_runtime(), move |cx| {
cx.start_hydration(&parent);
(f(cx));
cx.end_hydration();

View File

@ -31,8 +31,11 @@ cfg_if! {
/// 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 Fn(Scope) -> Element + 'static) -> impl Stream<Item = String> {
// create the runtime
let runtime = create_runtime();
let ((shell, pending_resources, pending_fragments, serializers), _, disposer) =
run_scope_undisposed({
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
@ -55,6 +58,39 @@ cfg_if! {
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})) {{
console.log("(create_resource) calling resolver");
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
}} else {{
console.log("(create_resource) saving data for resource creation");
__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}");
console.log("replace", frag, "with", tpl.content.cloneNode(true));
frag.replaceWith(tpl.content.cloneNode(true));
</script>
"#
)
})
);
// HTML for the view function and script to store resources
futures::stream::once(async move {
format!(
@ -68,42 +104,11 @@ cfg_if! {
"#
)
})
// TODO this is wrong: it should merge the next two streams, not chain them
// you may well need to resolve some fragments before some of the resources are resolved
// stream data for each Resource as it resolves
.chain(serializers.map(|(id, json)| {
let id = serde_json::to_string(&id).unwrap();
format!(
r#"<script>
if(__LEPTOS_RESOURCE_RESOLVERS.get({id})) {{
console.log("(create_resource) calling resolver");
__LEPTOS_RESOURCE_RESOLVERS.get({id})({json:?})
}} else {{
console.log("(create_resource) saving data for resource creation");
__LEPTOS_RESOLVED_RESOURCES.set({id}, {json:?});
}}
</script>"#,
)
}))
// stream HTML for each <Suspense/> as it resolves
.chain(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}");
console.log("replace", frag, "with", tpl.content.cloneNode(true));
frag.replaceWith(tpl.content.cloneNode(true));
</script>
"#
)
}))
// dispose of Scope
.chain(futures::stream::once(async {
.chain(resources_and_fragments)
// dispose of Scope and Runtime
.chain(futures::stream::once(async move {
disposer.dispose();
runtime.dispose();
Default::default()
}))
}

View File

@ -22,7 +22,7 @@ pub(crate) type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
thread_local! {
pub(crate) static RUNTIME: Runtime = Default::default();
pub(crate) static RUNTIME: Runtime = Runtime::new();
}
} else {
thread_local! {