mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Improve nested html! expansion by unwrapping VNodes (#820)
* Improve nested html! expansion by omitting VNodes * Add unittest and fix child node expansion * Fix existing unittests and check backward compat * Fix rebase conflicts * Parse expression * Fix clippy Co-authored-by: Justin Starry <justin.starry@icloud.com>
This commit is contained in:
parent
b801d4fbf9
commit
a900fbee49
@ -23,7 +23,7 @@ lazy_static = "1.3.0"
|
||||
proc-macro-hack = "0.5"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
syn = { version = "1.0", features = ["full", "extra-traits", "visit-mut"] }
|
||||
|
||||
[dev-dependencies]
|
||||
yew = { path = "../.." }
|
||||
|
||||
@ -26,7 +26,7 @@ impl PeekValue<()> for HtmlBlock {
|
||||
|
||||
impl Parse for HtmlBlock {
|
||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||
let content;
|
||||
let content: syn::parse::ParseBuffer<'_>;
|
||||
let brace = braced!(content in input);
|
||||
let content = if HtmlIterable::peek(content.cursor()).is_some() {
|
||||
BlockContent::Iterable(content.parse()?)
|
||||
|
||||
@ -11,7 +11,7 @@ use syn::parse;
|
||||
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Expr, Ident, Path, PathArguments, PathSegment, Token, Type, TypePath};
|
||||
use syn::{Expr, Ident, Index, Path, PathArguments, PathSegment, Token, Type, TypePath};
|
||||
|
||||
pub struct HtmlComponent {
|
||||
ty: Type,
|
||||
@ -123,10 +123,17 @@ impl ToTokens for HtmlComponent {
|
||||
};
|
||||
|
||||
let set_children = if !children.is_empty() {
|
||||
let i = (0..children.len())
|
||||
.map(|x| Index::from(x))
|
||||
.collect::<Vec<_>>();
|
||||
quote! {
|
||||
.children(::yew::html::ChildrenRenderer::new(
|
||||
vec![#(#children.into(),)*]
|
||||
))
|
||||
.children(::yew::html::ChildrenRenderer::new({
|
||||
let mut v = Vec::new();
|
||||
let comps = (#(#children,)*);
|
||||
#(::yew::utils::NodeSeq::from(comps.#i).into_iter()
|
||||
.for_each(|x| v.push(x.into()));)*
|
||||
v
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
|
||||
@ -7,6 +7,10 @@ use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Expr, Token};
|
||||
|
||||
use proc_macro2::{Ident, Span};
|
||||
use syn::visit_mut::{self, VisitMut};
|
||||
use syn::Macro;
|
||||
|
||||
pub struct HtmlIterable(Expr);
|
||||
|
||||
impl PeekValue<()> for HtmlIterable {
|
||||
@ -16,12 +20,28 @@ impl PeekValue<()> for HtmlIterable {
|
||||
}
|
||||
}
|
||||
|
||||
struct HtmlInnerModifier;
|
||||
impl VisitMut for HtmlInnerModifier {
|
||||
fn visit_macro_mut(&mut self, node: &mut Macro) {
|
||||
if node.path.is_ident("html") {
|
||||
let ident = &mut node.path.segments.last_mut().unwrap().ident;
|
||||
*ident = Ident::new("html_nested", Span::call_site());
|
||||
}
|
||||
|
||||
// Delegate to the default impl to visit any nested functions.
|
||||
visit_mut::visit_macro_mut(self, node);
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for HtmlIterable {
|
||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||
let for_token = input.parse::<Token![for]>()?;
|
||||
|
||||
match input.parse() {
|
||||
Ok(expr) => Ok(HtmlIterable(expr)),
|
||||
Ok(mut expr) => {
|
||||
HtmlInnerModifier.visit_expr_mut(&mut expr);
|
||||
Ok(HtmlIterable(expr))
|
||||
}
|
||||
Err(err) => {
|
||||
if err.to_string().starts_with("unexpected end of input") {
|
||||
Err(syn::Error::new_spanned(
|
||||
|
||||
@ -4,8 +4,26 @@ use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::buffer::Cursor;
|
||||
use syn::parse::{Parse, ParseStream, Result};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::Expr;
|
||||
use syn::Lit;
|
||||
|
||||
use proc_macro2::{Ident, Span};
|
||||
use syn::visit_mut::{self, VisitMut};
|
||||
use syn::Macro;
|
||||
|
||||
struct HtmlInnerModifier;
|
||||
impl VisitMut for HtmlInnerModifier {
|
||||
fn visit_macro_mut(&mut self, node: &mut Macro) {
|
||||
if node.path.is_ident("html") {
|
||||
let ident = &mut node.path.segments.last_mut().unwrap().ident;
|
||||
*ident = Ident::new("html_nested", Span::call_site());
|
||||
}
|
||||
|
||||
// Delegate to the default impl to visit any nested functions.
|
||||
visit_mut::visit_macro_mut(self, node);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HtmlNode(Node);
|
||||
|
||||
impl Parse for HtmlNode {
|
||||
@ -18,7 +36,9 @@ impl Parse for HtmlNode {
|
||||
}
|
||||
Node::Literal(lit)
|
||||
} else {
|
||||
Node::Raw(input.parse()?)
|
||||
let mut expr: Expr = input.parse()?;
|
||||
HtmlInnerModifier.visit_expr_mut(&mut expr);
|
||||
Node::Expression(expr)
|
||||
};
|
||||
|
||||
Ok(HtmlNode(node))
|
||||
@ -46,12 +66,8 @@ impl ToTokens for HtmlNode {
|
||||
impl ToTokens for Node {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let node_token = match &self {
|
||||
Node::Literal(lit) => quote! {
|
||||
::yew::virtual_dom::VNode::from(#lit)
|
||||
},
|
||||
Node::Raw(stream) => quote_spanned! {stream.span()=>
|
||||
::yew::virtual_dom::VNode::from({#stream})
|
||||
},
|
||||
Node::Literal(lit) => quote! {#lit},
|
||||
Node::Expression(expr) => quote_spanned! {expr.span()=> {#expr} },
|
||||
};
|
||||
|
||||
tokens.extend(node_token);
|
||||
@ -60,5 +76,5 @@ impl ToTokens for Node {
|
||||
|
||||
enum Node {
|
||||
Literal(Lit),
|
||||
Raw(TokenStream),
|
||||
Expression(Expr),
|
||||
}
|
||||
|
||||
@ -126,6 +126,19 @@ impl HtmlTree {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HtmlRootNested(HtmlTreeNested);
|
||||
impl Parse for HtmlRootNested {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(HtmlRootNested(HtmlTreeNested::parse(input)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for HtmlRootNested {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
self.0.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HtmlTreeNested(HtmlTree);
|
||||
impl Parse for HtmlTreeNested {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
|
||||
@ -63,7 +63,7 @@ mod derive_props;
|
||||
mod html_tree;
|
||||
|
||||
use derive_props::DerivePropsInput;
|
||||
use html_tree::HtmlRoot;
|
||||
use html_tree::{HtmlRoot, HtmlRootNested};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_hack::proc_macro_hack;
|
||||
use quote::{quote, ToTokens};
|
||||
@ -94,6 +94,12 @@ pub fn derive_props(input: TokenStream) -> TokenStream {
|
||||
TokenStream::from(input.into_token_stream())
|
||||
}
|
||||
|
||||
#[proc_macro_hack]
|
||||
pub fn html_nested(input: TokenStream) -> TokenStream {
|
||||
let root = parse_macro_input!(input as HtmlRootNested);
|
||||
TokenStream::from(quote! {#root})
|
||||
}
|
||||
|
||||
#[proc_macro_hack]
|
||||
pub fn html(input: TokenStream) -> TokenStream {
|
||||
let root = parse_macro_input!(input as HtmlRoot);
|
||||
|
||||
@ -72,9 +72,14 @@ use proc_macro_hack::proc_macro_hack;
|
||||
#[proc_macro_hack(support_nested)]
|
||||
pub use yew_macro::html;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_hack(support_nested)]
|
||||
pub use yew_macro::html_nested;
|
||||
|
||||
/// This module contains macros which implements html! macro and JSX-like templates
|
||||
pub mod macros {
|
||||
pub use crate::html;
|
||||
pub use crate::html_nested;
|
||||
pub use yew_macro::Properties;
|
||||
}
|
||||
|
||||
@ -158,6 +163,7 @@ pub mod prelude {
|
||||
Renderable, ShouldRender,
|
||||
};
|
||||
pub use crate::macros::*;
|
||||
pub use crate::utils::NodeSeq;
|
||||
pub use crate::virtual_dom::Classes;
|
||||
|
||||
/// Prelude module for creating worker.
|
||||
|
||||
29
src/utils.rs
29
src/utils.rs
@ -3,6 +3,8 @@
|
||||
use failure::{err_msg, Error};
|
||||
use stdweb::web::document;
|
||||
|
||||
use crate::virtual_dom::VNode;
|
||||
|
||||
/// Returns `host` for the current document. Useful to connect to a server that server the app.
|
||||
pub fn host() -> Result<String, Error> {
|
||||
document()
|
||||
@ -10,3 +12,30 @@ pub fn host() -> Result<String, Error> {
|
||||
.ok_or_else(|| err_msg("can't get location"))
|
||||
.and_then(|l| l.host().map_err(Error::from))
|
||||
}
|
||||
|
||||
/// Specialty type necessary for helping flattening components returned from nested html macros.
|
||||
#[derive(Debug)]
|
||||
pub struct NodeSeq<T>(Vec<T>)
|
||||
where
|
||||
T: Into<VNode>;
|
||||
|
||||
impl<T: Into<VNode>> From<T> for NodeSeq<T> {
|
||||
fn from(val: T) -> Self {
|
||||
NodeSeq(vec![val])
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<VNode>> From<Vec<T>> for NodeSeq<T> {
|
||||
fn from(val: Vec<T>) -> Self {
|
||||
NodeSeq(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<VNode>> IntoIterator for NodeSeq<T> {
|
||||
type Item = T;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,13 +188,13 @@ error[E0599]: no method named `children` found for type `ChildPropertiesBuilder<
|
||||
|
|
||||
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
|
||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<&str>` is not satisfied
|
||||
--> $DIR/html-component-fail.rs:78:5
|
||||
|
|
||||
78 | html! { <ChildContainer>{ "Not allowed" }</ChildContainer> };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<yew::virtual_dom::vnode::VNode>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<&str>` is not implemented for `yew::virtual_dom::vcomp::VChild<Child>`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `yew::virtual_dom::vnode::VNode`
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<yew::virtual_dom::vcomp::VChild<Child>>` for `&str`
|
||||
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild<Child>: std::convert::From<yew::virtual_dom::vnode::VNode>` is not satisfied
|
||||
|
||||
@ -2,6 +2,79 @@
|
||||
|
||||
use yew::prelude::*;
|
||||
use yew::html::ChildrenRenderer;
|
||||
use yew::virtual_dom::{VChild, VComp, VNode};
|
||||
|
||||
#[derive(Clone, Debug, Properties)]
|
||||
pub struct ParentProperties {
|
||||
#[props(required)]
|
||||
pub children: ChildrenRenderer<ParentVariant>,
|
||||
}
|
||||
|
||||
pub struct Parent {
|
||||
props: ParentProperties,
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
impl Component for Parent {
|
||||
type Message = ();
|
||||
type Properties = ParentProperties;
|
||||
|
||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
return Parent { props, link };
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ParentVariants {
|
||||
Child(<Child as Component>::Properties),
|
||||
ChildA(<ChildA as Component>::Properties),
|
||||
}
|
||||
|
||||
impl From<ChildProperties> for ParentVariants {
|
||||
fn from(props: ChildProperties) -> Self {
|
||||
ParentVariants::Child(props)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChildAProperties> for ParentVariants {
|
||||
fn from(props: ChildAProperties) -> Self {
|
||||
ParentVariants::ChildA(props)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ParentVariant {
|
||||
props: ParentVariants,
|
||||
}
|
||||
|
||||
impl<CHILD> From<VChild<CHILD>> for ParentVariant
|
||||
where
|
||||
CHILD: Component,
|
||||
CHILD::Properties: Into<ParentVariants>,
|
||||
{
|
||||
fn from(comp: VChild<CHILD>) -> Self {
|
||||
return ParentVariant {
|
||||
props: comp.props.into(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<VNode> for ParentVariant {
|
||||
fn into(self) -> VNode {
|
||||
match self.props {
|
||||
ParentVariants::Child(props) => VComp::new::<Child>(props, NodeRef::default()).into(),
|
||||
ParentVariants::ChildA(props) => VComp::new::<ChildA>(props, NodeRef::default()).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Properties, Default, PartialEq)]
|
||||
pub struct ChildProperties {
|
||||
@ -30,6 +103,33 @@ impl Component for Child {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Properties, Default, PartialEq)]
|
||||
pub struct ChildAProperties {
|
||||
pub string: String,
|
||||
#[props(required)]
|
||||
pub int: i32,
|
||||
pub vec: Vec<i32>,
|
||||
pub optional_callback: Option<Callback<()>>,
|
||||
}
|
||||
|
||||
pub struct ChildA;
|
||||
impl Component for ChildA {
|
||||
type Message = ();
|
||||
type Properties = ChildAProperties;
|
||||
|
||||
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
|
||||
ChildA
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Self::Message) -> ShouldRender {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Properties, Default)]
|
||||
pub struct ContainerProperties {
|
||||
#[props(required)]
|
||||
@ -178,6 +278,22 @@ fn compile_pass() {
|
||||
<ChildContainer int=1><Child int = 2 /><Child int = 2 /></ChildContainer>
|
||||
</>
|
||||
};
|
||||
|
||||
html! {
|
||||
<Parent>
|
||||
<ChildA int=1 />
|
||||
{
|
||||
html! {
|
||||
<Child int=1 />
|
||||
}
|
||||
}
|
||||
{(0..2).map(|_| {
|
||||
return html! {
|
||||
<Child int=1 />
|
||||
}
|
||||
}).collect::<Vec<VChild<Child>>>()}
|
||||
</Parent>
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user