Skip to content

human-solutions/silkenweb-lite

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

silkenweb-lite

A lightweight extraction of silkenweb without CSS-in-Rust features. Silkenweb is a reactive web framework for Rust with fine-grained reactivity, server-side rendering, and hydration support.

Crates

Crate Description
silkenweb Main framework: elements, DOM abstractions, routing, SSR, hydration, animation
silkenweb-base Core primitives and browser API wrappers
silkenweb-macros Proc macros for deriving element traits (Element, ChildElement, etc.)
silkenweb-signals-ext Extensions for futures-signals (signal products, value abstractions)
silkenweb-task Cross-platform task management (browser microtasks / tokio on server)
silkenweb-test Testing utilities for browser and SSR tests

Key Features

  • Pure Rust API with type-safe HTML and SVG element construction
  • Fine-grained reactivity via futures-signals
  • Multiple DOM modes: Dry (SSR), Wet (client), Hydro (hydration), Template (reusable)
  • Routing, animation, and local/session storage APIs
  • Cross-platform: browser (wasm32-unknown-unknown) and server (tokio)

Quick Example

use futures_signals::signal::{Mutable, SignalExt};
use silkenweb::prelude::*;
use silkenweb::elements::html::{button, div, span};
use silkenweb::value::Sig;

let count = Mutable::new(0);
let count_text = count.signal().map(|i| format!("Value: {i}!"));

let app = div()
    .child(button().on_click({
        let count = count.clone();
        move |_, _| count.replace_with(|n| *n - 1);
    }).text("-1"))
    .child(span().text(Sig(count_text)))
    .child(button().on_click(move |_, _| {
        count.replace_with(|n| *n + 1);
    }).text("+1"));

Server-Side Rendering

use silkenweb::dom::Dry;
use silkenweb::elements::html::p;

let rendered = p::<Dry>().text("Hello, world!").freeze().to_string();
assert_eq!(rendered, "<p>Hello, world!</p>");

Usage Guide

Element Construction

Elements use a fluent builder pattern. Functions like div(), button(), input() create elements; methods chain to add attributes, children, classes, and events.

div()
    .class("container")
    .child(h1().text("Hello"))
    .child(button().on_click(|_, _| { /* handler */ }).text("Click"))

Use r#type for the type attribute (Rust keyword): input().r#type("text").

Reactivity with Signals

State lives in Mutable<T>. Wrap signals in Sig() to bind them to the UI:

let name = Mutable::new(String::new());

input()
    .value(Sig(name.signal_cloned()))
    .on_input({
        let name = name.clone();
        move |_, el| name.set(el.value())
    })

Key signal methods:

  • .signal() / .signal_cloned() — get a signal from a Mutable
  • .signal_ref(|val| ...) — map over a reference to the current value
  • .map(|val| ...) — transform signal values
  • .broadcast() — share a signal across multiple consumers
  • .set() / .set_neq() — update value (neq only if changed)
  • .lock_ref() / .lock_mut() — synchronous access to current value

Combine signals with map_ref!:

use futures_signals::map_ref;

let combined = map_ref! {
    let a = mutable_a.signal(),
    let b = mutable_b.signal() => {
        *a + *b
    }
};

Component Pattern

Components are functions generic over <D: Dom>, returning a concrete element type. Props are passed via trait objects or Rc:

pub fn search_bar<D: Dom>(data: &Rc<impl SearchBarInfo + 'static>) -> Div<D> {
    div()
        .class("search-bar")
        .child(input()
            .placeholder(Sig(data.placeholder()))
            .value(Sig(data.value()))
            .on_input({
                let data = data.clone();
                move |_, el| data.set_value(el.value())
            }))
}

The <D: Dom> generic enables the same component code for client rendering (Wet), SSR (Dry), and hydration (Hydro).

Children

// Single child
div().child(p().text("hi"))

// Multiple from iterator
ul().children(items.iter().map(|i| li().text(i.name())))

// Conditional
div().optional_child(some_option_element)
div().optional_child(Sig(signal_returning_option))

// Dynamic list from SignalVec
div().children_signal(my_signal_vec.map(|item| li().text(item.name())))

Conditional Rendering

Use .map() with .then_some() and .optional_child():

let panel = is_visible.signal().map({
    let data = data.clone();
    move |visible| visible.then_some(settings_panel(&data))
});

div().optional_child(Sig(panel))

Dynamic CSS Classes

// Static
div().class("active")

// Conditional via signal — return Option<&str>
let cls = is_open.signal().map(|open| open.then_some("expanded"));
div().classes(Sig(cls))

Events

Event handlers take |event, element|. Clone state into the closure:

button().on_click({
    let data = data.clone();
    move |_, _| data.submit()
})

input().on_input({
    let data = data.clone();
    move |_, el| data.set_value(el.value())
})

// Prevent default
anchor().on_click({
    let data = data.clone();
    move |e, _| {
        e.prevent_default();
        data.navigate();
    }
})

Async Tasks

Use spawn_local for fire-and-forget async work. Combine with .for_each() on signals for reactive effects:

use silkenweb::task::spawn_local;

spawn_local(search_term.signal_cloned().for_each({
    let api = api.clone();
    move |term| {
        let api = api.clone();
        async move {
            if !term.is_empty() {
                api.search(&term).await;
            }
        }
    }
}));

SSR and Hydration

Same component code works across rendering modes:

// Server: render to HTML string
use silkenweb::dom::Dry;
let html = my_component::<Dry>(data).freeze().to_string();

// Client: hydrate server-rendered HTML
use silkenweb::{dom::Hydro, hydration::hydrate};
hydrate("app", my_component::<Hydro>(data)).await;

Routing

use silkenweb::router;

// Navigate
router::set_url_path("settings");

// React to URL changes
let page = router::url_path().signal_ref(|path| {
    match path.as_str() {
        "/settings" => settings_page(),
        _ => home_page(),
    }
});

div().child(Sig(page))

License

MIT OR Apache-2.0

About

A lite version of silkenweb without the css features.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages