Skip to content

Commit

Permalink
feat: hot reloading support for cargo-leptos (#592)
Browse files Browse the repository at this point in the history
  • Loading branch information
gbj authored Mar 4, 2023
1 parent 1e0adcd commit 55ce805
Show file tree
Hide file tree
Showing 21 changed files with 1,449 additions and 88 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"leptos",
"leptos_dom",
"leptos_config",
"leptos_hot_reload",
"leptos_macro",
"leptos_reactive",
"leptos_server",
Expand All @@ -29,6 +30,7 @@ version = "0.2.0"
[workspace.dependencies]
leptos = { path = "./leptos", default-features = false, version = "0.2.0" }
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.2.0" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.2.0" }
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.2.0" }
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.2.0" }
leptos_server = { path = "./leptos_server", default-features = false, version = "0.2.0" }
Expand Down
44 changes: 25 additions & 19 deletions examples/tailwind/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,38 @@ use leptos_router::*;
pub fn App(cx: Scope) -> impl IntoView {
provide_meta_context(cx);

let (count, set_count) = create_signal(cx, 0);

view! {
cx,
<Stylesheet id="leptos" href="/pkg/tailwind.css"/>
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Router>
<Routes>
<Route path="" view= move |cx| view! {
cx,
<main class="my-0 mx-auto max-w-3xl text-center">
<h2 class="p-6 text-4xl">"Welcome to Leptos with Tailwind"</h2>
<p class="px-10 pb-10 text-left">"Tailwind will scan your Rust files for Tailwind class names and compile them into a CSS file."</p>
<button
class="bg-sky-600 hover:bg-sky-700 px-5 py-3 text-white rounded-lg"
on:click=move |_| set_count.update(|count| *count += 1)
>
{move || if count() == 0 {
"Click me!".to_string()
} else {
count().to_string()
}}
</button>
</main>
}/>
<Route path="" view= move |cx| view! { cx, <Home/> }/>
</Routes>
</Router>
}
}

