Skip to content

ochairo/beat

beat

Pulse-native JSX framework for direct-DOM applications.
Fine-grained rendering with explicit routing, async primitives, and SSR.

npm version npm downloads CI License

Documentation

Scaffold

Start a new app with the scaffolder:

pnpm dlx @ochairo/beat-create my-app

Use the showcases template for a full-featured app with routing, crypto dashboard, kanban board, and spreadsheet:

pnpm dlx @ochairo/beat-create my-app --template showcases

That command scaffolds a Vite + TypeScript starter already configured for Beat's JSX runtime and Vite plugin.

Example

import { component } from "@ochairo/beat";
import { pulse } from "@ochairo/pulse";

const counter = pulse(0);

const onclick = (value) => {
  counter.set(counter.get() + value);
};

export const App = component(() => {
  return (
    <main>
      <header class="header">
        <h1 class="title">Counter app</h1>
      </header>
      <section class="counter">
        <button class="button" onClick={() => onclick(-1)}>
          -
        </button>
        <strong class="result">{counter}</strong>
        <button class="button" onClick={() => onclick(1)}>
          +
        </button>
      </section>
    </main>
  );
});

Server-Side Rendering

Beat's SSR uses the same component tree and router — two entry points, no new framework.

// entry-server.ts
import { Window } from "happy-dom";
import { createRouter } from "@ochairo/beat";
import { renderToString, waitForRouter } from "@ochairo/beat/server";

export async function render(url: string): Promise<string> {
  const win = new Window({ url });
  globalThis.document = win.document as unknown as Document;

  const router = createRouter({ routes, initialUrl: url });
  await waitForRouter(router, { signal: AbortSignal.timeout(5_000) });
  const html = renderToString(() => <App router={router} />);

  win.happyDOM.close();
  return `<div id="app">${html}</div>`;
}

// entry-client.ts
import { createRouter, hydrate } from "@ochairo/beat";

const router = createRouter({ routes, window });
hydrate(document.getElementById("app")!, <App router={router} />);
  • initialUrl — resolves the route on the server without reading window.location
  • waitForRouter — waits for route loaders to settle before rendering; accepts AbortSignal
  • renderToString — takes a factory () => JSX; suppresses onMount on the server
  • hydrate — single atomic swap from server HTML to live Beat tree, no blank frame

About

Pulse-native JSX for SPA applications.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors