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

Upstream yewtil crate #1119

Merged
merged 6 commits into from Apr 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.toml
Expand Up @@ -13,6 +13,19 @@ members = [
"yew-router/examples/router_component",
"yew-router/examples/switch",

# Utilities
"yewtil",
"yewtil-macro",
"yewtil/examples/pure_component",
# "yewtil/examples/dsl",
"yewtil/examples/lrc",
"yewtil/examples/history",
"yewtil/examples/mrc_irc",
"yewtil/examples/effect",
"yewtil/examples/fetch",
"yewtil/examples/futures",
"yewtil/examples/function_component",

# Examples
"examples/counter",
"examples/crm",
Expand Down
15 changes: 15 additions & 0 deletions yewtil-macro/Cargo.toml
@@ -0,0 +1,15 @@
[package]
name = "yewtil-macro"
version = "0.1.0"
authors = ["Henry Zimmerman <zimhen7@gmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"
description = "Macros to be re-exported from the yewtil crate"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0.6"
quote = "1.0.2"
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
194 changes: 194 additions & 0 deletions yewtil-macro/src/function_component.rs
@@ -0,0 +1,194 @@
use proc_macro::TokenStream as TokenStream1;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::export::ToTokens;
use syn::parse::{Parse, ParseBuffer};
use syn::parse_macro_input;
use syn::punctuated::Punctuated;
use syn::token;
use syn::Token;
use syn::{braced, parenthesized};
use syn::{Block, Error, Field, Stmt, Type, VisPublic, Visibility};

pub fn function_component_handler(attr: TokenStream, item: TokenStream1) -> TokenStream1 {
let component_name = attr.to_string();
assert!(
!component_name.is_empty(),
"you must provide a component name. eg: function_component(MyComponent)"
);
let component_name = Ident::new(&component_name, Span::call_site());
let function = parse_macro_input!(item as Function);
TokenStream1::from(
FunctionComponentInfo {
component_name,
function,
}
.to_token_stream(),
)
}

pub struct FunctionComponentInfo {
component_name: Ident,
function: Function,
}

// TODO, support type parameters

pub struct Function {
pub vis: Visibility,
pub fn_token: Token![fn],
pub name: Ident,
pub paren_token: token::Paren,
pub fields: Punctuated<Field, Token![,]>,
pub returns_token: Token![->],
pub return_ty: Ident,
pub brace_token: token::Brace,
pub body: Vec<Stmt>,
}

impl Parse for Function {
fn parse(input: &ParseBuffer) -> Result<Self, Error> {
let vis = input.parse()?;
let fn_token = input.parse()?;
let name = input.parse()?;
let content;
let paren_token = parenthesized!(content in input);
let returns_token = input.parse()?;
let return_ty = input.parse()?;
let content2;
let brace_token = braced!(content2 in input);
Ok(Function {
vis,
fn_token,
name,
paren_token,
fields: content.parse_terminated(Field::parse_named)?,
returns_token,
return_ty,
brace_token,
body: content2.call(Block::parse_within)?,
})
}
}

impl ToTokens for Function {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Function {
vis,
fn_token,
name,
fields,
returns_token,
return_ty,
body,
..
} = self;
let fields = fields
.iter()
.map(|field: &Field| {
let mut new_field: Field = field.clone();
new_field.attrs = vec![];
new_field
})
.collect::<Punctuated<_, Token![,]>>();

tokens.extend(quote! {
#vis #fn_token #name(#fields) #returns_token #return_ty {
#(#body)*
}
})
}
}

