ssr-rs

repository·main·Indexed 42 days ago

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

A lightweight Rust crate for server-side rendering (SSR) that embeds the V8 JavaScript engine to parse and evaluate bundled JavaScript files from tools like Vite, Webpack, and Rspack to return rendered HTML strings. Version 0.8.3.

Tokens
6.4K
Snippets
20
Records
27
Agent score
53%

What's inside ssr-rs

  1. React + TypeScript + Vite Template Overview

    main

    This template provides a minimal setup for running React with TypeScript in a Vite environment. It includes Hot Module Replacement (HMR) and a baseline ESLint configuration.

    It supports two official Vite plugins for Fast Refresh:

    • @vitejs/plugin-react: Uses Babel for Fast Refresh.
    • @vitejs/plugin-react-swc: Uses SWC for Fast Refresh.
  2. How Vite Solid.js SSR works with ssr-rs

    main

    The integration between Solid.js, Vite, and the ssr-rs Rust library follows this lifecycle:

    1. Client Build: A standard Vite build generates the client-side bundle for browser execution.
    2. Server Build: Running build:rust-ssr creates a specialized server bundle that exports a renderToString function.
    3. Rust Integration: The Rust server (implemented in server.rs) loads this server bundle and invokes the renderToString function to generate the initial HTML.
    4. Hydration: Once the HTML is delivered to the browser, the client-side JavaScript (from src/main.tsx) executes to hydrate the application, making it interactive.

    Key Files in this Architecture

    • src/server-entry.tsx: The server-side entry point that exports the required render function.
    • src/main.tsx: The client-side entry point responsible for hydration.
    • server.rs: The Rust server implementation that serves the rendered HTML.
    • vite.config.ts: The Vite configuration containing specific SSR settings.
  3. Understanding the `entryPoint` parameter

    main

    The entryPoint argument in Ssr::new tells the engine where to find the rendering logic within your bundled JavaScript. It can be:

    • An empty string (""): Use this if your bundle is an IIFE (Immediately Invoked Function Expression) that returns the required object, or if the bundle is just a plain object.
    • A variable name: Use the name of the variable that holds the object containing your rendering functions (e.g., "SSR").

    Examples of entryPoint mapping:

    IIFE (entryPoint = "")

    (() => ({ renderToStringFn: (props) => "<html></html>" }))()

    Plain Object (entryPoint = "")

    ({renderToStringFn: (props) => "<html></html{}"})

    IIFE Variable (entryPoint = "SSR")

    var SSR = (() => ({renderToStringFn: (props) => "<html></html>"}))()

    Variable (entryPoint = "SSR")

    var SSR = {renderToStringFn: (props) => "<html></html>"};
  4. Set up the Svelte-Salvo-SSR-template

    main

    To use this template for Svelte Server-Side Rendering (SSR) with Salvo-rs and Vite, follow these steps in order:

    1. Install dependencies: Use pnpm to install the necessary npm packages.
    2. Build Client Assets: Use Vite to build the Svelte client-side JavaScript and CSS using the specific client configuration.
    3. Build SSR Assets: Use Vite to build the Svelte SSR-specific JavaScript using the SSR configuration.
    4. Start the Server: Run the Rust server using cargo.
    # 1. Install dependencies
    pnpm install
    
    # 2. Build client JS and CSS
    pnpx vite build --config vite.client.config.js
    
    # 3. Build SSR JS
    pnpx vite build --config vite.ssr.config.js
    
    # 4. Run Rust server
    cargo run
  5. Explore SSR-RS implementation examples

    main

    The examples/ directory contains various implementations of Server-Side Rendering (SSR) using different frontend bundlers and backend frameworks. Use these as templates for your own projects.

    React Implementations

    • webpack-react: The most comprehensive React example. It demonstrates:
      • Server-side props handling
      • React hydration
      • Asset generation
    • vite-react: A simplified example showcasing a base bundler configuration using Vite.
    • rspack-react-ts-starter: A simplified example showcasing a base bundler configuration using Rspack.

    Note: If you are using Vite or Rspack and need advanced features like hydration or server-side props, you can port them from the webpack-react example.

    Rust Backend Implementations

    At the root of the examples folder, you can find simple configurations demonstrating how to integrate SSR with common Rust backend frameworks.

  6. Setup the Vite Solid.js SSR Example

    main

    To set up the Vite Solid.js SSR environment, follow these steps to install dependencies and build both the client and server bundles:

    1. Install dependencies using your preferred package manager:

      npm install
      # or
      yarn install
      # or
      pnpm install
    2. Build the client-side bundle:

      npm run build
      # or
      yarn build
      # or
      pnpm build
    3. Build the server-side bundle for SSR (this creates the bundle used by the Rust server):

      npm run build:rust-ssr
      # or
      yarn build:rust-ssr
      # or
      pnpm build:rust-ssr
    npm install
    npm run build
    npm run build:rust-ssr
  7. Run benchmarks with Actix-rs + ssr_rs

    main

    To benchmark the ssr_rs implementation using the Actix-rs server, navigate to the examples/webpack-react directory, install dependencies, build the SSR assets, and run the cargo example.

    Note: These benchmarks refer to ssr_rs v0.3.0 and use the wrk benchmarking tool.

    $ cd examples/webpack-react
    $ pnpm i && pnpm build:ssr
    $ cargo run --example webpack
  8. Enable type-aware lint rules in ESLint

    main

    For production applications, it is recommended to enable type-aware linting to improve code quality. To do this, follow these steps:

    1. Update parserOptions: Configure the top-level parserOptions in your ESLint configuration to include your tsconfig files.
    2. Update TypeScript Plugins: Replace plugin:@typescript-eslint/recommended with either plugin:@typescript-eslint/recommended-type-checked or plugin:@typescript-eslint/strict-type-checked.
    3. Add Stylistic Rules (Optional): Add plugin:@typescript-eslint/stylistic-type-checked.
    4. Add React Plugin: Install eslint-plugin-react and add plugin:react/recommended and plugin:react/jsx-runtime to your extends list.
    export default {
      // other rules...
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        project: ['./tsconfig.json', './tsconfig.node.json'],
        tsconfigRootDir: __dirname,
      },
    }
  9. Build the React SSR client and server bundles

    main

    To prepare the distribution files required for the ssr-rs server, you must build both the client-side and server-side bundles. This project uses Webpack for production builds. Run the following command to build both simultaneously:

    npm run build:all
    # or
    yarn build:all

    Individual build commands are also available:

    • npm run build:client: Builds only the client bundle using webpack.client.build.js.
    • npm run build:ssr: Builds only the SSR bundle using webpack.ssr.js.
    npm run build:all
  10. Run the SSR Server

    main

    Once the bundles are built, you can run the SSR server using Cargo. This requires the ssr-rs library to be available in your Rust project.

    1. Build and run the Rust server:

      cargo run --example vite-solid
    2. Access the application: Visit http://127.0.0.1:8080 in your browser to view the SSR-rendered Solid.js application.

    cargo run --example vite-solid
  11. Integrate ssr_rs with actix-web

    main

    Because the underlying rusty_v8 engine does not yet support the V8 Locker API, the Ssr struct cannot be safely shared across threads. To use ssr_rs in a multi-threaded web server like actix-web, you should use thread_local! to ensure each thread has its own instance of the SSR engine.

    Implementation Pattern

    1. Initialize the platform with Ssr::create_platform().
    2. Define a thread_local! block containing a RefCell<Ssr>.
    3. Inside your request handler, use SSR.with(|ssr| ...) to access and borrow the engine for rendering.
    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<Ssr<'static, 'static>> = 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)
    }