jsxrs 0.1.4

A Rust library for rendering JSX/TSX to complete HTML documents at build-time or server-side.
Documentation

jsxrs

A Rust library for rendering JSX/TSX to complete HTML documents at build-time or server-side.

Overview

jsxrs parses JSX/TSX files and renders them to full HTML documents. It leverages SWC for high-performance parsing and supports modern JSX features including components, props, fragments, and TypeScript.

Features

  • JSX & TSX Support - Parse and render both JavaScript and TypeScript JSX files
  • Props & Expressions - Pass runtime values to components via props
  • Component Imports - Import and render other JSX/TSX components
  • Special <Head> Component - Add elements to the document <head> (title, meta, link, style, script)
  • Tailwind CSS Integration - Automatically generate CSS from Tailwind class names
  • TypeScript Codegen - Generate Rust structs from TypeScript interfaces
  • XSS Protection - Automatic HTML escaping for user content

Installation

Add jsxrs to your Cargo.toml:

[dependencies]
jsxrs = "0.1"

Usage

Basic Rendering

use jsxrs::{render_string, RenderConfig};
use serde_json::json;

let source = r#"
export default function Greeting(props) {
  return <div>Hello, {props.name}!</div>;
}
"#;

let config = RenderConfig::default();
let props = json!({"name": "World"});
let html = render_string(source, "page.jsx", &props, &config).unwrap();

println!("{}", html);

Rendering from File

use jsxrs::{render_file, RenderConfig};
use serde_json::json;
use std::path::PathBuf;

let path = PathBuf::from("src/page.tsx");
let config = RenderConfig {
    pretty: true,
    base_dir: Some(PathBuf::from("src")),
    ..Default::default()
};
let html = render_file(&path, &json!({}), &config).unwrap();

Configuration

use jsxrs::{HeadElement, RenderConfig};

let config = RenderConfig {
    pretty: true,                          // Format output with indentation
    base_dir: Some(PathBuf::from(".")),    // Base directory for component resolution
    head_elements: vec![
        HeadElement::Title("My Page".to_string()),
        HeadElement::Meta {
            name: "description".to_string(),
            content: "A wonderful page".to_string()
        },
    ],
    tailwind: false,                       // Enable Tailwind CSS generation
    fragment: false,                       // Return fragment (body HTML only) instead of full document
};

Tailwind CSS

Enable Tailwind CSS generation to automatically collect and generate CSS from class names:

let config = RenderConfig {
    tailwind: true,
    ..Default::default()
};

The library will extract all class attributes and generate the corresponding Tailwind CSS.

Using the <Head> Component

Add elements to the document head using the special <Head> component:

export default function Page() {
  return (
    <>
      <Head>
        <title>My Page</title>
        <meta name="description" content="Welcome" />
        <link rel="stylesheet" href="/styles.css" />
      </Head>
      <div>Page content here</div>
    </>
  );
}

Fragment Rendering (HTMX)

When using jsxrs as an HTMX backend, return a full HTML document for the initial page load and an HTML fragment for subsequent HTMX requests (identified by the HX-Request header).

Set fragment: true to skip the document wrapper and return only the body HTML:

use jsxrs::{render_string, RenderConfig};
use serde_json::json;

// In your request handler:
let is_htmx_request = request.headers().get("HX-Request").is_some();

let config = RenderConfig {
    fragment: is_htmx_request,
    ..Default::default()
};

let html = render_string(source, "page.jsx", &props, &config).unwrap();
// Returns full document for normal requests, body HTML only for HTMX requests

In fragment mode:

  • The <!DOCTYPE html><html><head>...</head><body>...</body></html> wrapper is omitted
  • <Head> component content is ignored (already loaded by the full-page response)
  • Tailwind CSS <style> tags are skipped (already loaded by the full-page response)

Component Imports

Import and render other components:

import Button from './components/button';

export default function Page() {
  return (
    <div>
      <Button label="Click me" />
    </div>
  );
}

Ensure base_dir is configured in RenderConfig for import resolution.

TypeScript Codegen

Generate Rust structs from TypeScript interfaces:

use jsxrs::codegen;

codegen::generate_types(
    &["src/types.tsx"],
    "src/generated/types.rs",
).unwrap();

Given a TypeScript interface:

interface User {
  id: number;
  name: string;
  email?: string;
}

This generates:

use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: f64,
    pub name: String,
    pub email: Option<String>,
}

API Reference

render_string

Renders a JSX/TSX source string to a complete HTML document.

pub fn render_string(
    source: &str,
    file_name: &str,
    props: &Value,
    config: &RenderConfig,
) -> Result<String, JsxrsError>

render_file

Renders a JSX/TSX file to a complete HTML document.

pub fn render_file(
    path: &Path,
    props: &Value,
    config: &RenderConfig,
) -> Result<String, JsxrsError>

generate_types

Generates Rust struct definitions from TypeScript interface declarations.

pub fn generate_types(
    tsx_paths: &[impl AsRef<Path>],
    output_path: &Path,
) -> Result<(), JsxrsError>

RenderConfig

Configuration for the rendering pipeline:

Field Type Description
pretty bool Format output with indentation
base_dir Option<PathBuf> Base directory for component resolution
head_elements Vec<HeadElement> Static head elements to include
tailwind bool Enable Tailwind CSS generation
fragment bool Return body HTML only, skipping the full document wrapper

HeadElement

Elements that can be added to the document head:

  • Title(String) - Page title
  • Meta { name, content } - Meta tags
  • Link { rel, href } - Link tags
  • Style(String) - Inline styles
  • Script(String) - Inline scripts

Supported JSX Features

  • HTML elements (div, span, p, etc.)
  • Self-closing tags (img, br, hr, input, etc.)
  • JSX Fragments (<>...</>)
  • Props and expressions ({props.value}, {1 + 1})
  • Type annotations in TSX files
  • Arrow function and function declarations
  • Component composition

Security

jsxrs automatically escapes HTML content to prevent XSS attacks. User-supplied content in text nodes and attributes is properly escaped.