Rust Server Components. JSX-like syntax and async out of the box.

Related tags

Command-line rscx
Overview

crates.io

RSCx - Rust Server Components

RSCx is a server-side HTML rendering engine library with a neat developer experience and great performance.

Features:

  • all components are async functions
  • JSX-like syntax called RSX parsed with rstml
  • contexts, to easily pass values down the components tree (example)
  • inspired by Maud and Leptos

⚠️ Warning: not production-ready yet. It lacks important features such as HTML escaping!

Usage

All the examples can be found in rscx/examples/.

use rscx::{component, html, props, CollectFragment};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = app().await;
    println!("{}", app);
    Ok(())
}

// simple function returning a String
// it will call the Items() function
async fn app() -> String {
    let s = "ul { color: red; }";
    html! {
        <!DOCTYPE html>
        <html>
            <head>
                <style>{s}</style>
            </head>
            <body>
                // call a component with no props
                <Section />

                // call a component with props and children
                <Section title="Hello">
                    <Items />
                </Section>
            </body>
        </html>
    }
}

#[props]
/// mark a struct with #[props] to use it as props in a component.
/// #[builder] can customize single props, marking them as option or setting a default value.
struct SectionProps {
    #[builder(setter(into), default = "Default Title".to_string())]
    title: String,
    #[builder(default)]
    children: String,
}

#[component]
/// mark functions with #[component] to use them as components inside html! macro
fn Section(props: SectionProps) -> String {
    html! {
        <div>
            <h1>{ props.title }</h1>
            { props.children }
        </div>
    }
}

#[component]
async fn Items() -> String {
    let data = load_data_async().await;
    html! {
        <ul>
            {
                data
                    .into_iter()
                    .map(|item| html! { <li>{ item }</li> })
                    .collect_fragment() // helper method to collect a list of components into a String
            }
        </ul>
    }
}

/// async functions can be easily used in the body of a component, as every component is an async
/// function
async fn load_data_async() -> Vec<String> {
    vec!["a".to_string(), "b".to_string(), "c".to_string()]
}

Benchmarks

RSCx is fast.

Disclaimer: RSCx is for servers, as the name suggests. Therefore the following comparisons with Leptos are unfair. This library contains only a fraction of Leptos' features.

Disclaimer 2: The benchmarks are pretty basics and should not influence your decision on whether to use or not this library. Focus on the DX. They are included as I kept running them to make sure I didn't fall too much behind alternatives.

The time in the middle of the three is the average.

Run the benchmarks locally

cd bench
# cargo install criterion
cargo criterion

Benchmark 1: single element, lots of HTML attributes

many_attrs/maud_many_attrs
                        time:   [205.89 ns 208.35 ns 211.53 ns]
many_attrs/horrorshow_many_attrs
                        time:   [37.221 µs 37.304 µs 37.401 µs]
many_attrs/html_node_many_attrs
                        time:   [67.726 µs 67.830 µs 67.939 µs]
many_attrs/leptos_many_attrs
                        time:   [923.31 ns 928.46 ns 935.04 ns]
many_attrs/rscx_many_attrs
                        time:   [207.96 ns 212.82 ns 219.28 ns]

RSCx and Maud pretty much are the same as their macros output is effectively a static string with the result.

Benchmark 2: little element with props and child

small_fragment/maud_small_fragment
                        time:   [107.60 ns 107.71 ns 107.81 ns]
small_fragment/horrorshow_small_fragment
                        time:   [405.98 ns 406.08 ns 406.21 ns]
small_fragment/leptos_small_fragment
                        time:   [1.7641 µs 1.7652 µs 1.7662 µs]
small_fragment/rscx_small_fragment
                        time:   [101.79 ns 101.87 ns 101.97 ns]

RSCx offers a better DX than Maud, as the syntax is nicer and values such as i32 can be passed as props/attributes, while in Maud every attribute must be a static string.

Benchmark 3: dynamic attributes (read for variable)

many_dyn_attrs/horrorshow_many_dyn_attrs
                        time:   [50.445 µs 50.702 µs 50.977 µs]
many_dyn_attrs/leptos_many_dyn_attrs
                        time:   [100.13 µs 100.52 µs 101.00 µs]
many_dyn_attrs/rscx_many_dyn_attrs
                        time:   [33.953 µs 33.990 µs 34.037 µs]

Benchmark 4: async component rendering a list of 100 items

async_list/maud_async_list
                        time:   [2.3114 µs 2.3241 µs 2.3377 µs]
