Skip to content

Shane-IL/pulse

Repository files navigation

Pulse

Note: This is an experiment in iterative AI-assisted development — a "see if I could" project more than anything production-grade. The goal was to build something resembling pre-hooks React but without the lifecycle method mess: just pure render functions, external stores, and a clean connect API. Every commit was planned and written collaboratively with Claude Code.

A render-driven UI framework with virtual DOM and immutable stores. Like React, but with no hooks — state stores are first-class citizens and components are pure render functions.

Why Pulse?

  • No hooks. All state lives in external stores. Components are (props) => VNode.
  • Stores are first-class. Create, import, and share stores anywhere. They're framework-agnostic. Per-component local stores for UI-only state.
  • Render-driven. Describe what the UI looks like for a given state. Pulse handles the rest.
  • Built-in routing. Store-based client-side router — routes are just state.
  • Middleware. Pluggable middleware for logging, action history, and custom logic.
  • Devtools. Built-in browser devtools panel — store inspector, action replay, time-travel.
  • Tiny. ~7 KB gzipped core, ~9 KB devtools. Zero runtime dependencies.

Quick Start

npm install @shane_il/pulse

Configure JSX automatic runtime (Vite, TypeScript, or Babel):

Vite (vite.config.js):

export default defineConfig({
  esbuild: {
    jsx: 'automatic',
    jsxImportSource: '@shane_il/pulse',
  },
});

TypeScript (tsconfig.json / jsconfig.json):

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@shane_il/pulse"
  }
}

No need to import h in every file — the bundler handles it automatically.

Classic mode (manual h import)
{ "compilerOptions": { "jsx": "react", "jsxFactory": "h", "jsxFragmentFactory": "Fragment" } }

Then import { h } from '@shane_il/pulse' in every JSX file.

Example

import { createStore, connect, render } from '@shane_il/pulse';

// 1. Create a store
const counterStore = createStore({
  state: { count: 0 },
  actions: {
    increment: (s) => ({ count: s.count + 1 }),
    decrement: (s) => ({ count: s.count - 1 }),
  },
});

// 2. Write a pure component
function Counter({ count }) {
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => counterStore.actions.increment()}>+</button>
      <button onClick={() => counterStore.actions.decrement()}>-</button>
    </div>
  );
}

// 3. Connect it to the store
const ConnectedCounter = connect.from(counterStore, 'count')(Counter);
// or: connect({ count: counterStore.select(s => s.count) })(Counter)

// 4. Render (defaults to #app)
render(<ConnectedCounter />);

How It Works

Store dispatch → Notify subscribers → Schedule re-render → Expand components
    → Diff VDOM → Patch DOM (single paint)
  1. Stores hold immutable state. Actions produce new state via pure functions.
  2. connect() subscribes components to store slices via selectors.
  3. When a store changes, connected components whose selected values differ are scheduled for re-render.
  4. The scheduler batches multiple store updates in the same tick into a single render pass.
  5. The VDOM engine diffs the old and new virtual trees and patches only the changed DOM nodes.

Live Examples

  • Todo App — stores, connected components, routing, devtools
  • Weather Dashboard — async actions, multi-store connect, logger middleware

Documentation

  • Getting Started — installation, JSX setup, first app, project structure
  • StorescreateStore, actions, selectors, subscriptions, derived state
  • Components — pure components, connect(), keyed lists, error boundaries
  • LifecycleonMount, onUpdate, onDestroy, onError, cleanup functions
  • RoutingcreateRouter, Route/Link/Redirect, path matching, nested routes
  • Middlewarelogger, actionHistory, createAsyncAction, custom middleware
  • Devtools — browser panel, store inspector, time-travel, component tracking
  • Architecture — how the VDOM engine works under the hood

Development

npm install
npm test          # 313 tests (vitest)
npm run typecheck # tsc --noEmit
npm run lint      # eslint
npm run build     # vite lib mode → dist/

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors