Skip to content

Commit

Permalink
Merge pull request #29 from maciejhirsz/docs
Browse files Browse the repository at this point in the history
Update docs
  • Loading branch information
maciejhirsz authored Mar 22, 2023
2 parents e1ef8ae + 93ce44e commit 48e28a9
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 54 deletions.
44 changes: 20 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,24 @@

_Easy declarative web interfaces._

**Kobold** uses macros to deliver familiar HTML-esque syntax for building declarative web interfaces,
**Kobold** uses macros to deliver familiar JSX-esque syntax for building declarative web interfaces,
while leveraging Rust's powerful type system for safety and performance.

### Zero-cost static HTML
### Zero-Cost Static HTML

Like in [React](https://reactjs.org/) or [Yew](https://yew.rs/) updates are done by repeating calls
to a render function whenever the state changes. However, unlike either, **Kobold** does not produce a
full blown [virtual DOM](https://en.wikipedia.org/wiki/Virtual_DOM). Instead the `view!` macro compiles
all static [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) elements to a single
JavaScript function that constructs them.
The [`view!`](view) macro produces opaque `impl View` types that by default do no allocations.
All static [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) elements compile to
inline JavaScript code that constructs them. Expressions are injected into the constructed DOM on first render.
Kobold keeps track of the DOM node references for these expressions.

All expressions, which must implement the `View` trait, are injected into the constructed DOM on first
render. Kobold keeps track of the DOM node references for these expressions. Since the exact types the
expressions evaluate to are known to the Rust compiler, update calls can diff them by value and surgically
update the DOM should they change. Changing a string or an integer only updates the exact
[`Text` node](https://developer.mozilla.org/en-US/docs/Web/API/Text) that string or integer was rendered to.
Since the exact types the expressions evaluate to are known to the Rust compiler, update calls can diff them by
value (or pointer) and surgically update the DOM should they change. Changing a
string or an integer only updates the exact [`Text` node](https://developer.mozilla.org/en-US/docs/Web/API/Text)
that string or integer was rendered to.

_If the `view!` macro invocation contains HTML elements with no expressions, the constructed `View`
_If the `view!` macro invocation contains DOM elements with no expressions, the constructed `View`
type will be zero-sized, and its `View::update` method will be empty, making updates of static
HTML quite literally zero-cost._
DOM literally zero-cost._

### Hello World!

Expand All @@ -46,9 +44,8 @@ fn main() {
}
```

The _render function_ must return a type that implements the `View` trait. Since the `view!` macro
produces _transient types_, or [_Voldemort types_](https://wiki.dlang.org/Voldemort_types), the best approach
here is to always use the `impl View` return type.
The component function must return a type that implements the `View` trait. Since the `view!` macro
produces transient locally defined types the best approach here is to always use the opaque `impl View` return type.

Everything here is statically typed and the macro doesn't delete any information when manipulating the
token stream, so the Rust compiler can tell you when you've made a mistake:
Expand Down Expand Up @@ -82,7 +79,7 @@ fn Counter(init: u32) -> impl View {

view! {
<p>
"You clicked on the "
"You clicked the "
// `{onclick}` here is shorthand for `onclick={onclick}`
<button {onclick}>"Button"</button>
" "{ count }" times."
Expand Down Expand Up @@ -110,7 +107,7 @@ underlying state.

For more details visit the [`stateful` module documentation](https://docs.rs/kobold/latest/kobold/stateful/index.html).

### Conditional rendering
### Conditional Rendering

Because the `view!` macro produces unique transient types, `if` and `match` expressions that invoke
the macro will naturally fail to compile.
Expand All @@ -119,7 +116,6 @@ Using the `auto_branch` flag on the `#[component]` attribute
**Kobold** will scan the body of of your component render function, and make all `view!` macro invocations
inside an `if` or `match` expression, and wrap them in an enum making them the same type:


```rust
#[component(auto_branch)]
fn Conditional(illuminatus: bool) -> impl View {
Expand Down Expand Up @@ -161,7 +157,7 @@ On updates the iterator is consumed once and all items are diffed with the previ
No allocations are made by **Kobold** when updating such a list, unless the rendered list needs
to grow past its original capacity.

### Borrowed values
### Borrowed Values

`View` types are truly transient and only need to live for the duration of the initial render,
or for the duration of the subsequent update. This means that you can easily and cheaply render borrowed
Expand All @@ -183,7 +179,7 @@ fn Users<'a>(names: &'a [&'a str]) -> impl View + 'a {
}
```

### Components with children
### Components with Children

If you wish to capture children from parent `view!` invocation, simply change
`#[component]` to `#[component(children)]`:
Expand All @@ -205,7 +201,7 @@ fn main() {
}
```

You can change the name of the function argument used, or even set a concrete type:
You can change the name of the parameter used and even set it to a concrete:

```rust
use kobold::prelude::*;
Expand All @@ -227,7 +223,7 @@ fn main() {
}
```

## More examples
## More Examples

To run **Kobold** you'll need to install [`trunk`](https://trunkrs.dev/) (check the [full instructions](https://trunkrs.dev/#install) if you have problems):
```sh
Expand Down
54 changes: 26 additions & 28 deletions crates/kobold/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,24 @@
//!
//! # Kobold
//!
//! **Kobold** uses macros to deliver familiar HTML-esque syntax for building declarative web interfaces,
//! **Kobold** uses macros to deliver familiar JSX-esque syntax for building declarative web interfaces,
//! while leveraging Rust's powerful type system for safety and performance.
//!
//! ### Zero-cost static HTML
//! ### Zero-Cost Static HTML
//!
//! Like in [React](https://reactjs.org/) or [Yew](https://yew.rs/) updates are done by repeating calls
//! to a render function whenever the state changes. However, unlike either, **Kobold** does not produce a
//! full blown [virtual DOM](https://en.wikipedia.org/wiki/Virtual_DOM). Instead the [`view!`](view) macro compiles
//! all static [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) elements to a single
//! JavaScript function that constructs them.
//! The [`view!`](view) macro produces opaque [`impl View`](View) types that by default do no allocations.
//! All static [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) elements compile to
//! inline JavaScript code that constructs them. Expressions are injected into the constructed DOM on first render.
//! Kobold keeps track of the DOM node references for these expressions.
//!
//! All expressions, which must implement the [`View`](View) trait, are injected into the constructed DOM on first
//! render. Kobold keeps track of the DOM node references for these expressions. Since the exact types the
//! expressions evaluate to are known to the Rust compiler, update calls can diff them by value and surgically
//! update the DOM should they change. Changing a string or an integer only updates the exact
//! [`Text` node](https://developer.mozilla.org/en-US/docs/Web/API/Text) that string or integer was rendered to.
//! Since the exact types the expressions evaluate to are known to the Rust compiler, update calls can diff them by
//! value ([or pointer](crate::value::StrExt::fast_diff)) and surgically update the DOM should they change. Changing a
//! string or an integer only updates the exact [`Text` node](https://developer.mozilla.org/en-US/docs/Web/API/Text)
//! that string or integer was rendered to.
//!
//! _If the [`view!`](view) macro invocation contains HTML elements with no expressions, the constructed [`View`](View)
//! _If the [`view!`](view) macro invocation contains DOM elements with no expressions, the constructed [`View`](View)
//! type will be zero-sized, and its [`View::update`](View::update) method will be empty, making updates of static
//! HTML quite literally zero-cost._
//! HTML literally zero-cost._
//!
//! ### Hello World!
//!
Expand All @@ -44,9 +42,8 @@
//! }
//! ```
//!
//! The _render function_ must return a type that implements the [`View`](View) trait. Since the [`view!`](view) macro
//! produces _transient types_, or [_Voldemort types_](https://wiki.dlang.org/Voldemort_types), the best approach
//! here is to always use the `impl View` return type.
//! The component function must return a type that implements the [`View`](View) trait. Since the [`view!`](view) macro
//! produces transient locally defined types the best approach here is to always use the opaque `impl View` return type.
//!
//! Everything here is statically typed and the macro doesn't delete any information when manipulating the
//! token stream, so the Rust compiler can tell you when you've made a mistake:
Expand Down Expand Up @@ -80,7 +77,7 @@
//!
//! view! {
//! <p>
//! "You clicked on the "
//! "You clicked the "
//! // `{onclick}` here is shorthand for `onclick={onclick}`
//! <button {onclick}>"Button"</button>
//! " "{ count }" times."
Expand Down Expand Up @@ -108,7 +105,7 @@
//!
//! For more details visit the [`stateful` module documentation](stateful).
//!
//! ### Conditional rendering
//! ### Conditional Rendering
//!
//! Because the [`view!`](view) macro produces unique transient types, `if` and `match` expressions that invoke
//! the macro will naturally fail to compile.
Expand All @@ -117,7 +114,6 @@
//! **Kobold** will scan the body of of your component render function, and make all [`view!`](view) macro invocations
//! inside an `if` or `match` expression, and wrap them in an enum making them the same type:
//!
//!
//! ```
//! # use kobold::prelude::*;
//! #[component(auto_branch)]
Expand Down Expand Up @@ -160,7 +156,7 @@
//! No allocations are made by **Kobold** when updating such a list, unless the rendered list needs
//! to grow past its original capacity.
//!
//! ### Borrowed values
//! ### Borrowed Values
//!
//! [`View`](View) types are truly transient and only need to live for the duration of the initial render,
//! or for the duration of the subsequent update. This means that you can easily and cheaply render borrowed
Expand All @@ -183,7 +179,7 @@
//! }
//! ```
//!
//! ### Components with children
//! ### Components with Children
//!
//! If you wish to capture children from parent [`view!`](view) invocation, simply change
//! `#[component]` to `#[component(children)]`:
Expand All @@ -205,7 +201,7 @@
//! }
//! ```
//!
//! You can change the name of the function argument used, or even set a concrete type:
//! You can change the name of the parameter used and even set it to a concrete:
//!
//! ```no_run
//! use kobold::prelude::*;
Expand All @@ -227,7 +223,7 @@
//! }
//! ```
//!
//! ## More examples
//! ## More Examples
//!
//! To run **Kobold** you'll need to install [`trunk`](https://trunkrs.dev/):
//! ```sh
Expand Down Expand Up @@ -288,14 +284,13 @@ pub use kobold_macros::view;

use wasm_bindgen::{JsCast, JsValue};

mod value;

pub mod attribute;
pub mod branching;
pub mod dom;
pub mod event;
pub mod list;
pub mod util;
pub mod value;

#[cfg(feature = "stateful")]
pub mod stateful;
Expand Down Expand Up @@ -327,8 +322,8 @@ pub mod reexport {

/// Trait that describes types that can be rendered in the DOM.
pub trait View {
/// HTML product of this type, this is effectively the strongly-typed
/// virtual DOM equivalent for Kobold.
/// The product should contain a DOM reference to this View and
/// any data it needs to update itself.
type Product: Mountable;

/// Build a product that can be mounted in the DOM from this type.
Expand All @@ -337,6 +332,7 @@ pub trait View {
/// Update the product and apply changes to the DOM if necessary.
fn update(self, p: &mut Self::Product);

/// Once this view is built, do something once.
fn on_mount<F>(self, handler: F) -> OnMount<Self, F>
where
F: FnOnce(&<Self::Product as Mountable>::Js),
Expand All @@ -348,6 +344,8 @@ pub trait View {
}
}

/// Similar to [`on_mount`](View::on_mount) but triggers on every
/// update, not just initial render.
fn on_render<F>(self, handler: F) -> OnRender<Self, F>
where
F: FnOnce(&<Self::Product as Mountable>::Js),
Expand Down
21 changes: 20 additions & 1 deletion crates/kobold/src/stateful.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl<S> Inner<S> {
}
}

/// Trait used to create stateful components, see the [module documentation](crate::stateful) for details.
/// Trait used to create stateful components, see [`stateful`](crate::stateful::stateful) for details.
pub trait IntoState: Sized {
type State: 'static;

Expand Down Expand Up @@ -67,6 +67,20 @@ pub struct StatefulProduct<S> {
el: Element,
}

/// Create a stateful [`View`](crate::View) over some mutable state. The state
/// needs to be created using the [`IntoState`](IntoState) trait.
///
/// ```
/// # use::kobold::prelude::*;
/// // `IntoState` is implemented for primitive values
/// let int_view = stateful(0, |count: &Hook<i32>| { "TODO" });
///
/// // Another easy way to create arbitrary state is using a closure...
/// let string_view = stateful(|| String::from("foo"), |text: &Hook<String>| { "TODO" });
///
/// // ...or a function with no parameters
/// let vec_view = stateful(Vec::new, |counts: &Hook<Vec<i32>>| { "TODO" });
/// ```
pub fn stateful<'a, S, F, H>(
state: S,
render: F,
Expand All @@ -76,6 +90,11 @@ where
F: Fn(&'a Hook<S::State>) -> H + 'static,
H: View + 'a,
{
// There is no safe way to represent a generic closure with generic return type
// that borrows from that closure's arguments, without also slapping a lifetime.
//
// The `stateful` function ensures that correct lifetimes are used before we
// erase them for the use in the `Stateful` struct.
let render = move |hook: *const Hook<S::State>| render(unsafe { &*hook });
Stateful { state, render }
}
Expand Down
32 changes: 31 additions & 1 deletion crates/kobold/src/stateful/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use std::rc::Weak;
use crate::stateful::{Inner, ShouldRender, WeakRef, WithCell};
use crate::View;

/// A hook to some state `S`. A reference to `Hook` is obtained by using the [`stateful`](crate::stateful::stateful)
/// A hook into some state `S`. A reference to `Hook` is obtained by using the [`stateful`](crate::stateful::stateful)
/// function.
///
/// Hook can be read from though its `Deref` implementation, and it allows for mutations either by [`bind`ing](Hook::bind)
/// closures to it, or the creation of [`signal`s](Hook::signal).
pub struct Hook<S> {
pub(super) state: S,
pub(super) inner: WeakRef<WithCell<Inner<S>>>,
Expand All @@ -16,6 +19,25 @@ pub struct Signal<S> {
}

impl<S> Signal<S> {
/// Update the state behind this `Signal`.
///
/// ```
/// # use kobold::prelude::*;
/// fn example(count: Signal<i32>) {
/// // increment count and trigger a render
/// count.update(|count| *count += 1);
///
/// // increment count if less than 10, only render on change
/// count.update(|count| {
/// if *count < 10 {
/// *count += 1;
/// Then::Render
/// } else {
/// Then::Stop
/// }
/// })
/// }
/// ```
pub fn update<F, O>(&self, mutator: F)
where
F: FnOnce(&mut S) -> O,
Expand All @@ -30,6 +52,7 @@ impl<S> Signal<S> {
}
}

/// Same as [`update`](Signal::update), but it never renders updates.
pub fn update_silent<F>(&self, mutator: F)
where
F: FnOnce(&mut S),
Expand All @@ -39,6 +62,7 @@ impl<S> Signal<S> {
}
}

/// Replace the entire state with a new value and trigger an update.
pub fn set(&self, val: S) {
self.update(move |s| *s = val);
}
Expand All @@ -53,6 +77,8 @@ impl<S> Clone for Signal<S> {
}

impl<S> Hook<S> {
/// Create an owned `Signal` to the state. This is effectively a weak reference
/// that allows for remote updates, particularly useful in async code.
pub fn signal(&self) -> Signal<S> {
let weak = self.inner.weak();

Expand All @@ -61,6 +87,8 @@ impl<S> Hook<S> {
}
}

/// Binds a closure to a mutable reference of the state. While this method is public
/// it's recommended to use the [`bind!`](crate::bind) macro instead.
pub fn bind<E, F, O>(&self, callback: F) -> impl Fn(E) + 'static
where
S: 'static,
Expand All @@ -80,6 +108,8 @@ impl<S> Hook<S> {
}
}

/// Get the value of state if state implements `Copy`. This is equivalent to writing
/// `**hook` but conveys intent better.
pub fn get(&self) -> S
where
S: Copy,
Expand Down
Loading

0 comments on commit 48e28a9

Please sign in to comment.