impl ToTokens for FunctionComponentInfo {
fn to_tokens(&self, tokens: &mut TokenStream) {
let FunctionComponentInfo {
component_name,
function,
} = self;
// The function tokens must be re-generated in order to strip the attributes that are not allowed.
let function_token_stream = function.to_token_stream();
let Function {
vis, name, fields, ..
} = function;

let impl_name = format!("FuncComp{}", component_name.to_string());
let impl_name = Ident::new(&impl_name, Span::call_site());

let alias = quote! {
#vis type #component_name = ::yewtil::Pure<#impl_name>;
};

// Set the fields to be public and strips references as necessary.
// This will preserve attributes like #[props(required)], which will appear in the generated struct below.
let new_fields = fields
.iter()
.map(|field: &Field| {
let mut new_field: Field = field.clone();
let visibility = Visibility::Public(VisPublic {
pub_token: syn::token::Pub {
span: Span::call_site(),
},
});
// Strip references so the component can have a static lifetime.
// TODO Handle 'static lifetimes gracefully here - allowing &'static strings instead of erroneously converting them to plain strs.
let ty = match &field.ty {
Type::Reference(x) => {
let elem = x.elem.clone();
Type::Verbatim(quote! {
#elem
})
}
x => x.clone(),
};
new_field.vis = visibility;
new_field.ty = ty;
new_field
})
.collect::<Punctuated<_, Token![,]>>();

let component_struct = quote! {
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
#vis struct #impl_name {
#new_fields
}
};

let arguments = fields
.iter()
.zip(new_fields.iter())
.map(|(field, new_field): (&Field, &Field)| {
let field_name = field.ident.as_ref().expect("Field must have name");

// If the fields differ, then a reference was removed from the function's field's type
// to make it static.
// Otherwise it is assumed that the type is not a reference on the function and it
// implements clone, and that when calling the function, the type should be cloned again.
if field.ty != new_field.ty {
quote! {
&self.#field_name
}
} else {
quote! {
self.#field_name.clone()
}
}
})
.collect::<Punctuated<_, Token![,]>>();

let pure_component_impl = quote! {
impl ::yewtil::PureComponent for #impl_name {
fn render(&self) -> ::yew::Html {
#name(#arguments)
}
}
};

tokens.extend(quote! {
#function_token_stream
#alias
#component_struct
#pure_component_impl
})
}
}
10 changes: 10 additions & 0 deletions yewtil-macro/src/lib.rs
@@ -0,0 +1,10 @@
extern crate proc_macro;
use proc_macro::TokenStream;

use crate::function_component::function_component_handler;

mod function_component;
#[proc_macro_attribute]
pub fn function_component(attr: TokenStream, item: TokenStream) -> TokenStream {
function_component_handler(attr.into(), item)
}
20 changes: 20 additions & 0 deletions yewtil/CHANGELOG.md
@@ -0,0 +1,20 @@
# Changelog

<!-- START TEMPLATE

## ✨ **VERSION** *(DATE)*

- #### ⚡️ Features
- Sample
- #### 🛠 Fixes
- Sample
- #### 🚨 Breaking changes
- Sample

END TEMPLATE-->

## ✨ **v0.2.0** *11/18/19*
- #### ⚡️ Features
- Add new `FetchRequest` trait, `fetch_resource()` function, and `FetchState` enum
to simplify making fetch requests using futures.
- Add `Default` implementations to `Irc` and `Mrc`.
68 changes: 68 additions & 0 deletions yewtil/Cargo.toml
@@ -0,0 +1,68 @@
[package]
name = "yewtil"
version = "0.2.0"
authors = ["Henry Zimmerman <zimhen7@gmail.com>"]
edition = "2018"
description = "Utility crate for Yew"
license = "MIT/Apache-2.0"
repository = "https://github.com/yewstack/yewtil"
readme = "Readme.md"

[features]
default = ["stable"] # Only stable is included by default.
all = ["stable", "experimental"]

# Broad features
## All features MUST be stable or experimental
stable = ["neq", "pure", "history", "mrc_irc", "effect", "future"]
experimental = ["dsl", "lrc", "with_callback", "fetch" ]

# Some pointers are stable, some experimental.
# This makes sure you get all the pointers
ptr = ["lrc", "mrc_irc"]

# Misc features
neq = []
pure = ["neq", "yewtil-macro"]
with_callback = []
history = []
dsl = []
effect = []
fetch = ["serde", "serde_json", "neq", "future"]
future = ["wasm-bindgen-futures", "wasm-bindgen", "stdweb", "futures", "web-sys"]

# Ptr features
lrc = []
mrc_irc = []

[dependencies]
futures = {version = "0.3.1", optional = true}
log = "0.4.8"
serde = {version= "1.0.102", optional = true}
serde_json = { version = "1.0.41", optional = true }
wasm-bindgen = {version = "0.2.51", features=["serde-serialize"], optional = true}
wasm-bindgen-futures = {version = "0.4.3", optional = true}
yew = { path = "../yew" }
yewtil-macro = { path = "../yewtil-macro", optional = true }

[dependencies.stdweb]
version = "0.4.20"
optional = true
features = [
"futures-support",
"experimental_features_which_may_break_on_minor_version_bumps",
]

[dependencies.web-sys]
version = "0.3.31"
optional = true
features = [
'Headers',
'Request',
'RequestInit',
'RequestMode',
'Response',
'Window',
'Location',
'Storage',
]