5 releases

1.0.0-alpha.8 Mar 2, 2022
1.0.0-alpha.7 Mar 1, 2022
1.0.0-alpha.6 Feb 28, 2022
1.0.0-alpha.5 Feb 27, 2022
1.0.0-alpha.2 Jan 28, 2022

#1785 in Web programming

MIT license

15KB
156 lines

frender

Crates.io docs.rs GitHub license GitHub stars

Functional Rendering: React in Rust

frender is still in alpha and it's api might change. For now it is recommended to specify the exact version in Cargo.toml. Before updating, please see the full changelog in case there are breaking changes.

There are some example apps in examples folder. You can preview them at this site.

Quick Start

  1. Create a new cargo project

    cargo new my-frender-app
    cd my-frender-app
    
  2. Add frender to dependencies in Cargo.toml.

    [dependencies]
    frender = "= 1.0.0-alpha.8"
    
  3. Create index.html in the project root directory.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>My frender App</title>
        <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
        <link data-trunk rel="rust" href="Cargo.toml" />
      </head>
      <body>
        <div id="frender-root"></div>
      </body>
    </html>
    
  4. Modify src/main.rs

    use frender::prelude::*;
    
    #[component(main(mount_element_id = "frender-root"))]
    fn Main() {
        rsx!(
            <div>
                "Hello, frender!"
            </div>
        )
    }
    
  5. Run with trunk

    Install trunk and then execute:

    trunk serve
    

    Then you can navigate to http://localhost:8080 to see your frender app.

rsx syntax

rsx element

use frender::prelude::*;

rsx! (
  <MyComp id="my-component">
    // Child node can be any literal strings or numbers
    "some string"
    1
    // Child node can be any rust expressions wrapped in braces
    { 1 + 6 }
    { value }
    // Child node can be an element
    <MyChild key="k" prop={any_expr} />

    // Prop without value means `true`, just like React
    <MyDialog show />

    // Fragment
    <>1 2 3</>
    // Fragment with key
    <# key="key">1 2 3</#>

    // you can also use `</_>` to enclose any element
    <path::to::Component></_>
    // the above is equivalent to:
    <path::to::Component></path::to::Component>
  </MyComp>
)

Any component name starting with lower case letter [a-z] will be interpreted as an intrinsic component. For example, rsx!( <div id="my-div" /> ) will be resolved to:

use frender::prelude::*;
use self::intrinsic_components::div::prelude::*;

rsx! (
  <self::intrinsic_components::div::prelude::Component id="my-div" />
)

rsx prop

In order to make rsx less verbose, frender provides IntoPropValue trait. The value in <MyComponent prop={value} /> will be mapped to IntoPropValue::into_prop_value(value).

With this, assuming the prop accepts Option<i32>, you can simplify prop={Some(1)} to prop={1}, because T implements IntoPropValue<Option<T>>.

If you want to pass the value as is, you can use := to set prop. prop:={value}

Write a component

Component with no props

use frender::prelude::*;

#[component]
fn MyComponent() {
  //            ^
  //            the return type defaults to react::Element
  rsx!( <div /> )
}

// Or you can specify the return type explicitly
#[component]
fn MyAnotherComponent() -> Option<react::Element> {
  if check_something() {
    Some(rsx!( <MyComponent /> ))
  } else {
    None
  }
}

Component with props

First, define MyProps

use frender::prelude::*;

def_props! {
  pub struct MyProps {
    // Required prop
    name: String,

    // Optional prop which defaults to `Default::default()`
    // The following property `age` is optional, and defaults to `None`
    age?: Option<u8>,

    // The following property `tags` is optional, and defaults to `Vec::default()`
    tags?: Vec<String>,

    // If the prop type is not specified,
    // then frender will infer the type by prop name.
    // For example, `class_name` default has type `Option<String>`
    // The following property `class_name` is optional, has type Option<String>
    class_name?,

    // The following property `id` is required, has type Option<String>
    id,

    // Prop can also have type generics.
    // For example, the following is
    // the default definition for prop `children`,
    // which means it accepts any `Option<TNode>` where TNode implements react::Node,
    // and then map the value into `Option<react::Children>` and store it into MyProps.
    children<TNode: react::Node>(value: Option<TNode>) -> Option<react::Children> {
      value.and_then(react::Node::into_children)
    },
  }
}

Then write the component with the above props:

use frender::prelude::*;
#[component]
pub fn MyComponent(props: &MyProps) {
  rsx!(<div>{&props.children}</div>)
}

Due to the generics, in some very rare cases, you may meet errors like type annotations needed cannot infer type for type parameter. You can solve it by specifying the type with the turbofish syntax ::<>. For example:

rsx! (
  // ERROR: type annotations needed
  <a children={None} />
)
rsx! (
  // it works!
  <a children={None::<()>} />
)

Hooks

React hooks are also available in frender.

You checkout the examples for the usage.

Future Development Plans

  • Documentation
  • Intrinsic svg components
  • Export frender components to js
  • Server Side Rendering
  • Type checking for CssProperties
  • Css-in-rust (For example, integrate with emotion/react)

Contributing

frender is open sourced at GitHub. Pull requests and issues are welcomed.

You can also sponsor me and I would be very grateful ❤️

Buy Me a Coffee at ko-fi.com

Dependencies

~15MB
~277K SLoC