Feat: dioxus frontend crate

This commit is contained in:
Jonathan Kelley committed 2021-01-20 12:04:27 -05:00
1 parent 6c1205e91b
commit 4d7ac5bb5d
13 files changed
+465 -15

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
[package]
name = "dioxus-core-macro"
version = "0.0.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.6"
quote = "1.0"
syn = { version = "1.0.11", features = ["full"] }
# testing
[dev-dependencies]
rustversion = "1.0"
trybuild = "1.0"
+211
View File
@@ -0,0 +1,211 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{
parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
};
struct FunctionComponent {
block: Box<Block>,
props_type: Box<Type>,
arg: FnArg,
vis: Visibility,
attrs: Vec<Attribute>,
name: Ident,
return_type: Box<Type>,
}
impl Parse for FunctionComponent {
fn parse(input: ParseStream) -> syn::Result<Self> {
let parsed: Item = input.parse()?;
match parsed {
Item::Fn(func) => {
let ItemFn {
attrs,
vis,
sig,
block,
} = func;
if !sig.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
sig.generics,
"function components can't contain generics",
));
}
if sig.asyncness.is_some() {
return Err(syn::Error::new_spanned(
sig.asyncness,
"function components can't be async",
));
}
if sig.constness.is_some() {
return Err(syn::Error::new_spanned(
sig.constness,
"const functions can't be function components",
));
}
if sig.abi.is_some() {
return Err(syn::Error::new_spanned(
sig.abi,
"extern functions can't be function components",
));
}
let return_type = match sig.output {
ReturnType::Default => {
return Err(syn::Error::new_spanned(
sig,
"function components must return `yew::Html`",
))
}
ReturnType::Type(_, ty) => ty,
};
let mut inputs = sig.inputs.into_iter();
let arg: FnArg = inputs
.next()
.unwrap_or_else(|| syn::parse_quote! { _: &() });
let ty = match &arg {
FnArg::Typed(arg) => match &*arg.ty {
Type::Reference(ty) => {
if ty.lifetime.is_some() {
return Err(syn::Error::new_spanned(
&ty.lifetime,
"reference must not have a lifetime",
));
}
if ty.mutability.is_some() {
return Err(syn::Error::new_spanned(
&ty.mutability,
"reference must not be mutable",
));
}
ty.elem.clone()
}
ty => {
let msg = format!(
"expected a reference to a `Properties` type (try: `&{}`)",
ty.to_token_stream()
);
return Err(syn::Error::new_spanned(ty, msg));
}
},
FnArg::Receiver(_) => {
return Err(syn::Error::new_spanned(
arg,
"function components can't accept a receiver",
));
}
};
// Checking after param parsing may make it a little inefficient
// but that's a requirement for better error messages in case of receivers
// `>0` because first one is already consumed.
if inputs.len() > 0 {
let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect();
return Err(syn::Error::new_spanned(
params,
"function components can accept at most one parameter for the props",
));
}
Ok(Self {
props_type: ty,
block,
arg,
vis,
attrs,
name: sig.ident,
return_type,
})
}
item => Err(syn::Error::new_spanned(
item,
"`function_component` attribute can only be applied to functions",
)),
}
}
}
struct FunctionComponentName {
component_name: Ident,
}
impl Parse for FunctionComponentName {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.is_empty() {
return Err(input.error("expected identifier for the component"));
}
let component_name = input.parse()?;
Ok(Self { component_name })
}
}
#[proc_macro_attribute]
pub fn function_component(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = parse_macro_input!(item as FunctionComponent);
let attr = parse_macro_input!(attr as FunctionComponentName);
function_component_impl(attr, item)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
fn function_component_impl(
name: FunctionComponentName,
component: FunctionComponent,
) -> syn::Result<TokenStream> {
let FunctionComponentName { component_name } = name;
let FunctionComponent {
block,
props_type,
arg,
vis,
attrs,
name: function_name,
return_type,
} = component;
if function_name == component_name {
return Err(syn::Error::new_spanned(
component_name,
"the component must not have the same name as the function",
));
}
let ret_type = quote_spanned!(return_type.span()=> ::yew::html::Html);
let quoted = quote! {
#[doc(hidden)]
#[allow(non_camel_case_types)]
#vis struct #function_name;
impl ::yew_functional::FunctionProvider for #function_name {
type TProps = #props_type;
fn run(#arg) -> #ret_type {
#block
}
}
#(#attrs)*
#vis type #component_name = ::yew_functional::FunctionComponent<#function_name>;
};
Ok(quoted)
}
+1 -1
View File
@@ -11,8 +11,8 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
[dependencies]
generational-arena = "0.2.8"
dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
once_cell = "1.5.2"
dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
# web-sys = "0.3.46"
+77 -12
View File
@@ -1,6 +1,47 @@
//! Dioxus: a concurrent, functional, virtual dom for any renderer in Rust
//! <div align="center">
//! <h1>🌗🚀 📦 Dioxus</h1>
//! <p>
//! <strong>A concurrent, functional, virtual DOM for Rust</strong>
//! </p>
//! </div>
//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
//!
//!
//! Dioxus is an efficient virtual dom implementation for building interactive user interfaces in Rust.
//! This crate aims to maintain a uniform hook-based, renderer-agnostic UI framework for cross-platform development.
//!
//! ## Components
//! The base unit of Dioxus is the `component`. Components can be easily created from just a function - no traits required:
//! ```
//! use dioxus_core::prelude::*;
//!
//! fn Example(ctx: Context<()>) -> VNode {
//! html! { <div> "Hello world!" </div> }
//! }
//! ```
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
//! and what properties can be used to specify it in the VNode output. All components in Dioxus are hook-based, which might be more
//! complex than other approaches that use traits + lifecycle events. Alternatively, we provide a "lifecycle hook" if you want more
//! granualar control with behavior similar to other UI frameworks.
//!
//! ## Hooks
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. Instead of
//! using a single struct to store data, hooks use the "use_hook" building block which allows the persistence of data between
//! function component renders.
//!
//! This allows functions to reuse stateful logic between components, simplify large complex components, and adopt more clear context
//! subscription patterns to make components easier to read.
//!
//! ## Supported Renderers
//! Instead of being tightly coupled to a platform, browser, or toolkit, Dioxus implements a VirtualDOM object which
//! can be consumed to draw the UI. The Dioxus VDOM is reactive and easily consumable by 3rd-party renderers via
//! the `Patch` object. See [Implementing a Renderer](docs/8-custom-renderer.md) and the `StringRenderer` classes for information
//! on how to implement your own custom renderer. We provide 1st-class support for these renderers:
//! - dioxus-desktop (via WebView)
//! - dioxus-web (via WebSys)
//! - dioxus-ssr (via StringRenderer)
//! - dioxus-liveview (SSR + StringRenderer)
//!
/// Re-export common types for ease of development use.
/// Essential when working with the html! macro
@@ -9,12 +50,12 @@
///
pub mod prelude {
use crate::nodes;
pub use crate::virtual_dom::Context;
pub use crate::virtual_dom::VirtualDom;
pub use crate::virtual_dom::{Context, VirtualDom, FC};
pub use nodes::iterables::IterableNodes;
pub use nodes::*;
// hack "VNode"
// TODO @Jon, fix this
// hack the VNode type until VirtualNode is fixed in the macro crate
pub type VirtualNode = VNode;
// Re-export from the macro crate
@@ -24,7 +65,6 @@ pub mod prelude {
/// The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
///
/// This module includes all life-cycle related mechanics, including the virtual dom, scopes, properties, and lifecycles.
///
pub mod virtual_dom {
use super::*;
use crate::nodes::VNode;
@@ -33,7 +73,12 @@ pub mod virtual_dom {
/// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom {
arena: Arena<Scope>,
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
components: Arena<Scope>,
/// Components generate lifecycle events
event_queue: Vec<LifecycleEvent>,
}
impl VirtualDom {
@@ -51,25 +96,41 @@ pub mod virtual_dom {
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
/// to toss out the entire tree.
pub fn new_with_props<T>(root: FC<T>) -> Self {
// Set a first lifecycle event to add the component
let first_event = LifecycleEvent::Add;
Self {
arena: Arena::new(),
components: Arena::new(),
event_queue: vec![first_event],
}
}
}
enum LifecycleEvent {
Add,
}
/// Functional Components leverage the type FC to
pub type FC<T> = fn(&mut Context<T>) -> VNode;
/// The Scope that wraps a functional component
/// Scope's hold subscription, context, and hook information, however, it is allocated on the heap.
pub struct Scope {}
pub struct Scope {
hook_idx: i32,
hooks: Vec<()>,
}
impl Scope {
fn new<T>() -> Self {
Self {}
Self {
hook_idx: 0,
hooks: vec![],
}
}
}
pub struct HookState {}
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
///
@@ -92,7 +153,12 @@ pub mod virtual_dom {
}
pub trait Properties {}
// Auto derive for pure components
impl Properties for () {}
// Set up a derive macro
// #[derive(Macro)]
}
/// Virtual Node Support
@@ -600,9 +666,8 @@ pub mod nodes {
}
}
///
///
///
/// The diffing algorithm to compare two VNode trees and generate a list of patches to update the VDom.
/// Currently, using an index-based patching algorithm
///
pub mod diff {
use super::*;
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "dioxus"
version = "0.1.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
"license" = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "0.1.0" }
+7
View File
@@ -0,0 +1,7 @@
# Dioxus
This crate serves as the thin frontend over dioxus-core and dioxus-app. Here, we support docs, feature flags, and incorporate some extra tools and utilities into a consistent API.
Ideally, we can prevent cfg creep into the core crate by moving it all into this frontend crate.
For now, see dioxus-core for all docs and details
+16
View File
@@ -0,0 +1,16 @@
pub mod prelude {
pub use dioxus_core::prelude::*;
}
// Re-export core completely
pub use dioxus_core as core;
struct App {}
fn new() -> App {
todo!()
}
struct Router {}
struct Route {}