npm package: @lms5400/babel-plugin-rsx
Description: Babel plugin for RSX Components
Latest version: 1.2.0

# RSX — On‑Demand Rendered Components

**RSX** is a component model designed for **real‑time and imperative workloads** where React’s state, effects, and memoization layers become accidental complexity.

> **RSX = On‑Demand Rendered Components**
> You decide _when_ rendering happens.

RSX components look familiar to React developers, but behave very differently under the hood. They eliminate the need for hooks entirely—**including `useState`, `useEffect`, `useCallback`, `useMemo`, and `useRef`**—by making rendering an explicit operation instead of a side‑effect of state changes.

---

## Why RSX Exists

React excels at **declarative UI driven by application state**. But many real‑time systems are **not state‑driven UIs**:

- Timers, clocks, and stopwatches
- Game loops and input polling
- Media processing (audio/video analysis)
- Hardware and device bridges
- Animation engines and render pipelines
- High‑frequency data streams

In these domains, React often forces developers into patterns like:

- Deep `useEffect` chains
- `useCallback` for “stability”
- `useMemo` to fight re‑execution
- `useRef` as escape hatches
- Logic hidden inside custom hooks

The result is **indirect control**, harder reasoning, and fragile behavior.

RSX flips this model.

### Basic Structure

```jsx
export default function Example({ view, update, destroy, render, props }) {
  // Everything in this scope runs exactly once on
  // mount and persists for the duration of the component.

  // Initial props snapshot (mount only)
  const initialProps = props;

  // Persistent state
  let value = 0;

  function increment() {
    value++;
    render(); // explicit re-render
  }y

  view((props) => {
    // The render function
    return <button onClick={increment}>{value}</button>;
  });

  update((prevProps, nextProps) => {
    // runs when props change
  });

  destroy(() => {
    // runs once on unmount
  });
}
```

---

## How to Setup

### 1. Install the Package

```bash
npm install @lms5400/babel-plugin-rsx
```

> **Note:** This plugin requires `@babel/core` and `@babel/preset-react` as peer dependencies. Most React projects using Vite or Next.js already have these bundled internally. If you're using Webpack or a custom setup, check if they exist in your `node_modules/`. If not, install them:
>
> ```bash
> npm install --save-dev @babel/core @babel/preset-react
> ```

### 2. Configure Babel

Add the plugin to your Babel configuration. Choose the setup that matches your bundler:

#### Option A: Vite _(recommended)_

```typescript
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { rsxTypeStripPlugin, rsxVitePlugin } from "@lms5400/babel-plugin-rsx/vite";

export default defineConfig({
  resolve: {
    extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".rsx"],
  },
  plugins: [
    // Plugin order matters:
    rsxTypeStripPlugin(),  // 1. Strip TypeScript (optional - only if using TS in .rsx)
    rsxVitePlugin(),       // 2. RSX → React transformation
    react({                // 3. JSX → JS
      include: /\.(jsx|tsx)$/  // note: no need to include rsx because rsxVitePlugin already transforms JSX in .rsx files
    }),
  ],
});
```

#### Option B: Webpack (Recommended for Webpack users)

Use the dedicated RSX loader which handles TypeScript stripping and RSX transformation:

```javascript
// webpack.config.js
module.exports = {
  resolve: {
    extensions: [".js", ".jsx", ".ts", ".tsx", ".rsx"],
  },
  module: {
    rules: [
      // RSX files - use dedicated loader
      {
        test: /\.rsx$/,
        exclude: /node_modules/,
        use: "@lms5400/babel-plugin-rsx/webpack-loader",
      },
      // Regular JS/TS files
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
    ],
  },
};
```

#### Option C: Babel Config (works with any bundler)

Create or update your `babel.config.js`:

```javascript
module.exports = {
  presets: [
    ["@babel/preset-react", { runtime: "automatic" }],
    "@babel/preset-typescript", // Required for TypeScript in .rsx files
  ],
  plugins: ["@lms5400/babel-plugin-rsx"],
};
```

> **Note:** This approach requires `@babel/preset-typescript` to strip TypeScript before RSX transformation.

### 3. [Highly Recommended] – Configure ESLint for RSX Files

For RSX-specific linting and recommended VS Code lint configuration, use the official plugin:

https://github.com/LMS007/eslint-plugin-rsx

#### Files to edit/create:
- `eslint.config.js`
- `vscode/settings.json`

### 4. [Optional] - Add TypeScript Support