async_list/leptos_async_list
                        time:   [55.149 µs 55.228 µs 55.315 µs]
async_list/rscx_async_list
                        time:   [5.4809 µs 5.4987 µs 5.5151 µs]

I'll reiterate the disclaimer: Leptos is not specifically made for SSR. Going through its reactivity system (using async resources) adds overhead.

You might also like...
Black-box integration tests for your REST API using the Rust and its test framework

restest Black-box integration test for REST APIs in Rust. This crate provides the [assert_api] macro that allows to declaratively test, given a certai

Russh - Async (tokio) SSH2 client and server rimplementation

Russh Async (tokio) SSH2 client and server rimplementation. This is a fork of Thrussh by Pierre-Étienne Meunier which adds: More safety guarantees AES

`boxy` - declarative box-drawing characters

boxy - declarative box-drawing characters Box-drawing characters are used extensively in text user interfaces software for drawing lines, boxes, and o

Benson Box built on Substrate for a world UNcorporated.

Benson Box Benson Box built on Substrate. For getting started and technical guides, please refer to the Benson Wiki. Contributing All PRs are welcome!

Boxxy puts bad Linux applications in a box with only their files.

boxxy is a tool for boxing up misbehaving Linux applications and forcing them to put their files and directories in the right place, without symlinks!

A bit like tee, a bit like script, but all with a fake tty. Lets you remote control and watch a process

teetty teetty is a wrapper binary to execute a command in a pty while providing remote control facilities. This allows logging the stdout of a process

A blazing fast and easy to use TRPC-like server for Rust.

rspc 🚧 Work in progress 🚧 A blazing fast and easy to use TRPC-like server for Rust. Website Example You define a trpc router and attach resolvers to

Works out if this is running from inside a shell, and if so, which one.

pshell pshell answers the question "Is my application running in a shell, and if so, which one". Example: you are installing something and want to mak

Basic template for an out-of-tree Linux kernel module written in Rust.

Rust out-of-tree module This is a basic template for an out-of-tree Linux kernel module written in Rust. Please note that: The Rust support is experim

Comments
  • `Option`s are not handled very well

    `Option`s are not handled very well

    I might be missing something but rendering an Option doesn't work very well at the moment as one has to always append .unwrap_or_default() to construct an empty String.

    Is it possible to make that implicit and just omit rendering if the Option is None?

    opened by thomaseizinger 4
  • Auto-generate props if there are more than 1 arguments or the one provided does not end in `Props`

    Auto-generate props if there are more than 1 arguments or the one provided does not end in `Props`

    This is a bit opinionated because it always applies the #[builder(setter(into))] attribute but this makes usage a lot more ergonomic. It does carry over all attributes so users can customize it if they want.

    Resolves: #1.

    opened by thomaseizinger 0
  • Speed up void element detection 1.7–30x

    Speed up void element detection 1.7–30x

    Currently void element detection uses a hash map. However arrays are almost always faster when dealing with few items (even though the time complexity is O(n)).

    https://github.com/Pitasi/rscx/blob/e7d1e792527415db8f23a1b9e3f13a1dbf5a767b/rscx-macros/src/lib.rs#L23-L31

    So replacing the current code with the following array is much faster:

    const VOID_TAGS: [&str; 14] = [
        "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
        "track", "wbr",
    ];
    

    Potential other improvements

    Since the array is traversed from beginning to end, you can put the most common tags at the beginning to minimize the number of lookups.

    While I couldn't find any strong data on the most common tags, I'd suspect it'd be something like this:

    const VOID_TAGS: [&str; 14] = [
        "img", "input", "meta", "link", "hr", "br", "source", "track", "wbr", "area", "base", "col",
        "embed", "param",
    ];
    

    Benchmarks

    I wrote up a quick benchmark to demonstrate the difference. At worst the array is 70% faster. At best it's 30x faster.

    extern crate criterion;
    
    use criterion::{black_box, criterion_group, criterion_main, Criterion};
    
    pub fn criterion_benchmark(c: &mut Criterion) {
        let tags_arr = [
            "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
            "source", "track", "wbr",
        ];
        let tags_set: std::collections::HashSet<&str> = tags_arr.iter().copied().collect();
        c.bench_function("array first", |b| {
            b.iter(|| black_box(tags_arr.iter().any(|&x| x == "area")))
        });
        c.bench_function("array last", |b| {
            b.iter(|| black_box(tags_arr.iter().any(|&x| x == "wbr")))
        });
        c.bench_function("set first", |b| {
            b.iter(|| black_box(tags_set.contains("area")))
        });
        c.bench_function("set last", |b| {
            b.iter(|| black_box(tags_set.contains("wbr")))
        });
    }
    
    criterion_group!(benches, criterion_benchmark);
    criterion_main!(benches);
    
    array first             time:   [504.87 ps 505.23 ps 505.72 ps]
    Found 11 outliers among 100 measurements (11.00%)
      4 (4.00%) high mild
      7 (7.00%) high severe
    
    array last              time:   [8.0767 ns 8.0819 ns 8.0889 ns]
    Found 13 outliers among 100 measurements (13.00%)
      4 (4.00%) high mild
      9 (9.00%) high severe
    
    set first #2          time:   [13.737 ns 13.743 ns 13.750 ns]
    Found 9 outliers among 100 measurements (9.00%)
      2 (2.00%) high mild
      7 (7.00%) high severe
    
    set last #2           time:   [15.542 ns 15.554 ns 15.569 ns]
    Found 9 outliers among 100 measurements (9.00%)
      1 (1.00%) low mild
      2 (2.00%) high mild
      6 (6.00%) high severe
    

    P.S. I believe the improvement is real and not from LLVM optimize out the array traversal because the first/last element lookup times are vastly different. If LLVM did optimize out the array traversal, then we would expect the first/last element lookups to have very similar times.

    opened by Nick-Mazuk 1
  • [Feature] Inline prop definition inside component signature

    [Feature] Inline prop definition inside component signature

    I've been toying with this library, and it's a lot of fun. I've been looking for some kind of templating that supported context dependency injection like Leptos, awesome stuff!

    It looks like this is planned from reading the source, but having props defined in the function signature to generate the Props struct, just like Leptos, would be nice. I'd love to make a PR for this if you're interested in help.

    opened by wrapperup 2
