mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
perf: optimize inert HTML elements (#2989)
This commit is contained in:
parent
ba9604101d
commit
5af7b54c9c
10 changed files with 473 additions and 26 deletions
|
@ -162,22 +162,24 @@ pub fn App() -> impl IntoView {
|
||||||
<table class="table table-hover table-striped test-data">
|
<table class="table table-hover table-striped test-data">
|
||||||
<tbody>
|
<tbody>
|
||||||
<For
|
<For
|
||||||
each={move || data.get()}
|
each=move || data.get()
|
||||||
key={|row| row.id}
|
key=|row| row.id
|
||||||
children=move |row: RowData| {
|
children=move |row: RowData| {
|
||||||
let row_id = row.id;
|
let row_id = row.id;
|
||||||
let label = row.label;
|
let label = row.label;
|
||||||
let is_selected = is_selected.clone();
|
let is_selected = is_selected.clone();
|
||||||
ViewTemplate::new(view! {
|
template! {
|
||||||
<tr class:danger={move || is_selected.selected(Some(row_id))}>
|
< tr class : danger = { move || is_selected.selected(Some(row_id)) }
|
||||||
<td class="col-md-1">{row_id.to_string()}</td>
|
> < td class = "col-md-1" > { row_id.to_string() } </ td > < td
|
||||||
<td class="col-md-4"><a on:click=move |_| set_selected.set(Some(row_id))>{move || label.get()}</a></td>
|
class = "col-md-4" >< a on : click = move | _ | set_selected
|
||||||
<td class="col-md-1"><a on:click=move |_| remove(row_id)><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a></td>
|
.set(Some(row_id)) > { move || label.get() } </ a ></ td > < td
|
||||||
<td class="col-md-6"/>
|
class = "col-md-1" >< a on : click = move | _ | remove(row_id) ><
|
||||||
</tr>
|
span class = "glyphicon glyphicon-remove" aria - hidden = "true" ></
|
||||||
})
|
span ></ a ></ td > < td class = "col-md-6" /> </ tr >
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span>
|
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||||
|
|
|
@ -266,6 +266,21 @@ mod slot;
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
pub fn view(tokens: TokenStream) -> TokenStream {
|
pub fn view(tokens: TokenStream) -> TokenStream {
|
||||||
|
view_macro_impl(tokens, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `template` macro behaves like [`view`], except that it wraps the entire tree in a
|
||||||
|
/// [`ViewTemplate`](leptos::prelude::ViewTemplate). This optimizes creation speed by rendering
|
||||||
|
/// most of the view into a `<template>` tag with HTML rendered at compile time, then hydrating it.
|
||||||
|
/// In exchange, there is a small binary size overhead.
|
||||||
|
#[proc_macro_error2::proc_macro_error]
|
||||||
|
#[proc_macro]
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
pub fn template(tokens: TokenStream) -> TokenStream {
|
||||||
|
view_macro_impl(tokens, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_macro_impl(tokens: TokenStream, template: bool) -> TokenStream {
|
||||||
let tokens: proc_macro2::TokenStream = tokens.into();
|
let tokens: proc_macro2::TokenStream = tokens.into();
|
||||||
let mut tokens = tokens.into_iter();
|
let mut tokens = tokens.into_iter();
|
||||||
|
|
||||||
|
@ -308,12 +323,13 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||||
&mut nodes,
|
&mut nodes,
|
||||||
global_class.as_ref(),
|
global_class.as_ref(),
|
||||||
normalized_call_site(proc_macro::Span::call_site()),
|
normalized_call_site(proc_macro::Span::call_site()),
|
||||||
|
template,
|
||||||
);
|
);
|
||||||
|
|
||||||
// The allow lint needs to be put here instead of at the expansion of
|
// The allow lint needs to be put here instead of at the expansion of
|
||||||
// view::attribute_value(). Adding this next to the expanded expression
|
// view::attribute_value(). Adding this next to the expanded expression
|
||||||
// seems to break rust-analyzer, but it works when the allow is put here.
|
// seems to break rust-analyzer, but it works when the allow is put here.
|
||||||
quote! {
|
let output = quote! {
|
||||||
{
|
{
|
||||||
#[allow(unused_braces)]
|
#[allow(unused_braces)]
|
||||||
{
|
{
|
||||||
|
@ -321,6 +337,14 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||||
#nodes_output
|
#nodes_output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if template {
|
||||||
|
quote! {
|
||||||
|
::leptos::prelude::ViewTemplate::new(#output)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use syn::{spanned::Spanned, Expr, ExprPath, ExprRange, RangeLimits, Stmt};
|
||||||
pub(crate) fn component_to_tokens(
|
pub(crate) fn component_to_tokens(
|
||||||
node: &mut NodeElement<impl CustomNode>,
|
node: &mut NodeElement<impl CustomNode>,
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
|
disable_inert_html: bool,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
#[allow(unused)] // TODO this is used by hot-reloading
|
#[allow(unused)] // TODO this is used by hot-reloading
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -191,6 +192,7 @@ pub(crate) fn component_to_tokens(
|
||||||
Some(&mut slots),
|
Some(&mut slots),
|
||||||
global_class,
|
global_class,
|
||||||
None,
|
None,
|
||||||
|
disable_inert_html,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO view marker for hot-reloading
|
// TODO view marker for hot-reloading
|
||||||
|
|
|
@ -15,7 +15,7 @@ use rstml::node::{
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
};
|
};
|
||||||
use syn::{
|
use syn::{
|
||||||
spanned::Spanned, Expr, Expr::Tuple, ExprLit, ExprRange, Lit, LitStr,
|
spanned::Spanned, Expr, Expr::Tuple, ExprLit, ExprRange, Lit, LitStr,
|
||||||
|
@ -34,6 +34,7 @@ pub fn render_view(
|
||||||
nodes: &mut [Node],
|
nodes: &mut [Node],
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
view_marker: Option<String>,
|
view_marker: Option<String>,
|
||||||
|
disable_inert_html: bool,
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let (base, should_add_view) = match nodes.len() {
|
let (base, should_add_view) = match nodes.len() {
|
||||||
0 => {
|
0 => {
|
||||||
|
@ -52,6 +53,8 @@ pub fn render_view(
|
||||||
None,
|
None,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker.as_deref(),
|
view_marker.as_deref(),
|
||||||
|
true,
|
||||||
|
disable_inert_html,
|
||||||
),
|
),
|
||||||
// only add View wrapper and view marker to a regular HTML
|
// only add View wrapper and view marker to a regular HTML
|
||||||
// element or component, not to a <{..} /> attribute list
|
// element or component, not to a <{..} /> attribute list
|
||||||
|
@ -67,6 +70,7 @@ pub fn render_view(
|
||||||
None,
|
None,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker.as_deref(),
|
view_marker.as_deref(),
|
||||||
|
disable_inert_html,
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|
@ -91,12 +95,281 @@ pub fn render_view(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_inert_element(orig_node: &Node<impl CustomNode>) -> bool {
|
||||||
|
// do not use this if the top-level node is not an Element,
|
||||||
|
// or if it's an element with no children and no attrs
|
||||||
|
match orig_node {
|
||||||
|
Node::Element(el) => {
|
||||||
|
if el.attributes().is_empty() && el.children.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, walk over all the nodes to make sure everything is inert
|
||||||
|
let mut nodes = VecDeque::from([orig_node]);
|
||||||
|
|
||||||
|
while let Some(current_element) = nodes.pop_front() {
|
||||||
|
match current_element {
|
||||||
|
Node::Text(_) | Node::RawText(_) => {}
|
||||||
|
Node::Element(node) => {
|
||||||
|
if is_component_node(node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if is_spread_marker(node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match node.name() {
|
||||||
|
NodeName::Block(_) => return false,
|
||||||
|
_ => {
|
||||||
|
// check all attributes
|
||||||
|
for attr in node.attributes() {
|
||||||
|
match attr {
|
||||||
|
NodeAttribute::Block(_) => return false,
|
||||||
|
NodeAttribute::Attribute(attr) => {
|
||||||
|
let static_key =
|
||||||
|
!matches!(attr.key, NodeName::Block(_));
|
||||||
|
|
||||||
|
let static_value = match attr
|
||||||
|
.possible_value
|
||||||
|
.to_value()
|
||||||
|
{
|
||||||
|
None => true,
|
||||||
|
Some(value) => {
|
||||||
|
matches!(&value.value, KVAttributeValue::Expr(expr) if {
|
||||||
|
if let Expr::Lit(lit) = expr {
|
||||||
|
matches!(&lit.lit, Lit::Str(_))
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !static_key || !static_value {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check all children
|
||||||
|
nodes.extend(&node.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Item<'a, T> {
|
||||||
|
Node(&'a Node<T>),
|
||||||
|
ClosingTag(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InertElementBuilder<'a> {
|
||||||
|
GlobalClass {
|
||||||
|
global_class: &'a TokenTree,
|
||||||
|
strs: Vec<GlobalClassItem<'a>>,
|
||||||
|
buffer: String,
|
||||||
|
},
|
||||||
|
NoGlobalClass {
|
||||||
|
buffer: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToTokens for InertElementBuilder<'a> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
match self {
|
||||||
|
InertElementBuilder::GlobalClass { strs, .. } => {
|
||||||
|
tokens.extend(quote! {
|
||||||
|
[#(#strs),*].join("")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
InertElementBuilder::NoGlobalClass { buffer } => {
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#buffer
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GlobalClassItem<'a> {
|
||||||
|
Global(&'a TokenTree),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToTokens for GlobalClassItem<'a> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let addl_tokens = match self {
|
||||||
|
GlobalClassItem::Global(v) => v.to_token_stream(),
|
||||||
|
GlobalClassItem::String(v) => v.to_token_stream(),
|
||||||
|
};
|
||||||
|
tokens.extend(addl_tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> InertElementBuilder<'a> {
|
||||||
|
fn new(global_class: Option<&'a TokenTree>) -> Self {
|
||||||
|
match global_class {
|
||||||
|
None => Self::NoGlobalClass {
|
||||||
|
buffer: String::new(),
|
||||||
|
},
|
||||||
|
Some(global_class) => Self::GlobalClass {
|
||||||
|
global_class,
|
||||||
|
strs: Vec::new(),
|
||||||
|
buffer: String::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, c: char) {
|
||||||
|
match self {
|
||||||
|
InertElementBuilder::GlobalClass { buffer, .. } => buffer.push(c),
|
||||||
|
InertElementBuilder::NoGlobalClass { buffer } => buffer.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_str(&mut self, s: &str) {
|
||||||
|
match self {
|
||||||
|
InertElementBuilder::GlobalClass { buffer, .. } => {
|
||||||
|
buffer.push_str(s)
|
||||||
|
}
|
||||||
|
InertElementBuilder::NoGlobalClass { buffer } => buffer.push_str(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_class(&mut self, class: &str) {
|
||||||
|
match self {
|
||||||
|
InertElementBuilder::GlobalClass {
|
||||||
|
global_class,
|
||||||
|
strs,
|
||||||
|
buffer,
|
||||||
|
} => {
|
||||||
|
buffer.push_str(" class=\"");
|
||||||
|
strs.push(GlobalClassItem::String(std::mem::take(buffer)));
|
||||||
|
strs.push(GlobalClassItem::Global(global_class));
|
||||||
|
buffer.push(' ');
|
||||||
|
buffer.push_str(class);
|
||||||
|
buffer.push('"');
|
||||||
|
}
|
||||||
|
InertElementBuilder::NoGlobalClass { buffer } => {
|
||||||
|
buffer.push_str(" class=\"");
|
||||||
|
buffer.push_str(class);
|
||||||
|
buffer.push('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&mut self) {
|
||||||
|
match self {
|
||||||
|
InertElementBuilder::GlobalClass { strs, buffer, .. } => {
|
||||||
|
strs.push(GlobalClassItem::String(std::mem::take(buffer)));
|
||||||
|
}
|
||||||
|
InertElementBuilder::NoGlobalClass { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inert_element_to_tokens(
|
||||||
|
node: &Node<impl CustomNode>,
|
||||||
|
global_class: Option<&TokenTree>,
|
||||||
|
) -> Option<TokenStream> {
|
||||||
|
let mut html = InertElementBuilder::new(global_class);
|
||||||
|
let mut nodes = VecDeque::from([Item::Node(node)]);
|
||||||
|
|
||||||
|
while let Some(current) = nodes.pop_front() {
|
||||||
|
match current {
|
||||||
|
Item::ClosingTag(tag) => {
|
||||||
|
// closing tag
|
||||||
|
html.push_str("</");
|
||||||
|
html.push_str(&tag);
|
||||||
|
html.push('>');
|
||||||
|
}
|
||||||
|
Item::Node(current) => {
|
||||||
|
match current {
|
||||||
|
Node::RawText(raw) => {
|
||||||
|
let text = raw.to_string_best();
|
||||||
|
html.push_str(&text);
|
||||||
|
}
|
||||||
|
Node::Text(text) => {
|
||||||
|
let text = text.value_string();
|
||||||
|
html.push_str(&text);
|
||||||
|
}
|
||||||
|
Node::Element(node) => {
|
||||||
|
let self_closing = is_self_closing(node);
|
||||||
|
let el_name = node.name().to_string();
|
||||||
|
|
||||||
|
// opening tag
|
||||||
|
html.push('<');
|
||||||
|
html.push_str(&el_name);
|
||||||
|
|
||||||
|
for attr in node.attributes() {
|
||||||
|
if let NodeAttribute::Attribute(attr) = attr {
|
||||||
|
let attr_name = attr.key.to_string();
|
||||||
|
if attr_name != "class" {
|
||||||
|
html.push(' ');
|
||||||
|
html.push_str(&attr_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) =
|
||||||
|
attr.possible_value.to_value()
|
||||||
|
{
|
||||||
|
if let KVAttributeValue::Expr(Expr::Lit(
|
||||||
|
lit,
|
||||||
|
)) = &value.value
|
||||||
|
{
|
||||||
|
if let Lit::Str(txt) = &lit.lit {
|
||||||
|
if attr_name == "class" {
|
||||||
|
html.push_class(&txt.value());
|
||||||
|
} else {
|
||||||
|
html.push_str("=\"");
|
||||||
|
html.push_str(&txt.value());
|
||||||
|
html.push('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.push('>');
|
||||||
|
|
||||||
|
// render all children
|
||||||
|
if !self_closing {
|
||||||
|
nodes.push_front(Item::ClosingTag(el_name));
|
||||||
|
let children = node.children.iter().rev();
|
||||||
|
for child in children {
|
||||||
|
nodes.push_front(Item::Node(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.finish();
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
::leptos::tachys::html::InertElement::new(#html)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn element_children_to_tokens(
|
fn element_children_to_tokens(
|
||||||
nodes: &mut [Node<impl CustomNode>],
|
nodes: &mut [Node<impl CustomNode>],
|
||||||
parent_type: TagType,
|
parent_type: TagType,
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
view_marker: Option<&str>,
|
view_marker: Option<&str>,
|
||||||
|
disable_inert_html: bool,
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let children = children_to_tokens(
|
let children = children_to_tokens(
|
||||||
nodes,
|
nodes,
|
||||||
|
@ -104,6 +377,8 @@ fn element_children_to_tokens(
|
||||||
parent_slots,
|
parent_slots,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker,
|
view_marker,
|
||||||
|
false,
|
||||||
|
disable_inert_html,
|
||||||
);
|
);
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
None
|
None
|
||||||
|
@ -145,6 +420,7 @@ fn fragment_to_tokens(
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
view_marker: Option<&str>,
|
view_marker: Option<&str>,
|
||||||
|
disable_inert_html: bool,
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let children = children_to_tokens(
|
let children = children_to_tokens(
|
||||||
nodes,
|
nodes,
|
||||||
|
@ -152,6 +428,8 @@ fn fragment_to_tokens(
|
||||||
parent_slots,
|
parent_slots,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker,
|
view_marker,
|
||||||
|
true,
|
||||||
|
disable_inert_html,
|
||||||
);
|
);
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
None
|
None
|
||||||
|
@ -183,6 +461,8 @@ fn children_to_tokens(
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
view_marker: Option<&str>,
|
view_marker: Option<&str>,
|
||||||
|
top_level: bool,
|
||||||
|
disable_inert_html: bool,
|
||||||
) -> Vec<TokenStream> {
|
) -> Vec<TokenStream> {
|
||||||
if nodes.len() == 1 {
|
if nodes.len() == 1 {
|
||||||
match node_to_tokens(
|
match node_to_tokens(
|
||||||
|
@ -191,6 +471,8 @@ fn children_to_tokens(
|
||||||
parent_slots,
|
parent_slots,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker,
|
view_marker,
|
||||||
|
top_level,
|
||||||
|
disable_inert_html,
|
||||||
) {
|
) {
|
||||||
Some(tokens) => vec![tokens],
|
Some(tokens) => vec![tokens],
|
||||||
None => vec![],
|
None => vec![],
|
||||||
|
@ -206,6 +488,8 @@ fn children_to_tokens(
|
||||||
Some(&mut slots),
|
Some(&mut slots),
|
||||||
global_class,
|
global_class,
|
||||||
view_marker,
|
view_marker,
|
||||||
|
top_level,
|
||||||
|
disable_inert_html,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -227,7 +511,11 @@ fn node_to_tokens(
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
view_marker: Option<&str>,
|
view_marker: Option<&str>,
|
||||||
|
top_level: bool,
|
||||||
|
disable_inert_html: bool,
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
|
let is_inert = !disable_inert_html && is_inert_element(node);
|
||||||
|
|
||||||
match node {
|
match node {
|
||||||
Node::Comment(_) => None,
|
Node::Comment(_) => None,
|
||||||
Node::Doctype(node) => {
|
Node::Doctype(node) => {
|
||||||
|
@ -240,6 +528,7 @@ fn node_to_tokens(
|
||||||
parent_slots,
|
parent_slots,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker,
|
view_marker,
|
||||||
|
disable_inert_html,
|
||||||
),
|
),
|
||||||
Node::Block(block) => Some(quote! { #block }),
|
Node::Block(block) => Some(quote! { #block }),
|
||||||
Node::Text(text) => Some(text_to_tokens(&text.value)),
|
Node::Text(text) => Some(text_to_tokens(&text.value)),
|
||||||
|
@ -248,13 +537,20 @@ fn node_to_tokens(
|
||||||
let text = syn::LitStr::new(&text, raw.span());
|
let text = syn::LitStr::new(&text, raw.span());
|
||||||
Some(text_to_tokens(&text))
|
Some(text_to_tokens(&text))
|
||||||
}
|
}
|
||||||
Node::Element(node) => element_to_tokens(
|
Node::Element(el_node) => {
|
||||||
node,
|
if !top_level && is_inert {
|
||||||
|
inert_element_to_tokens(node, global_class)
|
||||||
|
} else {
|
||||||
|
element_to_tokens(
|
||||||
|
el_node,
|
||||||
parent_type,
|
parent_type,
|
||||||
parent_slots,
|
parent_slots,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker,
|
view_marker,
|
||||||
),
|
disable_inert_html,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Node::Custom(node) => Some(node.to_token_stream()),
|
Node::Custom(node) => Some(node.to_token_stream()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,6 +574,7 @@ pub(crate) fn element_to_tokens(
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
view_marker: Option<&str>,
|
view_marker: Option<&str>,
|
||||||
|
disable_inert_html: bool,
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
// attribute sorting:
|
// attribute sorting:
|
||||||
//
|
//
|
||||||
|
@ -347,10 +644,16 @@ pub(crate) fn element_to_tokens(
|
||||||
if is_component_node(node) {
|
if is_component_node(node) {
|
||||||
if let Some(slot) = get_slot(node) {
|
if let Some(slot) = get_slot(node) {
|
||||||
let slot = slot.clone();
|
let slot = slot.clone();
|
||||||
slot_to_tokens(node, &slot, parent_slots, global_class);
|
slot_to_tokens(
|
||||||
|
node,
|
||||||
|
&slot,
|
||||||
|
parent_slots,
|
||||||
|
global_class,
|
||||||
|
disable_inert_html,
|
||||||
|
);
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(component_to_tokens(node, global_class))
|
Some(component_to_tokens(node, global_class, disable_inert_html))
|
||||||
}
|
}
|
||||||
} else if is_spread_marker(node) {
|
} else if is_spread_marker(node) {
|
||||||
let mut attributes = Vec::new();
|
let mut attributes = Vec::new();
|
||||||
|
@ -467,6 +770,7 @@ pub(crate) fn element_to_tokens(
|
||||||
parent_slots,
|
parent_slots,
|
||||||
global_class,
|
global_class,
|
||||||
view_marker,
|
view_marker,
|
||||||
|
disable_inert_html,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if !node.children.is_empty() {
|
if !node.children.is_empty() {
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub(crate) fn slot_to_tokens(
|
||||||
slot: &KeyedAttribute,
|
slot: &KeyedAttribute,
|
||||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||||
global_class: Option<&TokenTree>,
|
global_class: Option<&TokenTree>,
|
||||||
|
disable_inert_html: bool,
|
||||||
) {
|
) {
|
||||||
let name = slot.key.to_string();
|
let name = slot.key.to_string();
|
||||||
let name = name.trim();
|
let name = name.trim();
|
||||||
|
@ -118,6 +119,7 @@ pub(crate) fn slot_to_tokens(
|
||||||
Some(&mut slots),
|
Some(&mut slots),
|
||||||
global_class,
|
global_class,
|
||||||
None,
|
None,
|
||||||
|
disable_inert_html,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO view markers for hot-reloading
|
// TODO view markers for hot-reloading
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
use self::attribute::Attribute;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
hydration::Cursor,
|
||||||
no_attrs,
|
no_attrs,
|
||||||
renderer::Renderer,
|
prelude::AddAnyAttr,
|
||||||
view::{Position, Render, RenderHtml},
|
renderer::{CastFrom, DomRenderer, Renderer},
|
||||||
|
view::{Position, PositionState, Render, RenderHtml},
|
||||||
};
|
};
|
||||||
use std::marker::PhantomData;
|
use std::{borrow::Cow, marker::PhantomData};
|
||||||
|
|
||||||
/// Types for HTML attributes.
|
/// Types for HTML attributes.
|
||||||
pub mod attribute;
|
pub mod attribute;
|
||||||
|
@ -76,8 +79,99 @@ where
|
||||||
|
|
||||||
fn hydrate<const FROM_SERVER: bool>(
|
fn hydrate<const FROM_SERVER: bool>(
|
||||||
self,
|
self,
|
||||||
_cursor: &crate::hydration::Cursor<R>,
|
_cursor: &Cursor<R>,
|
||||||
_position: &crate::view::PositionState,
|
_position: &PositionState,
|
||||||
) -> Self::State {
|
) -> Self::State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An element that contains no interactivity, and whose contents can be known at compile time.
|
||||||
|
pub struct InertElement {
|
||||||
|
html: Cow<'static, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InertElement {
|
||||||
|
/// Creates a new inert element.
|
||||||
|
pub fn new(html: impl Into<Cow<'static, str>>) -> Self {
|
||||||
|
Self { html: html.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Rndr> Render<Rndr> for InertElement
|
||||||
|
where
|
||||||
|
Rndr: DomRenderer,
|
||||||
|
{
|
||||||
|
type State = Rndr::Element;
|
||||||
|
|
||||||
|
fn build(self) -> Self::State {
|
||||||
|
Rndr::create_element_from_html(&self.html)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(self, _state: &mut Self::State) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Rndr> AddAnyAttr<Rndr> for InertElement
|
||||||
|
where
|
||||||
|
Rndr: DomRenderer,
|
||||||
|
{
|
||||||
|
type Output<SomeNewAttr: Attribute<Rndr>> = Self;
|
||||||
|
|
||||||
|
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||||
|
self,
|
||||||
|
_attr: NewAttr,
|
||||||
|
) -> Self::Output<NewAttr>
|
||||||
|
where
|
||||||
|
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||||
|
{
|
||||||
|
panic!(
|
||||||
|
"InertElement does not support adding attributes. It should only \
|
||||||
|
be used as a child, and not returned at the top level."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Rndr> RenderHtml<Rndr> for InertElement
|
||||||
|
where
|
||||||
|
Rndr: DomRenderer,
|
||||||
|
{
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
|
const MIN_LENGTH: usize = 0;
|
||||||
|
|
||||||
|
fn html_len(&self) -> usize {
|
||||||
|
self.html.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dry_resolve(&mut self) {}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_html_with_buf(
|
||||||
|
self,
|
||||||
|
buf: &mut String,
|
||||||
|
position: &mut Position,
|
||||||
|
_escape: bool,
|
||||||
|
_mark_branches: bool,
|
||||||
|
) {
|
||||||
|
buf.push_str(&self.html);
|
||||||
|
*position = Position::NextChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hydrate<const FROM_SERVER: bool>(
|
||||||
|
self,
|
||||||
|
cursor: &Cursor<Rndr>,
|
||||||
|
position: &PositionState,
|
||||||
|
) -> Self::State {
|
||||||
|
let curr_position = position.get();
|
||||||
|
if curr_position == Position::FirstChild {
|
||||||
|
cursor.child();
|
||||||
|
} else if curr_position != Position::Current {
|
||||||
|
cursor.sibling();
|
||||||
|
}
|
||||||
|
let el = Rndr::Element::cast_from(cursor.current()).unwrap();
|
||||||
|
position.set(Position::NextChild);
|
||||||
|
el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -390,6 +390,13 @@ impl DomRenderer for Dom {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into()
|
.unchecked_into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_element_from_html(html: &str) -> Self::Element {
|
||||||
|
// TODO can be optimized to cache HTML strings or cache <template>?
|
||||||
|
let tpl = document().create_element("template").unwrap();
|
||||||
|
tpl.set_inner_html(html);
|
||||||
|
Self::clone_template(tpl.unchecked_ref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mountable<Dom> for Node {
|
impl Mountable<Dom> for Node {
|
||||||
|
|
|
@ -300,6 +300,10 @@ impl DomRenderer for MockDom {
|
||||||
fn clone_template(tpl: &Self::TemplateElement) -> Self::Element {
|
fn clone_template(tpl: &Self::TemplateElement) -> Self::Element {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_element_from_html(html: &str) -> Self::Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Document {
|
impl Default for Document {
|
||||||
|
|
|
@ -213,8 +213,12 @@ pub trait DomRenderer: Renderer {
|
||||||
fn get_template<V>() -> Self::TemplateElement
|
fn get_template<V>() -> Self::TemplateElement
|
||||||
where
|
where
|
||||||
V: ToTemplate + 'static;
|
V: ToTemplate + 'static;
|
||||||
|
|
||||||
/// Deeply clones a template.
|
/// Deeply clones a template.
|
||||||
fn clone_template(tpl: &Self::TemplateElement) -> Self::Element;
|
fn clone_template(tpl: &Self::TemplateElement) -> Self::Element;
|
||||||
|
|
||||||
|
/// Creates a single element from a string of HTML.
|
||||||
|
fn create_element_from_html(html: &str) -> Self::Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to cast from one type to another.
|
/// Attempts to cast from one type to another.
|
||||||
|
|
|
@ -560,6 +560,10 @@ impl DomRenderer for Sledgehammer {
|
||||||
});
|
});
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_element_from_html(html: &str) -> Self::Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mountable<Sledgehammer> for SNode {
|
impl Mountable<Sledgehammer> for SNode {
|
||||||
|
|
Loading…
Reference in a new issue