RSX fully supports TypeScript in `.rsx` files. Follow these steps to enable it:

#### Step 1: Add RSX types to `tsconfig.json`

```jsonc
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "types": ["@lms5400/babel-plugin-rsx/types"]
  },
  "include": ["src/**/*", "src/**/*.rsx"]  // Include .rsx files
}
```

This provides:
- Type declarations for `*.rsx` imports (so TypeScript understands `import X from "./X.rsx"`)
- The `Ctx` type for typing RSX component parameters

#### Step 2: Configure VS Code

Create or update `.vscode/settings.json` to treat `.rsx` files as TypeScript React:

```jsonc
{
  "files.associations": {
    "*.rsx": "typescriptreact"
  }
}
```

This enables full IDE support: syntax highlighting, IntelliSense, error checking, and go-to-definition.

#### Step 3: Update ESLint for TypeScript (if using ESLint)

If you followed Step 3 above to configure ESLint with `@lms5400/eslint-plugin-rsx`, update your `eslint.config.js` to use the TypeScript parser for `.rsx` files:

```javascript
import tseslint from "typescript-eslint";

export default [
  // ... other configs
  {
    files: ["**/*.rsx"],
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: {
        ecmaFeatures: { jsx: true },
      },
    },
    // ... rules
  },
];
```

---

## Create Your First RSX Component

Create a file with the `.rsx` extension:

> Note: The `RSX()` wrapper is necessary when mixing TypeScript and RSX. See [TS_Semantics.md](docs/TS_Semantics.md) for the full rationale and a nested components.

```typescript
// Counter.rsx
import type { RSX } from "@lms5400/babel-plugin-rsx/types";

interface CounterProps {
  name: string;
}

export default function RSX<CounterProps>(Counter({ view, render }) {
  let count = 0;

  function increment() {
    count++;
    render();
  }

  view((props) => (
    <>
      <label>{props.name}</label>
      <button onClick={increment}>Count: {count}</button>
    </>
  ));
})
```

The RSX parameter uses the `Ctx<P>` generic type with the following structure:
- `props` - your component's props (type `P`)
- `view(cb)` - register view callback, receives `(props: P) => ReactNode`
- `update(cb)` - register update callback, receives `(prev: P, next: P) => void`
- `render()` - trigger a re-render
- `destroy(cb)` - register cleanup callback

### Use It in Your React App

```tsx
import Counter from "./Counter.rsx";

function App() {
  return (
    <div>
      <h1>My App</h1>
      <Counter name="Count clicks" />
    </div>
  );
}
```


## The Core Idea: Render On Demand

In RSX:

- **Rendering is explicit**
- **Logic runs once**, not on every re‑render
- **Local variables behave like real variables**
- **You call `render()` only when output must change**

There is no implicit reactivity.

> If nothing meaningful changed, nothing renders.

This matches how real‑time systems already work.

---

## Why RSX Needs No Hooks (Including `memo`)

Hooks exist to compensate for **React’s re‑execution model**:

| React Hook    | Why It Exists                 |
| ------------- | ----------------------------- |
| `useState`    | Triggers renders indirectly   |
| `useEffect`   | Run code _after_ render       |
| `useCallback` | Prevent identity churn        |
| `useMemo`     | Prevent recomputation         |
| `useRef`      | Persist values across renders |

RSX removes the root cause:

- The component function does **not re‑execute** on updates
- Variables persist naturally
- Side‑effects are just normal code
- All data can be mutated
- Updates are intentional

Because nothing re‑runs implicitly:

- `memo` is unnecessary
- `callback` stability is irrelevant
- dependency arrays disappear

 Mutating data in RSX is safe because the framework does not rely on immutability or identity checks to decide when to update the UI. You explicitly control when rendering happens, so there’s no hidden scheduling, diffing, or replay that could be confused by in-place changes and therefore there is nothing to “optimize around.” or “safeguard.”

---

## Why `render()` in RSX Is Faster

When you call `render()` in RSX, you skip the overhead that React incurs on every update:

| React (on every render)                      | RSX (on `render()`)                |
| -------------------------------------------- | ---------------------------------- |
| Re‑executes entire component function        | Only re‑runs the `view()` callback |
| Runs all hooks sequentially                  | No hooks to run                    |
| Compares dependency arrays (`useMemo`, etc.) | No dependency tracking             |
| Recreates closures and inline functions      | Functions created once, persist    |
| Checks `memo` wrappers for prop changes      | No memo wrappers needed            |
| Schedules effects, flushes effect cleanup    | Side‑effects are imperative code   |
| Data needs to be copied for immutability     | No need to copy, just mutate in place |

