Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lay foundation for a statically typed vdom #2396

Closed
wants to merge 56 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e4eaac6
foundations of statically typed vdom
ranile Jan 22, 2022
d95ca3e
add tiny test
ranile Jan 22, 2022
6926723
listeners
ranile Jan 23, 2022
c25c612
a proper test
ranile Jan 23, 2022
60af6ab
generate all elements
ranile Jan 23, 2022
03250f2
-, not _
ranile Jan 23, 2022
507c2ee
it works
ranile Jan 23, 2022
4a885d4
we don't build typings for <html>
ranile Jan 23, 2022
6c431ad
make the html! macro work
ranile Jan 23, 2022
362aee9
please clippy and fmt
ranile Jan 23, 2022
3a0b4d5
reduce the number of compile errors
ranile Jan 24, 2022
b38cce5
fix the macro?
ranile Jan 25, 2022
36fdea5
fix tests
ranile Jan 25, 2022
09ae6c1
some clippy
ranile Jan 25, 2022
9f81614
disabled is not valid for <a>
ranile Jan 25, 2022
7399aa2
bool conversion
ranile Jan 25, 2022
1a5293d
try to make things compile
ranile Jan 25, 2022
9c7c867
more things compile
ranile Jan 26, 2022
a8539cf
text area uses children not value attribute
ranile Jan 26, 2022
c7870aa
clippy, fmt
ranile Jan 26, 2022
59297b9
suspense_not_suspended_at_start passes
ranile Jan 26, 2022
3dd8f6a
svg is untyped
ranile Jan 26, 2022
c24bb2d
yeet special treatment for textarea
ranile Jan 26, 2022
e35f193
examples should compile
ranile Jan 26, 2022
f19a28d
most of CI should be green
ranile Jan 26, 2022
9f05bc5
Merge branch 'master' into typed-vdom-foundation
ranile Jan 28, 2022
f43d0f5
fix doc tests
ranile Jan 28, 2022
dbcc1de
fix lints
ranile Jan 28, 2022
f117b84
clippy, fmt
ranile Jan 28, 2022
075004d
should fix doc tests?
ranile Jan 28, 2022
7430230
html-dashed-name is not an ident + revert unneeded change
ranile Jan 31, 2022
bda5ac8
raw idents or HtmlDashedName for prop labels
ranile Jan 31, 2022
edb1ee5
uncomment aria-labels attrs
ranile Jan 31, 2022
36152a4
fmt
ranile Jan 31, 2022
c8d98a8
fix error
ranile Jan 31, 2022
3960035
fmt
ranile Jan 31, 2022
89499bd
fix doc test
ranile Feb 1, 2022
23fb6a5
please fmt
ranile Feb 1, 2022
5d42e49
please
ranile Feb 3, 2022
c0e6ffe
please v2
ranile Feb 3, 2022
1035b7e
will this fucking work?
ranile Feb 5, 2022
50dcb36
another try
ranile Feb 5, 2022
fbfebfa
commit file from CI
ranile Feb 5, 2022
cec52f8
Revert "another try"
ranile Feb 5, 2022
e355d96
Merge branch 'master' into typed-vdom-foundation
ranile Feb 11, 2022
f5b252b
fix typo in docs, ignore failing test
ranile Feb 11, 2022
9173042
Fix IntoPropValue impl for bool
ranile Feb 16, 2022
3c473f9
hygiene + fragment key
ranile Feb 16, 2022
78cdf16
support for ARIA attributes
ranile Feb 16, 2022
5bec200
opps, we ain't on 0.58
ranile Feb 16, 2022
95cb65e
fix macro test
ranile Feb 16, 2022
f51003c
Merge branch 'master' into typed-vdom-foundation
ranile Feb 22, 2022
741c29b
don't add global fields in literally every element props struct
ranile Feb 22, 2022
8be6eec
clippy & fmt
ranile Feb 22, 2022
06d4bd8
fix fuck up
ranile Feb 22, 2022
3ee3b94
Merge branch 'master' into typed-vdom-foundation
ranile Mar 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/yew-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ proc-macro = true

[dependencies]
boolinator = "2"
convert_case = "0.5"
lazy_static = "1"
proc-macro-error = "1"
proc-macro2 = "1"
Expand Down
12 changes: 12 additions & 0 deletions packages/yew-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ mod function_component;
mod html_tree;
mod props;
mod stringify;
mod typed_vdom;

use derive_props::DerivePropsInput;
use function_component::{function_component_impl, FunctionComponent, FunctionComponentName};
Expand Down Expand Up @@ -134,3 +135,14 @@ pub fn function_component(
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

/// Declares HTML element
///
/// Every HTML element is a component, which returns a manually constructed `VTag`.
/// This macro is used to generate this component.
ranile marked this conversation as resolved.
Show resolved Hide resolved
#[proc_macro]
pub fn generate_element(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as typed_vdom::generate_element::GenerateElement);

input.to_token_stream().into()
}
103 changes: 103 additions & 0 deletions packages/yew-macro/src/typed_vdom/generate_element.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::typed_vdom::globals::global_attributes;
use crate::typed_vdom::{kw, AttributePropDefinition};
use convert_case::{Case, Casing};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{braced, Token};

pub struct GenerateElement {
element_name: Ident,
props: Vec<AttributePropDefinition>,
}

impl Parse for GenerateElement {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
let _separator = input.parse::<Token![;]>()?;

let props = {
let _props_kw = input.parse::<kw::props>()?;
let _separator = input.parse::<Token![:]>()?;

let buf;
let _brace_token = braced!(buf in input);
buf
};
let props: Punctuated<AttributePropDefinition, Token![,]> =
Punctuated::parse_terminated(&props)?;

Ok(Self {
element_name: name,
props: props.into_iter().collect(),
})
}
}

impl ToTokens for GenerateElement {
fn to_tokens(&self, tokens: &mut TokenStream) {
let element_name = &self.element_name;
let mut prop_definitions = self.props.clone();
prop_definitions.extend(global_attributes());

let props_ident = format_ident!(
"{}Props",
element_name.to_string().to_case(Case::Pascal),
span = element_name.span()
);
let props = prop_definitions.iter().map(|it| it.build_fields());
let if_lets = prop_definitions.iter().map(|it| it.build_if_lets());

let out = quote! {
#[allow(non_camel_case_types)]
struct #element_name;

#[derive(::std::default::Default, ::std::clone::Clone, ::std::fmt::Debug, ::yew::html::Properties, ::std::cmp::PartialEq)]
struct #props_ident {
#(#props)*
}

impl #props_ident {
fn into_data(self) -> ElementData {

ElementData {
node_ref: ::std::option::Option::unwrap_or_default(self.node_ref),
attributes: {
let mut attrs = ::std::collections::HashMap::new();
#(#if_lets)*
attrs
},
listeners: ::std::default::Default::default(),
key: self.key,
children: self.children.map(|it| it.into_iter().collect()).unwrap_or_default()
}
}
}

impl ::yew::Component for #element_name {
type Message = ();
type Properties = #props_ident;

fn create(_ctx: &::yew::html::Context<Self>) -> Self {
Self
}

fn view(&self, ctx: &::yew::html::Context<Self>) -> ::yew::html::Html {
let element: ElementData = ctx.props().clone().into_data();
// todo use __new_{other, textarea, input} depending upon the element
::yew::virtual_dom::VTag::__new_other(
::std::stringify!(#element_name).into(),
element.node_ref,
element.key,
::yew::virtual_dom::Attributes::IndexMap(element.attributes.into_iter().collect()),
::yew::virtual_dom::Listeners::Pending(element.listeners.into_boxed_slice()),
::yew::virtual_dom::VList::with_children(element.children, ::std::option::Option::None),
).into()
}
}
};

tokens.extend(out);
}
}
75 changes: 75 additions & 0 deletions packages/yew-macro/src/typed_vdom/globals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use crate::typed_vdom::AttributePropDefinition;
use syn::parse_quote;

pub fn global_attributes() -> [AttributePropDefinition; 17] {
[
AttributePropDefinition::new(
parse_quote! { autocapitalize },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { contextmenu },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { contenteditable },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { slot },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { spellcheck },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { class },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { title },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { itemprop },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { accesskey },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { lang },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { id },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { translate },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { draggable },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { style },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { dir },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { tabindex },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
AttributePropDefinition::new(
parse_quote! { hidden },
parse_quote! { ::yew::virtual_dom::AttrValue },
),
]
}
60 changes: 60 additions & 0 deletions packages/yew-macro/src/typed_vdom/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Token, Type};

pub mod generate_element;
mod globals;

#[derive(Clone)]
pub struct AttributePropDefinition {
name: Ident,
ty: Type,
}

impl Parse for AttributePropDefinition {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
let _separator = input.parse::<Token![:]>();
let ty = input.parse()?;
Ok(Self { name, ty })
}
}

impl AttributePropDefinition {
pub fn new(name: Ident, ty: Type) -> Self {
Self { name, ty }
}
fn build_fields(&self) -> TokenStream {
let AttributePropDefinition { name, ty } = self;
quote! {
pub #name: ::std::option::Option::<#ty>,
}
}

fn _build_setter(&self) -> TokenStream {
let AttributePropDefinition { name, ty } = self;
quote! {
pub fn #name(&mut self, val: #ty) {
self.#name = ::std::option::Option::Some(val);
}
}
}

fn build_if_lets(&self) -> TokenStream {
let AttributePropDefinition { name, .. } = self;
if name == "children" || name == "key" || name == "node_ref" {
quote! {}
} else {
quote! {
if let Some(val) = self.#name {
attrs.insert(::std::stringify!(#name), val);
}
}
}
}
}

pub(crate) mod kw {
syn::custom_keyword!(props);
}
1 change: 1 addition & 0 deletions packages/yew/src/virtual_dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pub mod key;
#[doc(hidden)]
pub mod listeners;
mod typings;
#[doc(hidden)]
pub mod vcomp;
#[doc(hidden)]
Expand Down
54 changes: 54 additions & 0 deletions packages/yew/src/virtual_dom/typings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! This module contains the items required for a statically typed VDOM

use std::collections::HashMap;
use std::rc::Rc;

use yew_macro::generate_element;

use crate::virtual_dom::{AttrValue, Key, Listener, VNode};
use crate::{Children, NodeRef};

generate_element! {
button;
props: {
autofocus: AttrValue,
disabled: AttrValue,
form: AttrValue,
formaction: AttrValue,
formenctype: AttrValue,
formmethod: AttrValue,
formnovalidate: AttrValue,
formtarget: AttrValue,
name: AttrValue,
type_: AttrValue,
value: AttrValue,
node_ref: NodeRef,
key: Key,
children: Children,
}
}

/// Metadata of an HTML element
///
/// A [Component](crate::html::Component) is generated using this data for every element.
#[derive(Debug)]
pub struct ElementData {
node_ref: NodeRef,
attributes: HashMap<&'static str, AttrValue>,
listeners: Vec<Option<Rc<dyn Listener>>>,
key: Option<Key>,
children: Vec<VNode>,
}


#[cfg(test)]
mod tests {
use crate::html;
fn _compiles() {
use super::button as Btn;

let _ = html! {
<Btn disabled="true" class="fuck" />
};
}
}