Skip to content

Commit

Permalink
Internally erase html elements (#3614)
Browse files Browse the repository at this point in the history
  • Loading branch information
zakstucke authored Feb 15, 2025
1 parent 6ad300c commit d37450d
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 25 deletions.
4 changes: 1 addition & 3 deletions leptos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,7 @@ pub mod prelude {
pub use server_fn::{self, error::ServerFnError};
pub use tachys::{
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
view::{
any_view::AnyView, fragment::Fragment, template::ViewTemplate,
},
view::{fragment::Fragment, template::ViewTemplate},
};
}
pub use export_types::*;
Expand Down
98 changes: 81 additions & 17 deletions tachys/src/html/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::hydration::set_currently_hydrating;
use crate::{
html::attribute::Attribute,
hydration::{failed_to_cast_element, Cursor},
prelude::IntoAttribute,
prelude::*,
renderer::{CastFrom, Rndr},
ssr::StreamBuilder,
view::{
Expand All @@ -15,7 +15,6 @@ use const_str_slice_concat::{
const_concat, const_concat_with_prefix, str_from_buffer,
};
use futures::future::join;
use next_tuple::NextTuple;
use std::ops::Deref;

mod custom;
Expand Down Expand Up @@ -71,36 +70,101 @@ where
}
}*/

#[cfg(not(erase_components))]
impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
where
E: ElementWithChildren,
Ch: Render + NextTuple,
<Ch as NextTuple>::Output<NewChild::Output>: Render,
Ch: RenderHtml + next_tuple::NextTuple,
<Ch as next_tuple::NextTuple>::Output<NewChild::Output>: Render,

NewChild: IntoRender,
NewChild::Output: Render,
NewChild::Output: RenderHtml,
{
type Output =
HtmlElement<E, At, <Ch as NextTuple>::Output<NewChild::Output>>;
type Output = HtmlElement<
E,
At,
<Ch as next_tuple::NextTuple>::Output<NewChild::Output>,
>;

fn child(self, child: NewChild) -> Self::Output {
let HtmlElement {
HtmlElement {
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at,
tag,
attributes,
children,
} = self;
defined_at: self.defined_at,
tag: self.tag,
attributes: self.attributes,
children: self.children.next_tuple(child.into_render()),
}
}
}

#[cfg(erase_components)]
impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
where
E: ElementWithChildren,
Ch: RenderHtml + NextChildren,

NewChild: IntoRender,
NewChild::Output: RenderHtml,
{
type Output =
HtmlElement<E, At, crate::view::iterators::StaticVec<AnyView>>;

fn child(self, child: NewChild) -> Self::Output {
use crate::view::any_view::IntoAny;

HtmlElement {
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at,
tag,
attributes,
children: children.next_tuple(child.into_render()),
defined_at: self.defined_at,
tag: self.tag,
attributes: self.attributes,
children: self
.children
.next_children(child.into_render().into_any()),
}
}
}

#[cfg(erase_components)]
trait NextChildren {
fn next_children(
self,
child: AnyView,
) -> crate::view::iterators::StaticVec<AnyView>;
}

#[cfg(erase_components)]
impl NextChildren for () {
fn next_children(
self,
child: AnyView,
) -> crate::view::iterators::StaticVec<AnyView> {
vec![child].into()
}
}

#[cfg(erase_components)]
impl<T: RenderHtml> NextChildren for (T,) {
fn next_children(
self,
child: AnyView,
) -> crate::view::iterators::StaticVec<AnyView> {
use crate::view::any_view::IntoAny;

vec![self.0.into_owned().into_any(), child].into()
}
}

#[cfg(erase_components)]
impl NextChildren for crate::view::iterators::StaticVec<AnyView> {
fn next_children(
mut self,
child: AnyView,
) -> crate::view::iterators::StaticVec<AnyView> {
self.0.push(child);
self
}
}

impl<E, At, Ch> AddAnyAttr for HtmlElement<E, At, Ch>
where
E: ElementType + Send,
Expand Down
5 changes: 3 additions & 2 deletions tachys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ pub mod prelude {
},
renderer::{dom::Dom, Renderer},
view::{
add_attr::AddAnyAttr, any_view::IntoAny, IntoRender, Mountable,
Render, RenderHtml,
add_attr::AddAnyAttr,
any_view::{AnyView, IntoAny},
IntoRender, Mountable, Render, RenderHtml,
},
};
}
Expand Down
1 change: 0 additions & 1 deletion tachys/src/view/any_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ impl<T> IntoAny for T
where
T: Send,
T: RenderHtml,
T::State: 'static,
{
fn into_any(self) -> AnyView {
#[cfg(feature = "ssr")]
Expand Down
200 changes: 198 additions & 2 deletions tachys/src/view/iterators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,9 @@ where
extra_attrs.clone(),
);
}
buf.push_str("<!>");
if escape {
buf.push_str("<!>");
}
}

fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
Expand Down Expand Up @@ -359,7 +361,9 @@ where
extra_attrs.clone(),
);
}
buf.push_sync("<!>");
if escape {
buf.push_sync("<!>");
}
}

fn hydrate<const FROM_SERVER: bool>(
Expand All @@ -384,6 +388,198 @@ where
}
}

/// A container used for ErasedMode. It's slightly better than a raw Vec<> because the rendering traits don't have to worry about the length of the Vec changing, therefore no marker traits etc.
pub struct StaticVec<T>(pub(crate) Vec<T>);

impl<T> From<Vec<T>> for StaticVec<T> {
fn from(vec: Vec<T>) -> Self {
Self(vec)
}
}

impl<T> From<StaticVec<T>> for Vec<T> {
fn from(static_vec: StaticVec<T>) -> Self {
static_vec.0
}
}

/// Retained view state for a `StaticVec<Vec<_>>`.
pub struct StaticVecState<T>
where
T: Mountable,
{
states: Vec<T>,
}

impl<T> Mountable for StaticVecState<T>
where
T: Mountable,
{
fn unmount(&mut self) {
self.states.iter_mut().for_each(Mountable::unmount);
}

fn mount(
&mut self,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
) {
for state in self.states.iter_mut() {
state.mount(parent, marker);
}
}

fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
if let Some(first) = self.states.first() {
first.insert_before_this(child)
} else {
false
}
}

fn elements(&self) -> Vec<crate::renderer::types::Element> {
self.states
.iter()
.flat_map(|item| item.elements())
.collect()
}
}

impl<T> Render for StaticVec<T>
where
T: Render,
{
type State = StaticVecState<T::State>;

fn build(self) -> Self::State {
Self::State {
states: self.0.into_iter().map(T::build).collect(),
}
}

fn rebuild(self, state: &mut Self::State) {
let Self::State { states } = state;
let old = states;
// this is an unkeyed diff
self.0
.into_iter()
.zip(old.iter_mut())
.for_each(|(new, old)| T::rebuild(new, old));
}
}

impl<T> AddAnyAttr for StaticVec<T>
where
T: AddAnyAttr,
{
type Output<SomeNewAttr: Attribute> =
StaticVec<<T as AddAnyAttr>::Output<SomeNewAttr::Cloneable>>;

fn add_any_attr<NewAttr: Attribute>(
self,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml,
{
let attr = attr.into_cloneable();
self.0
.into_iter()
.map(|n| n.add_any_attr(attr.clone()))
.collect::<Vec<_>>()
.into()
}
}

impl<T> RenderHtml for StaticVec<T>
where
T: RenderHtml,
{
type AsyncOutput = StaticVec<T::AsyncOutput>;
type Owned = StaticVec<T::Owned>;

const MIN_LENGTH: usize = 0;

fn dry_resolve(&mut self) {
for inner in self.0.iter_mut() {
inner.dry_resolve();
}
}

async fn resolve(self) -> Self::AsyncOutput {
futures::future::join_all(self.0.into_iter().map(T::resolve))
.await
.into_iter()
.collect::<Vec<_>>()
.into()
}

fn html_len(&self) -> usize {
self.0.iter().map(RenderHtml::html_len).sum::<usize>()
}

fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
for child in self.0.into_iter() {
child.to_html_with_buf(
buf,
position,
escape,
mark_branches,
extra_attrs.clone(),
);
}
}

fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) where
Self: Sized,
{
for child in self.0.into_iter() {
child.to_html_async_with_buf::<OUT_OF_ORDER>(
buf,
position,
escape,
mark_branches,
extra_attrs.clone(),
);
}
}

fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let states = self
.0
.into_iter()
.map(|child| child.hydrate::<FROM_SERVER>(cursor, position))
.collect();
Self::State { states }
}

fn into_owned(self) -> Self::Owned {
self.0
.into_iter()
.map(RenderHtml::into_owned)
.collect::<Vec<_>>()
.into()
}
}

impl<T, const N: usize> Render for [T; N]
where
T: Render,
Expand Down

0 comments on commit d37450d

Please sign in to comment.