### The Real Cost of Hooks

In React, even a "simple" component with a few hooks pays these costs **every render**:

```jsx
function ReactTimer() {
  const [time, setTime] = useState(0);           // hook 1
  const intervalRef = useRef(null);              // hook 2
  const start = useCallback(() => { ... }, []);  // hook 3 + dep check
  const stop = useCallback(() => { ... }, []);   // hook 4 + dep check

  useEffect(() => { ... }, [time]);              // hook 5 + dep check + cleanup

  return <div>{time}</div>;
}
```

Every frame: 5 hook calls, 3 dependency array comparisons, potential effect scheduling.

### RSX Equivalent

```jsx
function RsxTimer({ view, render }) {
  let time = 0;
  let intervalId = null;

  function start() {
    intervalId = setInterval(() => {
      time++;
      render();
    }, 1000);
  }
  function stop() {
    clearInterval(intervalId);
  }

  view(() => <div>{time}</div>);
}
```

On `render()`: just the `view()` callback runs. No hook overhead. No comparisons.

### The performance gap widens with:

- **High‑frequency updates** (60fps animations, real‑time data)
- **Many instances** (100+ timers, particles, list items)
- **Complex hook graphs** (effects depending on effects)

---

## What Types of React Components Are Perfect for RSX conversion?

RSX shines where **imperative control beats declarative diffusion**.

### Ideal Use Cases

- **Timers & schedulers**
- **Animation loops** (`requestAnimationFrame`)
- **Gamepad, MIDI, HID, or sensor input**
- **Audio / video analysis**
- **Streaming or polling systems**
- **Canvas / WebGL / WebGPU renderers**
- **Electron IPC bridges**
- **High‑frequency UI updates**

If a component:

- Does work continuously
- Talks to hardware or external systems
- Maintains internal mutable state
- Uses many `useRefs` as escape hatches and `useCallbacks` for stabilization
- Should _not_ re‑run on every parent render
- Has tangled or deeply nested `useEffect` chains

…it’s likely a strong RSX candidate.

---

## Designed to Be Used _With_ React

RSX is **not a replacement for React**.

It is meant to be **sprinkled into existing JSX projects**:

- Use React for layouts, routing, forms, and data fetching
- Use RSX for hot paths and real‑time subsystems

```jsx
import ReactComponent from 'ReactComponent.tsx'
import RsxComponent from 'RsxComponent.rsx'
...
<div>
 <ReactComponent name={hello world}/>
 <RsxComponent name={hello world}/>
</div>
```

RSX components:

- Mount inside normal React trees
- Coexist with JSX components
- Do not affect React’s mental model elsewhere

---

## Works Natively with TypeScript

RSX supports TypeScript end‑to‑end:

- Typed props
- Typed local state
- Typed helpers and APIs
- Full IDE inference

Because RSX avoids hook indirection:

- Types are flatter
- Control flow is obvious
- Fewer generics and wrapper types

The result is **clearer typings with less ceremony**.

---

## Easier to Read, Easier for AI to Write

RSX code is:

- Linear
- Explicit
- Single‑pass

There are no hidden lifecycles, no dependency arrays, and no hook rules.

This makes RSX:

- Easier for humans to reason about
- **Far less error‑prone when generated by AI**

AI systems struggle with:

- Hook ordering rules
- Dependency correctness
- Memoization correctness
- Effect timing

RSX removes these failure modes entirely.

> What you see is what runs.

---

## Mental Model Summary

| React                  | RSX                      |
| ---------------------- | ------------------------ |
| State‑driven           | Event‑driven             |
| Implicit re‑execution  | Explicit rendering       |
| Hooks manage lifetimes | Code manages itself      |
| Optimization via memo  | No optimization required |

---

## When to Avoid RSX

RSX is not a general replacement for React. Prefer JSX + hooks when:

- The component is mostly **declarative UI** (forms, lists, layout, content)
- UI is **derived from app or server state**
- Updates are **infrequent or user-driven**
- The component is meant to be **highly composable or generic**
- React’s **conventions and consistency** are more important than control

## Further Reading

- [RSX Component Execution & Lifecycle Specification](docs/RSX_Semantics.md)
- [TypeScript details](docs/TS_Semantics.md)