#[component]
fn Home(cx: Scope) -> impl IntoView {
let (count, set_count) = create_signal(cx, 0);

view! { cx,
<main class="my-0 mx-auto max-w-3xl text-center">
<h2 class="p-6 text-4xl">"Welcome to Leptos with Tailwind"</h2>
<p class="px-10 pb-10 text-left">"Tailwind will scan your Rust files for Tailwind class names and compile them into a CSS file."</p>
<button
class="bg-amber-600 hover:bg-sky-700 px-5 py-3 text-white rounded-lg"
on:click=move |_| set_count.update(|count| *count += 1)
>
"Something's here | "
{move || if count() == 0 {
"Click me!".to_string()
} else {
count().to_string()
}}
" | Some more text"
</button>
</main>
}
}
1 change: 1 addition & 0 deletions integrations/utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ description = "Utilities to help build server integrations for the Leptos web fr
[dependencies]
futures = "0.3"
leptos = { workspace = true, features = ["ssr"] }
leptos_hot_reload = { workspace = true }
leptos_meta = { workspace = true, features = ["ssr"] }
leptos_router = { workspace = true, features = ["ssr"] }
leptos_config = { workspace = true }
7 changes: 6 additions & 1 deletion integrations/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub fn html_parts(
true => format!(
r#"
<script crossorigin="">(function () {{
{}
var ws = new WebSocket('ws://{site_ip}:{reload_port}/live_reload');
ws.onmessage = (ev) => {{
let msg = JSON.parse(ev.data);
Expand All @@ -40,11 +41,15 @@ pub fn html_parts(
}});
if (!found) console.warn(`CSS hot-reload: Could not find a <link href=/\"${{msg.css}}\"> element`);
}};
if(msg.view) {{
patch(msg.view);
}}
}};
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
}})()
</script>
"#
"#,
leptos_hot_reload::HOT_RELOAD_JS
),
false => "".to_string(),
};
Expand Down
52 changes: 34 additions & 18 deletions leptos/tests/ssr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ fn simple_ssr_test() {

assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<div id=\"_0-1\"><button id=\"_0-2\">-1</button><span \
"<!--leptos-view|leptos-tests-ssr.rs-8|open--><div \
id=\"_0-1\"><button id=\"_0-2\">-1</button><span \
id=\"_0-3\">Value: \
<!--hk=_0-4o|leptos-dyn-child-start-->0<!\
--hk=_0-4c|leptos-dyn-child-end-->!</span><button \
id=\"_0-5\">+1</button></div>"
id=\"_0-5\">+1</button></div><!--leptos-view|leptos-tests-ssr.\
rs-8|close-->"
);
});
}
Expand Down Expand Up @@ -54,21 +56,25 @@ fn ssr_test_with_components() {

assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<div id=\"_0-1\" \
class=\"counters\"><!--hk=_0-1-0o|leptos-counter-start--><div \
"<!--leptos-view|leptos-tests-ssr.rs-49|open--><div id=\"_0-1\" \
class=\"counters\"><!--hk=_0-1-0o|leptos-counter-start--><!\
--leptos-view|leptos-tests-ssr.rs-38|open--><div \
id=\"_0-1-1\"><button id=\"_0-1-2\">-1</button><span \
id=\"_0-1-3\">Value: \
<!--hk=_0-1-4o|leptos-dyn-child-start-->1<!\
--hk=_0-1-4c|leptos-dyn-child-end-->!</span><button \
id=\"_0-1-5\">+1</button></div><!\
--hk=_0-1-0c|leptos-counter-end--><!\
--hk=_0-1-5-0o|leptos-counter-start--><div \
id=\"_0-1-5\">+1</button></div><!--leptos-view|leptos-tests-ssr.\
rs-38|close--><!--hk=_0-1-0c|leptos-counter-end--><!\
--hk=_0-1-5-0o|leptos-counter-start--><!\
--leptos-view|leptos-tests-ssr.rs-38|open--><div \
id=\"_0-1-5-1\"><button id=\"_0-1-5-2\">-1</button><span \
id=\"_0-1-5-3\">Value: \
<!--hk=_0-1-5-4o|leptos-dyn-child-start-->2<!\
--hk=_0-1-5-4c|leptos-dyn-child-end-->!</span><button \
id=\"_0-1-5-5\">+1</button></div><!\
--hk=_0-1-5-0c|leptos-counter-end--></div>"
--leptos-view|leptos-tests-ssr.rs-38|close--><!\
--hk=_0-1-5-0c|leptos-counter-end--></div><!\
--leptos-view|leptos-tests-ssr.rs-49|close-->"
);
});
}
Expand Down Expand Up @@ -102,22 +108,26 @@ fn ssr_test_with_snake_case_components() {

assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<div id=\"_0-1\" \
"<!--leptos-view|leptos-tests-ssr.rs-101|open--><div id=\"_0-1\" \
class=\"counters\"><!\
--hk=_0-1-0o|leptos-snake-case-counter-start--><div \
--hk=_0-1-0o|leptos-snake-case-counter-start--><!\
--leptos-view|leptos-tests-ssr.rs-90|open--><div \
id=\"_0-1-1\"><button id=\"_0-1-2\">-1</button><span \
id=\"_0-1-3\">Value: \
<!--hk=_0-1-4o|leptos-dyn-child-start-->1<!\
--hk=_0-1-4c|leptos-dyn-child-end-->!</span><button \
id=\"_0-1-5\">+1</button></div><!\
--hk=_0-1-0c|leptos-snake-case-counter-end--><!\
--hk=_0-1-5-0o|leptos-snake-case-counter-start--><div \
id=\"_0-1-5\">+1</button></div><!--leptos-view|leptos-tests-ssr.\
rs-90|close--><!--hk=_0-1-0c|leptos-snake-case-counter-end--><!\
--hk=_0-1-5-0o|leptos-snake-case-counter-start--><!\
--leptos-view|leptos-tests-ssr.rs-90|open--><div \
id=\"_0-1-5-1\"><button id=\"_0-1-5-2\">-1</button><span \
id=\"_0-1-5-3\">Value: \
<!--hk=_0-1-5-4o|leptos-dyn-child-start-->2<!\
--hk=_0-1-5-4c|leptos-dyn-child-end-->!</span><button \
id=\"_0-1-5-5\">+1</button></div><!\
--hk=_0-1-5-0c|leptos-snake-case-counter-end--></div>"
--leptos-view|leptos-tests-ssr.rs-90|close--><!\
--hk=_0-1-5-0c|leptos-snake-case-counter-end--></div><!\
--leptos-view|leptos-tests-ssr.rs-101|close-->"
);
});
}
Expand All @@ -136,7 +146,9 @@ fn test_classes() {

assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<div id=\"_0-1\" class=\"my big red car\"></div>"
"<!--leptos-view|leptos-tests-ssr.rs-142|open--><div id=\"_0-1\" \
class=\"my big red \
car\"></div><!--leptos-view|leptos-tests-ssr.rs-142|close-->"
);
});
}
Expand All @@ -158,8 +170,10 @@ fn ssr_with_styles() {

assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<div id=\"_0-1\" class=\" myclass\"><button id=\"_0-2\" \
class=\"btn myclass\">-1</button></div>"
"<!--leptos-view|leptos-tests-ssr.rs-164|open--><div id=\"_0-1\" \
class=\" myclass\"><button id=\"_0-2\" class=\"btn \
myclass\">-1</button></div><!--leptos-view|leptos-tests-ssr.\
rs-164|close-->"
);
});
}
Expand All @@ -178,7 +192,9 @@ fn ssr_option() {

assert_eq!(
rendered.into_view(cx).render_to_string(cx),
"<option id=\"_0-1\"></option>"
"<!--leptos-view|leptos-tests-ssr.rs-188|open--><option \
id=\"_0-1\"></option><!--leptos-view|leptos-tests-ssr.\
rs-188|close-->"
);
});
}
48 changes: 41 additions & 7 deletions leptos_dom/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ where
element,
#[cfg(debug_assertions)]
span: ::tracing::Span::current(),
#[cfg(debug_assertions)]
view_marker: None,
}
}

