An open API service indexing awesome lists of open source software.

https://github.com/valerioageno/ssr-rs

Server side rendering on rust servers using the v8 engine for parse and evaluate the javascript code.
https://github.com/valerioageno/ssr-rs

actix-web javascript react reactjs render rust rust-servers rust-ssr server-side-rendering ssr v8-engine

Last synced: 12 months ago
JSON representation

Server side rendering on rust servers using the v8 engine for parse and evaluate the javascript code.

Awesome Lists containing this project

README

          

# ๐Ÿš€ Rust server side rendering

[![API](https://docs.rs/ssr_rs/badge.svg)](https://docs.rs/ssr_rs)
[![codecov](https://codecov.io/gh/Valerioageno/ssr-rs/branch/main/graph/badge.svg?token=O0CZIZAR7X)](https://codecov.io/gh/Valerioageno/ssr-rs)

The crate aims to enable server side rendering on rust servers in the simplest and lightest way possible.

It uses an embedded version of the [V8](https://v8.dev/) javascript engine (rusty_v8) to parse and evaluate a built bundle file and return a string with the rendered html.
> [!NOTE]
> This project is the backbone of [tuono](https://github.com/Valerioageno/tuono); a fullstack react framework with built in SSR.

Currently it works with [Vite](https://vitejs.dev/), [Webpack](https://webpack.js.org/), [Rspack](https://www.rspack.dev/), [React 18](https://react.dev/) and [Svelte 4](https://svelte.dev/) - Check the `examples/` folder.

> Check here the benchmark results.

## Getting started

Add this to your `Cargo.toml`:

```bash
cargo add ssr_rs
```

## Example

To render to string a bundled react project the application should perform the following
calls.

```rust
use ssr_rs::Ssr;
use std::fs::read_to_string;

fn main() {
Ssr::create_platform();

let source = read_to_string("./path/to/build.js").unwrap();

let mut js = Ssr::new(&source, "entryPoint").unwrap();

let html = js.render_to_string(None).unwrap();

assert_eq!(html, "...".to_string());
}
```

## What is the "entryPoint"?

The `entryPoint` could be either:
- the function that returns an object with one or more properties that are functions that when called return the rendered result
- the object itself with one or more properties that are functions that when called return the rendered result

In case the bundled JS is an IIFE or the plain object the `entryPoint` is an empty string.

```javascript
// IIFE example | bundle.js -> See vite-react example
(() => ({ renderToStringFn: (props) => "" }))() // The entryPoint is an empty string
```

```javascript
// Plain object example | bundle.js
({renderToStringFn: (props) => ""}); // The entryPoint is an empty string
```

```javascript
// IIFE variable example | bundle.js -> See webpack-react example
var SSR = (() => ({renderToStringFn: (props) => ""}))() // SSR is the entry point
```

```javascript
// Variable example | bundle.js -> See webpack-react example
var SSR = {renderToStringFn: (props) => ""}; // SSR is the entry point
```

> The export results are managed by the bundler directly.

## Example with initial props

```rust
use ssr_rs::Ssr;
use std::fs::read_to_string;

fn main() {
Ssr::create_platform();

let props = r##"{
"params": [
"hello",
"ciao",
"ใ“ใ‚“ใซใกใฏ"
]
}"##;

let source = read_to_string("./path/to/build.js").unwrap();

let mut js = Ssr::new(&source, "entryPoint").unwrap();

let html = js.render_to_string(Some(&props)).unwrap();

assert_eq!(html, "...".to_string());
}
```

## Example with actix-web

> Examples with different web frameworks are available in the examples folder.

Even though the V8 engine allows accessing the same `isolate` from different threads that is forbidden by this crate for two reasons:

1. rusty_v8 library have not implemented yet the V8 Locker API. Accessing Ssr struct from a different thread will make the V8 engine to panic.
2. Rendering HTML does not need shared state across threads.

For the reasons above parallel computation is a better choice. Following actix-web setup:

```rust
use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer};
use std::cell::RefCell;
use std::fs::read_to_string;

use ssr_rs::Ssr;

thread_local! {
static SSR: RefCell> = RefCell::new(
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
).unwrap()
)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {

Ssr::create_platform();

HttpServer::new(|| {
App::new()
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
.await
}

#[get("/")]
async fn index() -> HttpResponse {
let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap());

HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(result)
}
```

## Contributing

Any helps or suggestions will be appreciated.

Known TODOs:
- Add examples with other rust backend frameworks
- Add examples with other frontend frameworks (i.e. vue, quik, solid)
- Add benchmark setup to test against Deno and Bun
- Explore support for V8 snapshots
- Explore js copilation to WASM (i.e. [javy](https://github.com/bytecodealliance/javy))

## License

This project is licensed under the MIT License - see the LICENSE_MIT || LICENSE_APACHE file for more information.