Dodrio support with a bespoke macro.
This commit is contained in:
parent
dbb4ba8738
commit
813121b3a7
|
@ -4,5 +4,6 @@ members = [
|
|||
"macros",
|
||||
"examples/stdweb",
|
||||
"examples/rocket",
|
||||
"examples/dodrio",
|
||||
"ui",
|
||||
]
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
[package]
|
||||
name = "dodrio-counter"
|
||||
version = "0.1.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.6"
|
||||
console_log = "0.1.2"
|
||||
dodrio = "0.1.0"
|
||||
log = "0.4.6"
|
||||
wasm-bindgen = "0.2.38"
|
||||
typed-html = { path = "../../typed-html", features = ["dodrio_macro"] }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.15"
|
||||
features = [
|
||||
"console",
|
||||
"Document",
|
||||
"Event",
|
||||
"EventTarget",
|
||||
"HtmlElement",
|
||||
"MouseEvent",
|
||||
"Node",
|
||||
"Window",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.2.38"
|
|
@ -0,0 +1,21 @@
|
|||
# Counter
|
||||
|
||||
A counter that can be incremented and decremented.
|
||||
|
||||
## Source
|
||||
|
||||
See `src/lib.rs`.
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
wasm-pack build --target no-modules
|
||||
```
|
||||
|
||||
## Serve
|
||||
|
||||
Use any HTTP server, for example:
|
||||
|
||||
```
|
||||
python -m SimpleHTTPServer
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
||||
<title>Counter</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="pkg/dodrio_counter.js"></script>
|
||||
<script>
|
||||
wasm_bindgen("pkg/dodrio_counter_bg.wasm");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,86 @@
|
|||
#![recursion_limit = "128"]
|
||||
|
||||
use dodrio::builder::text;
|
||||
use dodrio::bumpalo::{self, Bump};
|
||||
use dodrio::Render;
|
||||
use log::*;
|
||||
use typed_html::dodrio;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A counter that can be incremented and decrmented!
|
||||
struct Counter {
|
||||
count: isize,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
/// Construct a new, zeroed counter.
|
||||
fn new() -> Counter {
|
||||
Counter { count: 0 }
|
||||
}
|
||||
|
||||
/// Increment this counter's count.
|
||||
fn increment(&mut self) {
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
/// Decrement this counter's count.
|
||||
fn decrement(&mut self) {
|
||||
self.count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The `Render` implementation for `Counter`s displays the current count and has
|
||||
// buttons to increment and decrement the count.
|
||||
impl Render for Counter {
|
||||
fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> dodrio::Node<'bump>
|
||||
where
|
||||
'a: 'bump,
|
||||
{
|
||||
// Stringify the count as a bump-allocated string.
|
||||
let count = bumpalo::format!(in bump, "{}", self.count);
|
||||
|
||||
dodrio!(bump,
|
||||
<div>
|
||||
<button onclick={|root, vdom, _event| {
|
||||
// Cast the root render component to a `Counter`, since
|
||||
// we know that's what it is.
|
||||
let counter = root.unwrap_mut::<Counter>();
|
||||
|
||||
// Increment the counter.
|
||||
counter.increment();
|
||||
|
||||
// Since the count has updated, we should re-render the
|
||||
// counter on the next animation frame.
|
||||
vdom.schedule_render();
|
||||
}}>"+"</button>
|
||||
{ text(count.into_bump_str()) }
|
||||
<button onclick={|root, vdom, _event| {
|
||||
// Same as above, but decrementing instead of incrementing.
|
||||
root.unwrap_mut::<Counter>().decrement();
|
||||
vdom.schedule_render();
|
||||
}}>"-"</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn run() {
|
||||
// Initialize debug logging for if/when things go wrong.
|
||||
console_error_panic_hook::set_once();
|
||||
console_log::init_with_level(Level::Trace).expect("should initialize logging OK");
|
||||
|
||||
// Get the document's `<body>`.
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
|
||||
// Construct a new counter component.
|
||||
let counter = Counter::new();
|
||||
|
||||
// Mount our counter component to the `<body>`.
|
||||
let vdom = dodrio::Vdom::new(&body, counter);
|
||||
|
||||
// Run the virtual DOM and its listeners forever.
|
||||
vdom.forget();
|
||||
}
|
|
@ -25,3 +25,6 @@ quote = "0.6.10"
|
|||
[build-dependencies]
|
||||
lalrpop = "0.16.1"
|
||||
version_check = "0.1.5"
|
||||
|
||||
[features]
|
||||
dodrio = []
|
||||
|
|
|
@ -174,6 +174,10 @@ pub NodeWithType: (Node, Option<Vec<Token>>) = {
|
|||
},
|
||||
};
|
||||
|
||||
pub NodeWithBump: (Ident, Node) = {
|
||||
<Ident> "," <Node>,
|
||||
};
|
||||
|
||||
|
||||
// The declare macro
|
||||
|
||||
|
|
|
@ -60,6 +60,26 @@ impl Node {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_dodrio_token_stream(
|
||||
self,
|
||||
bump: &Ident,
|
||||
is_req_child: bool,
|
||||
) -> Result<TokenStream, TokenStream> {
|
||||
match self {
|
||||
Node::Element(el) => el.into_dodrio_token_stream(bump, is_req_child),
|
||||
Node::Text(text) => {
|
||||
let text = TokenTree::Literal(text);
|
||||
Ok(quote!(dodrio::builder::text(#text)))
|
||||
}
|
||||
Node::Block(group) => {
|
||||
let group: TokenTree = group.into();
|
||||
Ok(quote!(
|
||||
#group
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -119,6 +139,15 @@ fn is_string_literal(literal: &Literal) -> bool {
|
|||
literal.to_string().starts_with('"')
|
||||
}
|
||||
|
||||
fn stringify_ident(ident: &Ident) -> String {
|
||||
let s = ident.to_string();
|
||||
if s.starts_with("r#") {
|
||||
s[2..].to_string()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl Element {
|
||||
fn into_token_stream(mut self, ty: &Option<Vec<Token>>) -> Result<TokenStream, TokenStream> {
|
||||
let name = self.name;
|
||||
|
@ -249,9 +278,184 @@ impl Element {
|
|||
}
|
||||
))
|
||||
}
|
||||
|
||||
fn into_dodrio_token_stream(
|
||||
mut self,
|
||||
bump: &Ident,
|
||||
is_req_child: bool,
|
||||
) -> Result<TokenStream, TokenStream> {
|
||||
let name = self.name;
|
||||
let name_str = stringify_ident(&name);
|
||||
let typename: TokenTree = Ident::new(&name_str, name.span()).into();
|
||||
let tag_name = TokenTree::from(Literal::string(&name_str));
|
||||
let req_names = required_children(&name_str);
|
||||
if req_names.len() > self.children.len() {
|
||||
let span = name.span();
|
||||
let error = format!(
|
||||
"<{}> requires {} children but there are only {}",
|
||||
name_str,
|
||||
req_names.len(),
|
||||
self.children.len()
|
||||
);
|
||||
return Err(quote_spanned! {span=>
|
||||
compile_error! { #error }
|
||||
});
|
||||
}
|
||||
let events = extract_event_handlers(&mut self.attributes);
|
||||
let data_attrs = extract_data_attrs(&mut self.attributes);
|
||||
let attrs = self.attributes.iter().map(|(key, value)| {
|
||||
(
|
||||
key.to_string(),
|
||||
TokenTree::Ident(ident::new_raw(&key.to_string(), key.span())),
|
||||
value,
|
||||
)
|
||||
});
|
||||
let opt_children = self
|
||||
.children
|
||||
.split_off(req_names.len())
|
||||
.into_iter()
|
||||
.map(|node| node.into_dodrio_token_stream(bump, false))
|
||||
.collect::<Result<Vec<TokenStream>, TokenStream>>()?;
|
||||
let req_children = self
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|node| node.into_dodrio_token_stream(bump, true))
|
||||
.collect::<Result<Vec<TokenStream>, TokenStream>>()?;
|
||||
|
||||
let mut set_attrs = TokenStream::new();
|
||||
|
||||
for (attr_str, key, value) in attrs {
|
||||
match value {
|
||||
TokenTree::Literal(lit) if is_string_literal(lit) => {
|
||||
let mut eprintln_msg = "ERROR: ".to_owned();
|
||||
#[cfg(can_show_location_of_runtime_parse_error)]
|
||||
{
|
||||
let span = lit.span();
|
||||
eprintln_msg += &format!(
|
||||
"{}:{}:{}: ",
|
||||
span.unstable()
|
||||
.source_file()
|
||||
.path()
|
||||
.to_str()
|
||||
.unwrap_or("unknown"),
|
||||
span.unstable().start().line,
|
||||
span.unstable().start().column
|
||||
);
|
||||
}
|
||||
eprintln_msg += &format!(
|
||||
"<{} {}={}> failed to parse attribute value: {{}}",
|
||||
name_str, attr_str, lit,
|
||||
);
|
||||
#[cfg(not(can_show_location_of_runtime_parse_error))]
|
||||
{
|
||||
eprintln_msg += "\nERROR: rebuild with nightly to print source location";
|
||||
}
|
||||
|
||||
set_attrs.extend(quote!(
|
||||
element.attrs.#key = Some(#lit.parse().unwrap_or_else(|err| {
|
||||
eprintln!(#eprintln_msg, err);
|
||||
panic!("failed to parse string literal");
|
||||
}));
|
||||
));
|
||||
}
|
||||
value => {
|
||||
let value = process_value(value);
|
||||
set_attrs.extend(quote!(
|
||||
element.attrs.#key = Some(std::convert::Into::into(#value));
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut builder = TokenStream::new();
|
||||
builder.extend(quote!(
|
||||
dodrio::builder::ElementBuilder::new(#bump, #tag_name)
|
||||
));
|
||||
|
||||
for (key, _) in self.attributes.iter() {
|
||||
let key_str = TokenTree::from(Literal::string(&stringify_ident(key)));
|
||||
builder.extend(quote!(
|
||||
.attr(#key_str, dodrio::bumpalo::format!(in &#bump, "{}",
|
||||
element.attrs.#key.unwrap()).into_bump_str())
|
||||
));
|
||||
}
|
||||
|
||||
for (key, value) in data_attrs
|
||||
.iter()
|
||||
.map(|(k, v)| (TokenTree::from(Literal::string(&k)), v.clone()))
|
||||
{
|
||||
builder.extend(quote!(
|
||||
.attr(#key, #value.into())
|
||||
));
|
||||
}
|
||||
|
||||
for (key, value) in events.iter() {
|
||||
let key = TokenTree::from(Literal::string(&stringify_ident(key)));
|
||||
let value = process_value(value);
|
||||
builder.extend(quote!(
|
||||
.on(#key, #value)
|
||||
));
|
||||
}
|
||||
|
||||
let mut make_req_children = TokenStream::new();
|
||||
let mut arg_list = Vec::new();
|
||||
let mut req_nodes = Vec::new();
|
||||
for (index, child) in req_children.into_iter().enumerate() {
|
||||
let req_child = TokenTree::from(Ident::new(
|
||||
&format!("req_child_{}", index),
|
||||
Span::call_site(),
|
||||
));
|
||||
let child_node = TokenTree::from(Ident::new(
|
||||
&format!("child_node_{}", index),
|
||||
Span::call_site(),
|
||||
));
|
||||
make_req_children.extend(quote!(
|
||||
let (#req_child, #child_node) = #child;
|
||||
));
|
||||
builder.extend(quote!(
|
||||
.child(#child_node)
|
||||
));
|
||||
arg_list.push(req_child);
|
||||
req_nodes.push(child_node);
|
||||
}
|
||||
|
||||
for child in opt_children {
|
||||
builder.extend(quote!(
|
||||
.child(#child)
|
||||
));
|
||||
}
|
||||
|
||||
builder.extend(quote!(
|
||||
.finish()
|
||||
));
|
||||
|
||||
if is_req_child {
|
||||
builder = quote!(
|
||||
(element, #builder)
|
||||
);
|
||||
}
|
||||
|
||||
let mut args = TokenStream::new();
|
||||
for arg in arg_list {
|
||||
args.extend(quote!( #arg, ));
|
||||
}
|
||||
|
||||
Ok(quote!(
|
||||
{
|
||||
#make_req_children
|
||||
let mut element: typed_html::elements::#typename<typed_html::output::dodrio::Dodrio> = typed_html::elements::#typename::new(#args);
|
||||
#set_attrs
|
||||
#builder
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME report a decent error when the macro contains multiple top level elements
|
||||
pub fn expand_html(input: &[Token]) -> Result<(Node, Option<Vec<Token>>), ParseError> {
|
||||
grammar::NodeWithTypeParser::new().parse(Lexer::new(input))
|
||||
}
|
||||
|
||||
pub fn expand_dodrio(input: &[Token]) -> Result<(Ident, Node), ParseError> {
|
||||
grammar::NodeWithBumpParser::new().parse(Lexer::new(input))
|
||||
}
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
#![recursion_limit = "128"]
|
||||
#![cfg_attr(can_show_location_of_runtime_parse_error, feature(proc_macro_span))]
|
||||
|
||||
extern crate ansi_term;
|
||||
extern crate lalrpop_util;
|
||||
extern crate proc_macro;
|
||||
extern crate proc_macro2;
|
||||
extern crate proc_macro_hack;
|
||||
extern crate quote;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_hack::proc_macro_hack;
|
||||
|
@ -39,6 +34,25 @@ pub fn html(input: TokenStream) -> TokenStream {
|
|||
})
|
||||
}
|
||||
|
||||
/// Construct a Dodrio node.
|
||||
///
|
||||
/// See the crate documentation for [`typed_html`][typed_html].
|
||||
///
|
||||
/// [typed_html]: ../typed_html/index.html
|
||||
#[cfg(feature = "dodrio")]
|
||||
#[proc_macro_hack]
|
||||
pub fn dodrio(input: TokenStream) -> TokenStream {
|
||||
let stream = lexer::unroll_stream(input.into(), false);
|
||||
let result = html::expand_dodrio(&stream);
|
||||
TokenStream::from(match result {
|
||||
Err(err) => error::parse_error(&stream, &err),
|
||||
Ok((bump, node)) => match node.into_dodrio_token_stream(&bump, false) {
|
||||
Err(err) => err,
|
||||
Ok(success) => success,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// This macro is used by `typed_html` internally to generate types and
|
||||
/// implementations for HTML elements.
|
||||
#[proc_macro]
|
||||
|
|
|
@ -25,3 +25,8 @@ htmlescape = "0.3.1"
|
|||
proc-macro-hack = "0.5.4"
|
||||
proc-macro-nested = "0.1.3"
|
||||
stdweb = { version = "0.4.14", optional = true }
|
||||
dodrio = { version = "0.1.0", optional = true }
|
||||
web-sys = { version = "0.3.16", optional = true, features = ["Event", "Element"] }
|
||||
|
||||
[features]
|
||||
dodrio_macro = ["web-sys", "dodrio", "typed-html-macros/dodrio"]
|
||||
|
|
|
@ -199,6 +199,10 @@ use std::fmt::Display;
|
|||
#[proc_macro_hack(support_nested)]
|
||||
pub use typed_html_macros::html;
|
||||
|
||||
#[cfg(feature = "dodrio_macro")]
|
||||
#[proc_macro_hack(support_nested)]
|
||||
pub use typed_html_macros::dodrio;
|
||||
|
||||
pub mod dom;
|
||||
pub mod elements;
|
||||
pub mod events;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use std::fmt::{Display, Error, Formatter};
|
||||
|
||||
use crate::OutputType;
|
||||
|
||||
/// DOM output using the Dodrio virtual DOM
|
||||
pub struct Dodrio;
|
||||
impl OutputType for Dodrio {
|
||||
type Events = Events;
|
||||
type EventTarget = ();
|
||||
type EventListenerHandle = ();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Events;
|
||||
|
||||
impl Display for Events {
|
||||
fn fmt(&self, _: &mut Formatter) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
|
@ -1,2 +1,4 @@
|
|||
#[cfg(feature = "stdweb")]
|
||||
pub mod stdweb;
|
||||
#[cfg(feature = "dodrio_macro")]
|
||||
pub mod dodrio;
|
||||
|
|
Loading…
Reference in New Issue