Owner
Antonio Pitasi
Antonio Pitasi
Rudi - an out-of-the-box dependency injection framework for Rust.

Rudi Rudi - an out-of-the-box dependency injection framework for Rust. use rudi::{Context, Singleton, Transient}; // Register `fn(cx) -> A { A }` as

ZihanType 15 Aug 15, 2023
👩‍💻Type-checked JSX for Rust

This crate provides the html! macro for building fully type checked HTML documents inside your Rust code using roughly JSX compatible syntax.

axo 50 Jan 29, 2023
Count your code by tokens, types of syntax tree nodes, and patterns in the syntax tree. A tokei/scc/cloc alternative.

tcount (pronounced "tee-count") Count your code by tokens, types of syntax tree nodes, and patterns in the syntax tree. Quick Start Simply run tcount

Adam P. Regasz-Rethy 48 Dec 7, 2022
Valq - macros for querying and extracting value from structured data by JavaScript-like syntax

valq   valq provides a macro for querying and extracting value from structured data in very concise manner, like the JavaScript syntax. Look & Feel: u

Takumi Fujiwara 24 Dec 21, 2022
A prolog like syntax for egg

egglog Using the egg library with a file format and semantics similar to datalog. Explanatory blog posts: https://www.philipzucker.com/egglog-checkpoi

Philip Zucker 40 Dec 1, 2022
A simple, C-like, ternary operator for cleaner syntax.

A simple ternary operator macro in rust. the iff! macro is the only item exported by this crate, it simply takes three expressions, seperated by ? and

KaitlynEthylia 42 May 23, 2023
Pathfinding on grids using jumping point search and connected components.

grid_pathfinding A grid-based pathfinding system. Implements Jump Point Search with improved pruning rules for speedy pathfinding. Pre-computes connec

Thom van der Woude 16 Dec 12, 2022
🕺 Run React code snippets/components from your command-line without config

Run React code snippets/components from your command-line without config.

Eliaz Bobadilla 11 Dec 30, 2022
Components of Fornjot that are no longer actively maintained. Pull requests still welcome!

Fornjot - Extra Components About These are extra components from the Fornjot repository, that are no longer actively maintained. Fornjot's goal was to

Hanno Braun 4 Jun 6, 2023
Supercharge your markdown including RSCx components.

rscx-mdx Render Markdown into HTML, while having custom RSCx components inside. Usage use rscx::{component, html, props}; use rscx_mdx::mdx::{Mdx, Mdx

Antonio Pitasi 4 Oct 21, 2023