mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-12 23:57:09 +00:00
feat: restore hot reloading for 0.7 (#2775)
This commit is contained in:
parent
5657abc07d
commit
7b62ad44d2
13 changed files with 252 additions and 66 deletions
|
@ -72,6 +72,7 @@ pub fn ContactRoutes() -> impl MatchNestedRoutes<Dom> + Clone {
|
||||||
<Route path=path!("/:id") view=Contact/>
|
<Route path=path!("/:id") view=Contact/>
|
||||||
</ParentRoute>
|
</ParentRoute>
|
||||||
}
|
}
|
||||||
|
.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
|
@ -17,6 +17,7 @@ cfg-if = "1.0"
|
||||||
hydration_context = { workspace = true }
|
hydration_context = { workspace = true }
|
||||||
either_of = { workspace = true }
|
either_of = { workspace = true }
|
||||||
leptos_dom = { workspace = true }
|
leptos_dom = { workspace = true }
|
||||||
|
leptos_hot_reload = { workspace = true }
|
||||||
leptos_macro = { workspace = true }
|
leptos_macro = { workspace = true }
|
||||||
leptos_server = { workspace = true, features = ["tachys"] }
|
leptos_server = { workspace = true, features = ["tachys"] }
|
||||||
leptos_config = { workspace = true }
|
leptos_config = { workspace = true }
|
||||||
|
|
|
@ -168,7 +168,7 @@ mod tests {
|
||||||
fn creates_list() {
|
fn creates_list() {
|
||||||
Owner::new().with(|| {
|
Owner::new().with(|| {
|
||||||
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||||
let list: HtmlElement<_, _, _, Dom> = view! {
|
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||||
<ol>
|
<ol>
|
||||||
<For each=move || values.get() key=|i| *i let:i>
|
<For each=move || values.get() key=|i| *i let:i>
|
||||||
<li>{i}</li>
|
<li>{i}</li>
|
||||||
|
@ -187,7 +187,7 @@ mod tests {
|
||||||
fn creates_list_enumerate() {
|
fn creates_list_enumerate() {
|
||||||
Owner::new().with(|| {
|
Owner::new().with(|| {
|
||||||
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||||
let list: HtmlElement<_, _, _, Dom> = view! {
|
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||||
<ol>
|
<ol>
|
||||||
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
||||||
<li>{move || index.get()}"-"{i}</li>
|
<li>{move || index.get()}"-"{i}</li>
|
||||||
|
@ -200,7 +200,7 @@ mod tests {
|
||||||
<!>-<!>4</li><li>4<!>-<!>5</li><!></ol>"
|
<!>-<!>4</li><li>4<!>-<!>5</li><!></ol>"
|
||||||
);
|
);
|
||||||
|
|
||||||
let list: HtmlElement<_, _, _, Dom> = view! {
|
let list: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||||
<ol>
|
<ol>
|
||||||
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
<ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
|
||||||
<li>{move || index.get()}"-"{i}</li>
|
<li>{move || index.get()}"-"{i}</li>
|
||||||
|
|
|
@ -24,8 +24,13 @@ pub fn AutoReload(
|
||||||
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
|
leptos_config::ReloadWSProtocol::WSS => "'wss://'",
|
||||||
};
|
};
|
||||||
|
|
||||||
let script = include_str!("reload_script.js");
|
let script = format!(
|
||||||
view! { <script nonce=nonce>{format!("{script}({reload_port:?}, {protocol})")}</script> }
|
"(function (reload_port, protocol) {{ {} {} }})({reload_port:?}, \
|
||||||
|
{protocol})",
|
||||||
|
leptos_hot_reload::HOT_RELOAD_JS,
|
||||||
|
include_str!("reload_script.js")
|
||||||
|
);
|
||||||
|
view! { <script nonce=nonce>{script}</script> }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
(function (reload_port, protocol) {
|
|
||||||
let host = window.location.hostname;
|
let host = window.location.hostname;
|
||||||
let ws = new WebSocket(`${protocol}${host}:${reload_port}/live_reload`);
|
let ws = new WebSocket(`${protocol}${host}:${reload_port}/live_reload`);
|
||||||
ws.onmessage = (ev) => {
|
ws.onmessage = (ev) => {
|
||||||
|
@ -20,4 +19,3 @@ ws.onmessage = (ev) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
|
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
|
||||||
})
|
|
||||||
|
|
|
@ -1,18 +1,50 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
use tachys::{
|
use tachys::{
|
||||||
html::attribute::Attribute,
|
html::attribute::Attribute,
|
||||||
hydration::Cursor,
|
hydration::Cursor,
|
||||||
renderer::dom::Dom,
|
renderer::{dom::Dom, Renderer},
|
||||||
ssr::StreamBuilder,
|
ssr::StreamBuilder,
|
||||||
view::{add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml},
|
view::{
|
||||||
|
add_attr::AddAnyAttr, Position, PositionState, Render, RenderHtml,
|
||||||
|
ToTemplate,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct View<T>(T)
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct View<T>
|
||||||
where
|
where
|
||||||
T: Sized;
|
T: Sized,
|
||||||
|
{
|
||||||
|
inner: T,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
view_marker: Option<Cow<'static, str>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> View<T> {
|
impl<T> View<T> {
|
||||||
|
pub fn new(inner: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
view_marker: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn with_view_marker(
|
||||||
|
#[allow(unused_mut)] // used in debug
|
||||||
|
mut self,
|
||||||
|
#[allow(unused_variables)] // used in debug
|
||||||
|
view_marker: impl Into<Cow<'static, str>>,
|
||||||
|
) -> Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
self.view_marker = Some(view_marker.into());
|
||||||
|
}
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,33 +60,37 @@ where
|
||||||
T: Sized + Render<Dom> + RenderHtml<Dom> + Send, //+ AddAnyAttr<Dom>,
|
T: Sized + Render<Dom> + RenderHtml<Dom> + Send, //+ AddAnyAttr<Dom>,
|
||||||
{
|
{
|
||||||
fn into_view(self) -> View<Self> {
|
fn into_view(self) -> View<Self> {
|
||||||
View(self)
|
View {
|
||||||
|
inner: self,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
view_marker: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IntoView> Render<Dom> for View<T> {
|
impl<T: Render<Rndr>, Rndr: Renderer> Render<Rndr> for View<T> {
|
||||||
type State = T::State;
|
type State = T::State;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
self.0.build()
|
self.inner.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild(self, state: &mut Self::State) {
|
fn rebuild(self, state: &mut Self::State) {
|
||||||
self.0.rebuild(state)
|
self.inner.rebuild(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
impl<T: RenderHtml<Rndr>, Rndr: Renderer> RenderHtml<Rndr> for View<T> {
|
||||||
type AsyncOutput = T::AsyncOutput;
|
type AsyncOutput = T::AsyncOutput;
|
||||||
|
|
||||||
const MIN_LENGTH: usize = <T as RenderHtml<Dom>>::MIN_LENGTH;
|
const MIN_LENGTH: usize = <T as RenderHtml<Rndr>>::MIN_LENGTH;
|
||||||
|
|
||||||
async fn resolve(self) -> Self::AsyncOutput {
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
self.0.resolve().await
|
self.inner.resolve().await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dry_resolve(&mut self) {
|
fn dry_resolve(&mut self) {
|
||||||
self.0.dry_resolve();
|
self.inner.dry_resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_html_with_buf(
|
fn to_html_with_buf(
|
||||||
|
@ -64,8 +100,20 @@ impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
||||||
escape: bool,
|
escape: bool,
|
||||||
mark_branches: bool,
|
mark_branches: bool,
|
||||||
) {
|
) {
|
||||||
self.0
|
#[cfg(debug_assertions)]
|
||||||
|
let vm = self.view_marker.to_owned();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if let Some(vm) = vm.as_ref() {
|
||||||
|
buf.push_str(&format!("<!--hot-reload|{vm}|open-->"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner
|
||||||
.to_html_with_buf(buf, position, escape, mark_branches);
|
.to_html_with_buf(buf, position, escape, mark_branches);
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if let Some(vm) = vm.as_ref() {
|
||||||
|
buf.push_str(&format!("<!--hot-reload|{vm}|close-->"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||||
|
@ -77,35 +125,70 @@ impl<T: IntoView> RenderHtml<Dom> for View<T> {
|
||||||
) where
|
) where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.0.to_html_async_with_buf::<OUT_OF_ORDER>(
|
#[cfg(debug_assertions)]
|
||||||
|
let vm = self.view_marker.to_owned();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if let Some(vm) = vm.as_ref() {
|
||||||
|
buf.push_sync(&format!("<!--hot-reload|{vm}|open-->"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||||
buf,
|
buf,
|
||||||
position,
|
position,
|
||||||
escape,
|
escape,
|
||||||
mark_branches,
|
mark_branches,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if let Some(vm) = vm.as_ref() {
|
||||||
|
buf.push_sync(&format!("<!--hot-reload|{vm}|close-->"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hydrate<const FROM_SERVER: bool>(
|
fn hydrate<const FROM_SERVER: bool>(
|
||||||
self,
|
self,
|
||||||
cursor: &Cursor<Dom>,
|
cursor: &Cursor<Rndr>,
|
||||||
position: &PositionState,
|
position: &PositionState,
|
||||||
) -> Self::State {
|
) -> Self::State {
|
||||||
self.0.hydrate::<FROM_SERVER>(cursor, position)
|
self.inner.hydrate::<FROM_SERVER>(cursor, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IntoView> AddAnyAttr<Dom> for View<T> {
|
impl<T: ToTemplate> ToTemplate for View<T> {
|
||||||
type Output<SomeNewAttr: Attribute<Dom>> =
|
fn to_template(
|
||||||
<T as AddAnyAttr<Dom>>::Output<SomeNewAttr>;
|
buf: &mut String,
|
||||||
|
class: &mut String,
|
||||||
|
style: &mut String,
|
||||||
|
inner_html: &mut String,
|
||||||
|
position: &mut Position,
|
||||||
|
) {
|
||||||
|
T::to_template(buf, class, style, inner_html, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn add_any_attr<NewAttr: Attribute<Dom>>(
|
impl<T: AddAnyAttr<Rndr>, Rndr> AddAnyAttr<Rndr> for View<T>
|
||||||
|
where
|
||||||
|
Rndr: Renderer,
|
||||||
|
{
|
||||||
|
type Output<SomeNewAttr: Attribute<Rndr>> = View<T::Output<SomeNewAttr>>;
|
||||||
|
|
||||||
|
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||||
self,
|
self,
|
||||||
attr: NewAttr,
|
attr: NewAttr,
|
||||||
) -> Self::Output<NewAttr>
|
) -> Self::Output<NewAttr>
|
||||||
where
|
where
|
||||||
Self::Output<NewAttr>: RenderHtml<Dom>,
|
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||||
{
|
{
|
||||||
self.0.add_any_attr(attr)
|
let View {
|
||||||
|
inner,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
view_marker,
|
||||||
|
} = self;
|
||||||
|
View {
|
||||||
|
inner: inner.add_any_attr(attr),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
view_marker,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::{logging, IntoView};
|
#[cfg(debug_assertions)]
|
||||||
|
use crate::logging;
|
||||||
|
use crate::IntoView;
|
||||||
use any_spawner::Executor;
|
use any_spawner::Executor;
|
||||||
use reactive_graph::owner::Owner;
|
use reactive_graph::owner::Owner;
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
|
|
@ -5,7 +5,7 @@ fn simple_ssr_test() {
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
let (value, set_value) = signal(0);
|
let (value, set_value) = signal(0);
|
||||||
let rendered: HtmlElement<_, _, _, Dom> = view! {
|
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||||
<div>
|
<div>
|
||||||
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>
|
||||||
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
<span>"Value: " {move || value.get().to_string()} "!"</span>
|
||||||
|
@ -36,7 +36,7 @@ fn ssr_test_with_components() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rendered: HtmlElement<_, _, _, Dom> = view! {
|
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||||
<div class="counters">
|
<div class="counters">
|
||||||
<Counter initial_value=1/>
|
<Counter initial_value=1/>
|
||||||
<Counter initial_value=2/>
|
<Counter initial_value=2/>
|
||||||
|
@ -66,7 +66,7 @@ fn ssr_test_with_snake_case_components() {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rendered: HtmlElement<_, _, _, Dom> = view! {
|
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||||
<div class="counters">
|
<div class="counters">
|
||||||
<SnakeCaseCounter initial_value=1/>
|
<SnakeCaseCounter initial_value=1/>
|
||||||
<SnakeCaseCounter initial_value=2/>
|
<SnakeCaseCounter initial_value=2/>
|
||||||
|
@ -86,7 +86,7 @@ fn test_classes() {
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
let (value, _set_value) = signal(5);
|
let (value, _set_value) = signal(5);
|
||||||
let rendered: HtmlElement<_, _, _, Dom> = view! {
|
let rendered: View<HtmlElement<_, _, _, Dom>> = view! {
|
||||||
<div
|
<div
|
||||||
class="my big"
|
class="my big"
|
||||||
class:a=move || { value.get() > 10 }
|
class:a=move || { value.get() > 10 }
|
||||||
|
@ -104,7 +104,7 @@ fn ssr_with_styles() {
|
||||||
|
|
||||||
let (_, set_value) = signal(0);
|
let (_, set_value) = signal(0);
|
||||||
let styles = "myclass";
|
let styles = "myclass";
|
||||||
let rendered: HtmlElement<_, _, _, Dom> = view! { class=styles,
|
let rendered: View<HtmlElement<_, _, _, Dom>> = view! { class=styles,
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click=move |_| set_value.update(|value| *value -= 1)>
|
<button class="btn" on:click=move |_| set_value.update(|value| *value -= 1)>
|
||||||
"-1"
|
"-1"
|
||||||
|
@ -124,7 +124,7 @@ fn ssr_option() {
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
let (_, _) = signal(0);
|
let (_, _) = signal(0);
|
||||||
let rendered: HtmlElement<_, _, _, Dom> = view! { <option></option> };
|
let rendered: View<HtmlElement<_, _, _, Dom>> = view! { <option></option> };
|
||||||
|
|
||||||
assert_eq!(rendered.to_html(), "<option></option>");
|
assert_eq!(rendered.to_html(), "<option></option>");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
console.log("[HOT RELOADING] Connected to server.");
|
console.log("[HOT RELOADING] Connected to server.\n\nNote: `cargo-leptos watch --hot-reload` only works with the `nightly` feature enabled on Leptos.");
|
||||||
function patch(json) {
|
function patch(json) {
|
||||||
try {
|
try {
|
||||||
const views = JSON.parse(json);
|
const views = JSON.parse(json);
|
||||||
for (const [id, patches] of views) {
|
for (const [id, patches] of views) {
|
||||||
console.log("[HOT RELOAD]", id, patches);
|
console.log("[HOT RELOAD]", id, patches);
|
||||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT),
|
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT),
|
||||||
open = `leptos-view|${id}|open`,
|
open = `hot-reload|${id}|open`,
|
||||||
close = `leptos-view|${id}|close`;
|
close = `hot-reload|${id}|close`;
|
||||||
let start, end;
|
let start, end;
|
||||||
const instances = [];
|
const instances = [];
|
||||||
while (walker.nextNode()) {
|
while (walker.nextNode()) {
|
||||||
|
@ -259,11 +259,11 @@ function patch(json) {
|
||||||
node: walker.currentNode,
|
node: walker.currentNode,
|
||||||
});
|
});
|
||||||
} else if (walker.currentNode.nodeType == Node.COMMENT_NODE) {
|
} else if (walker.currentNode.nodeType == Node.COMMENT_NODE) {
|
||||||
if (walker.currentNode.textContent.trim().startsWith("leptos-view")) {
|
if (walker.currentNode.textContent.trim().startsWith("hot-reload")) {
|
||||||
if (walker.currentNode.textContent.trim().endsWith("-children|open")) {
|
if (walker.currentNode.textContent.trim().endsWith("-children|open")) {
|
||||||
const startingName = walker.currentNode.textContent.trim();
|
const startingName = walker.currentNode.textContent.trim();
|
||||||
const componentName = startingName.replace("-children|open").replace("leptos-view|");
|
const componentName = startingName.replace("-children|open").replace("hot-reload|");
|
||||||
const endingName = `leptos-view|${componentName}-children|close`;
|
const endingName = `hot-reload|${componentName}-children|close`;
|
||||||
let start = walker.currentNode;
|
let start = walker.currentNode;
|
||||||
let depth = 1;
|
let depth = 1;
|
||||||
|
|
||||||
|
|
|
@ -302,7 +302,11 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||||
let parser = rstml::Parser::new(config);
|
let parser = rstml::Parser::new(config);
|
||||||
let (nodes, errors) = parser.parse_recoverable(tokens).split_vec();
|
let (nodes, errors) = parser.parse_recoverable(tokens).split_vec();
|
||||||
let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());
|
let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());
|
||||||
let nodes_output = view::render_view(&nodes, global_class.as_ref(), None);
|
let nodes_output = view::render_view(
|
||||||
|
&nodes,
|
||||||
|
global_class.as_ref(),
|
||||||
|
normalized_call_site(proc_macro::Span::call_site()),
|
||||||
|
);
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
#(#errors;)*
|
#(#errors;)*
|
||||||
|
@ -312,6 +316,20 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalized_call_site(site: proc_macro::Span) -> Option<String> {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(all(debug_assertions, feature = "nightly"))] {
|
||||||
|
Some(leptos_hot_reload::span_to_stable_id(
|
||||||
|
site.source_file().path(),
|
||||||
|
site.start().line()
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
_ = site;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Annotates a function so that it can be used with your template as a Leptos `<Component/>`.
|
/// Annotates a function so that it can be used with your template as a Leptos `<Component/>`.
|
||||||
///
|
///
|
||||||
/// The `#[component]` macro allows you to annotate plain Rust functions as components
|
/// The `#[component]` macro allows you to annotate plain Rust functions as components
|
||||||
|
|
|
@ -30,28 +30,60 @@ pub fn render_view(
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
view_marker: Option<String>,
|
view_marker: Option<String>,
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
match nodes.len() {
|
let (base, should_add_view) = match nodes.len() {
|
||||||
0 => {
|
0 => {
|
||||||
let span = Span::call_site();
|
let span = Span::call_site();
|
||||||
Some(quote_spanned! {
|
(
|
||||||
span => ()
|
Some(quote_spanned! {
|
||||||
})
|
span => ()
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
1 => node_to_tokens(
|
1 => (
|
||||||
&nodes[0],
|
node_to_tokens(
|
||||||
TagType::Unknown,
|
&nodes[0],
|
||||||
None,
|
TagType::Unknown,
|
||||||
global_class,
|
None,
|
||||||
view_marker.as_deref(),
|
global_class,
|
||||||
|
view_marker.as_deref(),
|
||||||
|
),
|
||||||
|
// only add View wrapper and view marker to a regular HTML
|
||||||
|
// element or component, not to a <{..} /> attribute list
|
||||||
|
match &nodes[0] {
|
||||||
|
Node::Element(node) => !is_spread_marker(node),
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
_ => fragment_to_tokens(
|
_ => (
|
||||||
nodes,
|
fragment_to_tokens(
|
||||||
TagType::Unknown,
|
nodes,
|
||||||
None,
|
TagType::Unknown,
|
||||||
global_class,
|
None,
|
||||||
view_marker.as_deref(),
|
global_class,
|
||||||
|
view_marker.as_deref(),
|
||||||
|
),
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
}
|
};
|
||||||
|
base.map(|view| {
|
||||||
|
if !should_add_view {
|
||||||
|
view
|
||||||
|
} else if let Some(vm) = view_marker {
|
||||||
|
quote! {
|
||||||
|
::leptos::prelude::View::new(
|
||||||
|
#view
|
||||||
|
)
|
||||||
|
.with_view_marker(#vm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
::leptos::prelude::View::new(
|
||||||
|
#view
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_children_to_tokens(
|
fn element_children_to_tokens(
|
||||||
|
|
|
@ -345,7 +345,9 @@ where
|
||||||
Unsuspend::new(move || match condition {
|
Unsuspend::new(move || match condition {
|
||||||
Some(true) => Either::Left(view()),
|
Some(true) => Either::Left(view()),
|
||||||
#[allow(clippy::unit_arg)]
|
#[allow(clippy::unit_arg)]
|
||||||
Some(false) => Either::Right(view! { <Redirect path=redirect_path()/> }),
|
Some(false) => {
|
||||||
|
Either::Right(view! { <Redirect path=redirect_path()/> }.into_inner())
|
||||||
|
}
|
||||||
None => Either::Right(()),
|
None => Either::Right(()),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
@ -390,7 +392,9 @@ where
|
||||||
match condition() {
|
match condition() {
|
||||||
Some(true) => Either::Left(view()),
|
Some(true) => Either::Left(view()),
|
||||||
#[allow(clippy::unit_arg)]
|
#[allow(clippy::unit_arg)]
|
||||||
Some(false) => Either::Right(view! { <Redirect path=redirect_path()/> }),
|
Some(false) => {
|
||||||
|
Either::Right(view! { <Redirect path=redirect_path()/> }.into_inner())
|
||||||
|
}
|
||||||
None => Either::Right(()),
|
None => Either::Right(()),
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -92,11 +92,53 @@ impl Renderer for Dom {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_child(node: &Self::Node) -> Option<Self::Node> {
|
fn first_child(node: &Self::Node) -> Option<Self::Node> {
|
||||||
node.first_child()
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
let node = node.first_child();
|
||||||
|
// if it's a comment node that starts with hot-reload, it's a marker that should be
|
||||||
|
// ignored
|
||||||
|
if let Some(node) = node.as_ref() {
|
||||||
|
if node.node_type() == 8
|
||||||
|
&& node
|
||||||
|
.text_content()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.starts_with("hot-reload")
|
||||||
|
{
|
||||||
|
return Self::next_sibling(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
node.first_child()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_sibling(node: &Self::Node) -> Option<Self::Node> {
|
fn next_sibling(node: &Self::Node) -> Option<Self::Node> {
|
||||||
node.next_sibling()
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
let node = node.next_sibling();
|
||||||
|
// if it's a comment node that starts with hot-reload, it's a marker that should be
|
||||||
|
// ignored
|
||||||
|
if let Some(node) = node.as_ref() {
|
||||||
|
if node.node_type() == 8
|
||||||
|
&& node
|
||||||
|
.text_content()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.starts_with("hot-reload")
|
||||||
|
{
|
||||||
|
return Self::next_sibling(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
node.next_sibling()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_node(node: &Self::Node) {
|
fn log_node(node: &Self::Node) {
|
||||||
|
|
Loading…
Reference in a new issue