Expand Down Expand Up @@ -259,6 +261,8 @@ cfg_if! {
pub(crate) span: ::tracing::Span,
pub(crate) cx: Scope,
pub(crate) element: El,
#[cfg(debug_assertions)]
pub(crate) view_marker: Option<String>
}
// Server needs to build a virtualized DOM tree
} else {
Expand All @@ -274,7 +278,9 @@ cfg_if! {
#[allow(clippy::type_complexity)]
pub(crate) children: SmallVec<[View; 4]>,
#[educe(Debug(ignore))]
pub(crate) prerendered: Option<Cow<'static, str>>
pub(crate) prerendered: Option<Cow<'static, str>>,
#[cfg(debug_assertions)]
pub(crate) view_marker: Option<String>
}
}
}
Expand Down Expand Up @@ -302,15 +308,19 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
cx,
element,
#[cfg(debug_assertions)]
span: ::tracing::Span::current()
span: ::tracing::Span::current(),
#[cfg(debug_assertions)]
view_marker: None
}
} else {
Self {
cx,
attrs: smallvec![],
children: smallvec![],
element,
prerendered: None
prerendered: None,
#[cfg(debug_assertions)]
view_marker: None
}
}
}
Expand All @@ -329,9 +339,18 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
children: smallvec![],
element,
prerendered: Some(html.into()),
#[cfg(debug_assertions)]
view_marker: None,
}
}

#[cfg(debug_assertions)]
/// Adds an optional marker indicating the view macro source.
pub fn with_view_marker(mut self, marker: impl Into<String>) -> Self {
self.view_marker = Some(marker.into());
self
}

/// Converts this element into [`HtmlElement<AnyElement>`].
pub fn into_any(self) -> HtmlElement<AnyElement> {
cfg_if! {
Expand All @@ -340,7 +359,9 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
cx,
element,
#[cfg(debug_assertions)]
span
span,
#[cfg(debug_assertions)]
view_marker
} = self;

HtmlElement {
Expand All @@ -351,15 +372,19 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
is_void: element.is_void(),
},
#[cfg(debug_assertions)]
span
span,
#[cfg(debug_assertions)]
view_marker
}
} else {
let Self {
cx,
attrs,
children,
element,
prerendered
prerendered,
#[cfg(debug_assertions)]
view_marker
} = self;

HtmlElement {
Expand All @@ -370,8 +395,10 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
element: AnyElement {
name: element.name(),
is_void: element.is_void(),
id: element.hydration_id().clone(),
id: element.hydration_id().clone()
},
#[cfg(debug_assertions)]
view_marker
}
}
}
Expand Down Expand Up @@ -742,6 +769,8 @@ impl<El: ElementDescriptor> IntoView for HtmlElement<El> {
mut attrs,
children,
prerendered,
#[cfg(debug_assertions)]
view_marker,
..
} = self;

Expand All @@ -760,6 +789,11 @@ impl<El: ElementDescriptor> IntoView for HtmlElement<El> {
element.children.extend(children);
element.prerendered = prerendered;

#[cfg(debug_assertions)]
{
element.view_marker = view_marker;
}

View::Element(element)
}
}
Expand Down
18 changes: 18 additions & 0 deletions leptos_dom/src/hydration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ cfg_if! {
map
});

#[cfg(debug_assertions)]
pub(crate) static VIEW_MARKERS: LazyCell<HashMap<String, web_sys::Comment>> = LazyCell::new(|| {
let document = crate::document();
let body = document.body().unwrap();
let walker = document
.create_tree_walker_with_what_to_show(&body, 128)
.unwrap();
let mut map = HashMap::new();
while let Ok(Some(node)) = walker.next_node() {
if let Some(content) = node.text_content() {
if let Some(id) = content.strip_prefix("leptos-view|") {
map.insert(id.into(), node.unchecked_into());
}
}
}
map
});

static IS_HYDRATING: RefCell<LazyCell<bool>> = RefCell::new(LazyCell::new(|| {
#[cfg(debug_assertions)]
return crate::document().get_element_by_id("_0-0-0").is_some()
Expand Down
Loading

0 comments on commit 55ce805

Please sign in to comment.