2 pointsby kent81923 hours ago1 comment
  • kent81923 hours ago
    Author here. The novel piece is the Pages compiler (Manouche — named after the Django Reinhardt jazz genre): page!, head!, and form! macros go through TokenStream → AST → validation → IR → codegen and emit both client WASM and server SSR code from the same source. A #[server_fn] is callable from client components but compiles to a server-only function with full DI access:

      use reinhardt::DatabaseConnection;
      use reinhardt::db::orm::Model;
      use reinhardt::pages::server_fn::{ServerFnError, server_fn};
    
      #[server_fn]
      async fn list_active_users(#[inject] db: DatabaseConnection) -> Result<Vec<User>, ServerFnError> {
          User::objects()
              .filter_by(User::field_is_active().eq(true))
              .all_with_db(&db)
              .await
              .map_err(|e| ServerFnError::application(format!("DB error: {e}")))
      }
    
    The same file holds the client component that calls it — list_active_users is invoked as an ordinary async Rust function; on WASM the macro rewrites it into a typed RPC call:

      use reinhardt::pages::component::Page;
      use reinhardt::pages::page;
      use reinhardt::pages::reactive::hooks::{Action, use_action};
    
      pub fn active_users_view() -> Page {
          // use_action works uniformly on native (SSR) and WASM; on native the future
          // is dropped after a synchronous Idle→Pending→Idle cycle, so SSR renders the
          // empty shell that WASM later hydrates and populates.
          let load =
              use_action(|_: ()| async move { list_active_users().await.map_err(|e| e.to_string()) });
          load.dispatch(());
    
          page!(|load: Action<Vec<User>, String>| {
              div {
                  watch {
                      if load.is_pending() {
                          p { "Loading..." }
                      } else if let Some(err) = load.error() {
                          p { { err } }
                      } else {
                          ul {
                              { Page::Fragment(
                                  load.result().unwrap_or_default().iter()
                                      .map(|u| page!(|name: String| li { { name } })(u.username.clone()))
                                      .collect::<Vec<_>>()
                              ) }
                          }
                      }
                  }
              }
          })(load)
      }
    
    No OpenAPI schema, no hand-rolled fetch, no duplicated request/response types between client and server. The #[server_fn] macro generates the RPC endpoint + JSON codec on the server, a typed async stub on the client, and hydration markers so SSR-rendered HTML stays consistent after WASM takes over.

    Website: https://reinhardt-web.dev

    docs.rs: https://docs.rs/crate/reinhardt-web/latest