Asteracea is a web application framework aiming to combine the strengths of Angular and React while fully supporting Rust's lifetime model.
Note: Asteracea is experimental software.
While it appears to work well so far, there likely will be breaking changes to the template syntax.
Installation
Please use cargo-edit to always add the latest version of this library:
cargo add asteracea
Design goals
-
Little boilerplate / Useful defaults
Most generated boilerplate code is adjusted automatically to what is required. For example, the signature of a component's
.rendermethod changes if aNodeis generated.There is still room for improvement here without sacrificing readability.
-
Co-location / DRY
Intent shouldn't need to be reiterated in multiple places (split declaration, initialisation and usage).
For now, short form captures nested in the component templates provide a way to centralise some semantics (similarly to React's Hooks but without their control flow limitations).
-
Robust code
Element names are statically checked against
lignin-schemaby default, but other schemata can be defined similarly. Empty elements like<br>cannot contain children.Similar checks for attributes and event names are planned.
-
No default runtime
Asteracea components compile to plain Rust code with few dependencies, which helps keep bundles small.
Use
lignin-domorlignin-htmlto transform renderedNodetrees into live user interfaces.
Examples
Additional examples can be found in the examples directory.
Empty component
The most simple (Node-rendering) component can be written like this:
asteracea::component! { pub Empty()() -> Sync [] // Empty node sequence } // Render into a bump allocator: // This is generally only this explicit at the application root. let mut bump = bumpalo::Bump::new(); let root = { struct Root; rhizome::sync::Node::new(core::any::TypeId::of::<Root>()) }; assert!(matches!( Box::pin(Empty::new(root.as_ref(), Empty::new_args_builder().build()).unwrap()) .as_ref() .render(&mut bump, Empty::render_args_builder().build()) .unwrap(), lignin::Node::Multi(&[]) // Empty node sequence ));
VDOM Sync-ness can be inferred (even transitively) at zero runtime cost by omitting -> Sync (or -> !Sync), except for components visible outside their crate.
Unit component
A return type other than Node can be specified after the render argument list:
asteracea::component! { Unit(/* ::new arguments */)(/* .render arguments */) -> () {} // Empty Rust block } asteracea::component! { Offset(base: usize)(offset: usize) -> usize let pub self.base: usize = base; // ² { self.base + offset } } // This is generally only this explicit at the application root. let mut bump = bumpalo::Bump::new(); let root = { struct Root; rhizome::sync::Node::new(core::any::TypeId::of::<Root>()) }; assert_eq!( Box::pin(Unit::new(root.as_ref(), Unit::new_args_builder().build()).unwrap()) .as_ref() .render(&mut bump, Unit::render_args_builder().build()) .unwrap(), (), ); assert_eq!( Box::pin(Offset::new(root.as_ref(), Offset::new_args_builder().base(2).build()).unwrap()) .as_ref() .render(&mut bump, Offset::render_args_builder().offset(3).build()) .unwrap(), 5, );
² #2
Counter component
For a relatively complex example, see this parametrised counter: