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:
Konstantin Itskov 2019-12-28 23:27:23 -05:00 committed by Justin Starry
parent b801d4fbf9
commit a900fbee49
11 changed files with 232 additions and 19 deletions

View File

@ -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 = "../.." }

View File

@ -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()?)

View File

@ -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! {}

View File

@ -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(

View File

@ -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),
}

View File

@ -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> {

View File

@ -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);

View File

@ -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.

View File

@ -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()
}
}

View File

@ -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

View File

@ -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() {}