Introducing Ruukh Framework
source link: https://www.tuicool.com/articles/hit/FryEBfz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rust has its goals set on to be a primary WASM language and it would be awesome to use it both in backend and frontend web. Ruukh is one of such efforts to realise that dream. Ruukh, a frontend web framework, is inspired by both VueJS and ReactJS .
So, what does it look like?
#![feature(proc_macro_gen, proc_macro_non_items, decl_macro)] use wasm_bindgen::prelude::*; use ruukh::prelude::*; #[component] #[derive(Lifecycle)] struct MyApp; impl Render for MyApp { fn render(&self) -> Markup<Self> { html! { "Hello World!" } } } #[wasm_bindgen] pub fn run() { App::<MyApp>::new().mount("app"); }
This snippet as you would infer requires the latest rust nightly with Rust2018edition to run. The run
function is a special function in this context as it is exposed to JS
environment to be called after WASM intialization. Currently binary packages
are unexecutable using wasm-bindgen
. We’ll talk about how to setup a project
to run this web app later.
Now, let’s discuss what every bit of code does here.
#![feature(proc_macro_gen, proc_macro_non_items, decl_macro)]
The project heavily depends on the proc-macro
features as well as decl_macro
feature to make this framework work.
#[component] #[derive(Lifecycle)] struct MyApp;
MyApp
is a component struct onto which #[component]
is marked. This
attribute prepares the struct into a working implementation of a component.
At a higher level understanding, it provides a reactivity mechanism to the
component as well as implement Component
trait on the struct. #[derive(Lifecycle)]
implements Lifecycle
trait which provides methods
which are invoked when each of the lifecycles states are completed. You may
provide a custom implementation for the Lifecycle
if you desire so.
impl Render for MyApp { fn render(&self) -> Markup<Self> { html! { "Hello World!" } } }
View is rendered using Render
trait where a markup generated by html!
macro.
#[wasm_bindgen] pub fn run() { App::<MyApp>::new().mount("app"); }
Creates a new app with MyApp
mounted on element with id="app"
.
That was a simple overview, now lets incorporate some state to the app.
#[component] #[derive(Lifecycle)] struct MyApp { #[state] count: i32 } impl Render for MyApp { fn render(&self) -> Markup<Self> { html! { "The count is "{ self.count }"." <button @click={|this: &Self, _event| { this.set_state(|state| { state.count += 1; }); }}>"Increment"</button> } } }
To mark a struct field as a state, just mark it with #[state]
attribute. The
initial value of the state is Default::default()
. To override the default
value provided by Default
implementation just provide #[state(default = 5)]
or some other value.
Looking at the html!
markup, you may find three important bits:
First.
"The count is "{ self.count }"."
{ self.count }
is any rust expression which resolves to any type that is
convertible to Markup
.
Second.
<button @click={|this: &Self, _event| { ... }}>Increment</button>
Button is listening to click event with an event handler passed to it. You may
pass event listeners with @
prepending to the event name. Also, mind the
fact that the event handler signature is Fn(&self, event: Event)
.
Third.
this.set_state(|state| { state.count += 1; });
To mutate state, call self.set_state
with a closure which does all the
changes required. The component intelligently reacts to the state changes when
the state actually
changes i.e. state.count = state.count
does not
do anything.
Till now everything was a single component, now lets look at other examples to get the full benefits of a component centered framework.
#[component] #[derive(Lifecycle)] struct MyApp; impl Render for MyApp { fn render(&self) -> Markup<Self> { html! { <div> <Button text={"Save"} @save-all={Self::save_all} ></Button> </div> } } } // MyApp::save_all implementation #[component] #[derive(Lifecycle)] #[events( #[optional] fn save_all(&self, event: Event); )] struct Button { text: &'static str } impl Render for Button { fn render(&self) -> Markup<Self> { html! { <button style={"background-color: white;"} @click={Self::save_all} >{ self.text } </button> } } }
Now, we need to disect the code. The first of the tidbits:
#[events( #[optional] fn save_all(&self, event: Event); )]
This declares that the component Button
accepts the given events. The event
signature requires that the first argument be &self
. You also might note that
the event itself is marked with #[optional]
. This is to declare that the
event is optional to pass an event handler. If you require the event to be
absolutely required then omit this attribute.
struct Button { text: &'static str }
The text
field is a prop field for this component. Any field with no or #[prop]
attribute is a prop field. You may also make it optional by using
either Option<T>
or using a default value #[prop(default = val)]
.
<Button text={"Save"} @save-all={Self::save_all} ></Button>
The usage of this component is done using the component name in a
non-self-closing tag. The props and events are passed in a kebab-cased fashion.
Event names are likewise preceded by @
as always.
Currently, there is no way to pass children to the component . So you will have to wait out on that. Likewise, this initial release is really unstable and will have breaking changes between minor releases as I would like to experiment more on the design. Also, I cannot make a promise to ever stable-y release this project as I would want it to be full proof before I make a long term commitment. So, this project has to be taken as study and experimentation in the areas of frontend development in Rust.
Along with the framework,
cargo-ruukh
subcommand is provided to ease app building and running. So before you start
hacking on examples
provided, I would recommend you to install the CLI first with cargo install cargo-ruukh
. Happy Hacking!
The latest rust nightly by default creates a Rust2018 edition project.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK