npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@geajs/core

v1.0.1

Published

A lightweight, reactive JavaScript UI framework with compile-time JSX and proxy-based stores

Readme

npm version License: MIT

Gea

A batteries-included, reactive JavaScript UI framework with compile-time JSX and proxy-based stores. No virtual DOM — the Vite plugin analyzes your JSX at build time and generates surgical DOM patches that update only what changed. ~13 kb gzipped with the built-in router, ~10 kb without. Zero runtime dependencies.

Philosophy

Gea's guiding principle is that JavaScript code should be simple and understandable. A framework should not invent new programming concepts or expect you to conform to arbitrary rules. You write regular, idiomatic JavaScript — classes with state and methods, functions that return markup, getters for derived values — and Gea makes it reactive under the hood.

There are no signals, no hooks, no dependency arrays, no compiler directives, and no framework-specific primitives. The only concept Gea introduces is the Store class, which is just an ordinary class whose properties happen to be observed by a proxy. Everything else — class inheritance, method calls, property access, Array.map, ternary expressions — is standard JavaScript you already know.

The "magic" lives entirely in the build step. The Vite plugin analyzes your code at compile time, figures out which DOM nodes depend on which state, and generates the wiring. At runtime, you get clean, readable, object-oriented code that just works.

Performance

Gea is the fastest compiled UI framework — benchmarked with the js-framework-benchmark suite, the industry-standard stress test covering row creation, updates, swaps, selection, and deletion on large tables.

| Framework | Weighted geometric mean | | --- | --- | | vanillajs | 1.02 | | Gea 1.0 | 1.03 | | Solid 1.9 | 1.12 | | Svelte 5 | 1.14 | | Vue 3.6 | 1.26 | | React 19.2 | 1.50 |

Lower is better (1.00 = fastest). Gea outperforms every compiled framework while requiring zero framework-specific concepts — no signals, no hooks, no compiler directives. It's not just the DX — it's the fastest, too. Full benchmark report

Quick Start

npm create gea@latest my-app
cd my-app
npm install
npm run dev

Or add Gea to an existing Vite project:

npm install @geajs/core @geajs/vite-plugin
// vite.config.ts
import { defineConfig } from 'vite'
import { geaPlugin } from '@geajs/vite-plugin'

export default defineConfig({
  plugins: [geaPlugin()]
})

Core Concepts

Stores

A Store holds shared application state. Extend Store, declare reactive properties as class fields, add methods that mutate them, and export a singleton instance. The store instance is wrapped in a deep Proxy that tracks every mutation and batches notifications via queueMicrotask.

import { Store } from '@geajs/core'

class CounterStore extends Store {
  count = 0

  increment() { this.count++ }
  decrement() { this.count-- }
}

export default new CounterStore()

Mutate state directly — the proxy handles reactivity automatically. Array methods (push, pop, splice, sort, reverse, shift, unshift) are intercepted to produce fine-grained change events like append, reorder, and swap.

Class Components

Extend Component and implement a template() method that returns JSX. Class components inherit from Store, so they have their own reactive properties — use them when you need local, transient UI state that no other component cares about.

import { Component } from '@geajs/core'

export default class Counter extends Component {
  count = 0

  increment() { this.count++ }
  decrement() { this.count-- }

  template() {
    return (
      <div class="counter">
        <span>{this.count}</span>
        <button click={this.increment}>+</button>
        <button click={this.decrement}>-</button>
      </div>
    )
  }
}

Event handlers accept both method references (click={this.increment}) and arrow functions (click={() => this.increment()}). The compiler wires both forms to the component's event delegation system. Use method references for simple forwarding; use arrow functions when you need to pass arguments or compose logic.

Use class components when you need local state or lifecycle hooks.

Function Components

Export a default function that receives props and returns JSX. The Vite plugin converts it to a class component at build time.

export default function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>
}

Use function components for stateless, presentational UI.

Props and Data Flow

Gea's props follow standard JavaScript semantics — no framework-invented concepts like v-model, emit, or callback-based state lifting. When a parent passes data to a child component, it works exactly like passing arguments to a function in JavaScript:

  • Primitives (numbers, strings, booleans) are passed by value. The child receives a copy. Reassigning the prop in the child does not affect the parent — just like reassigning a function parameter in plain JS.
  • Objects and arrays are passed by reference. The child receives the same proxy the parent holds. Mutating the object or array in the child updates the parent's state and DOM automatically — because it's the same object.
// parent.tsx
import { Component } from '@geajs/core'

export default class Parent extends Component {
  count = 0
  user = { name: 'Alice', age: 30 }
  items = ['a', 'b']

  template() {
    return (
      <div>
        <span>{this.count}</span>
        <span>{this.user.name}</span>
        <span>{this.items.length} items</span>
        <Child count={this.count} user={this.user} items={this.items} />
      </div>
    )
  }
}
// child.tsx
export default function Child({ count, user, items }) {
  return (
    <div>
      <span>{count}</span>
      <span>{user.name}</span>
      <span>{items.length} items</span>
    </div>
  )
}

In this setup:

  • user.name = 'Bob' in the child updates both parent and child DOM — it's the same reactive proxy.
  • items.push('c') in the child updates both — same array reference.
  • count = 99 in the child updates only the child's DOM — the parent still holds the original value.

This is exactly how JavaScript works. There is no emit, no defineModel, no callback wiring for object/array mutations. The framework doesn't add a layer of indirection — it respects the language's native pass-by-value and pass-by-reference semantics.

For deep nesting (grandchild, great-grandchild, etc.), the same rules apply. As long as the same object reference is passed down, any descendant can mutate it and the change propagates up to every ancestor that observes it — because they all share the same proxy.

Computed Values

Use getters on stores for derived state. They re-evaluate on every access — the Vite plugin tracks which state paths the template reads and triggers updates when those paths change.

class TodoStore extends Store {
  todos = []
  filter = 'all'

  get filteredTodos() {
    const { todos, filter } = this
    if (filter === 'active') return todos.filter(t => !t.done)
    if (filter === 'completed') return todos.filter(t => t.done)
    return todos
  }

  get activeCount() {
    return this.todos.filter(t => !t.done).length
  }
}

JSX Syntax

Gea JSX is close to HTML. Key differences from React:

| Feature | Gea | React | | --- | --- | --- | | CSS classes | class="foo" | className="foo" | | Event handlers | click={fn} or onClick={fn} | onClick={fn} | | Input events | input={fn} or onInput={fn} | onChange={fn} | | Keyboard events | keydown={fn} or onKeyDown={fn} | onKeyDown={fn} |

Both native-style (click, change) and React-style (onClick, onChange) event attribute names are supported.

Supported event attributes: click, dblclick, input, change, keydown, keyup, blur, focus, mousedown, mouseup, submit, dragstart, dragend, dragover, dragleave, drop.

With @geajs/mobile: tap, longTap, swipeRight, swipeUp, swipeLeft, swipeDown.

Conditional Rendering

{step === 1 && <StepOne />}
{!done ? <Form /> : <Success />}

List Rendering

<ul>
  {todos.map(todo => (
    <TodoItem key={todo.id} todo={todo} onToggle={() => store.toggle(todo.id)} />
  ))}
</ul>

Always provide a key prop. Gea uses it for efficient list diffing — handling adds, deletes, reorders, and swaps without re-rendering the entire list.

Lifecycle

| Method | When called | | --- | --- | | created(props) | After constructor, before render | | onAfterRender() | After DOM insertion and child mounting | | onAfterRenderAsync() | Next requestAnimationFrame after render | | dispose() | Removes from DOM, cleans up observers and children |

DOM Helpers

| Method | Description | | --- | --- | | $(selector) | First matching descendant (scoped querySelector) | | $$(selector) | All matching descendants (scoped querySelectorAll) |

Rendering

import App from './app'

const app = new App()
app.render(document.getElementById('app'))

Components render once. Subsequent state changes trigger surgical DOM patches — not full re-renders.

Router

Gea includes a built-in client-side router for single-page applications.

Quick Example

import { Component, Link, RouterView } from '@geajs/core'
import Home from './views/Home'
import About from './views/About'
import UserProfile from './views/UserProfile'

export default class App extends Component {
  template() {
    return (
      <div class="app">
        <nav>
          <Link to="/" label="Home" />
          <Link to="/about" label="About" />
          <Link to="/users/1" label="Alice" />
        </nav>
        <RouterView routes={[
          { path: '/', component: Home },
          { path: '/about', component: About },
          { path: '/users/:id', component: UserProfile },
        ]} />
      </div>
    )
  }
}

Route Patterns

| Pattern | Example URL | Params | | --- | --- | --- | | /about | /about | {} | | /users/:id | /users/42 | { id: '42' } | | /repo/:owner/* | /repo/dashersw/src/index.ts | { owner: 'dashersw', '*': 'src/index.ts' } |

Components

  • RouterView — renders the component matching the current URL. Accepts a routes array of { path, component } objects. Supports both class and function components.
  • Link — renders an <a> tag that navigates via history.pushState instead of a full page reload. Modifier keys (Cmd/Ctrl+click) open in a new tab as expected.

Programmatic Navigation

import { router } from '@geajs/core'

router.navigate('/about')          // push new entry
router.replace('/login')           // replace current entry
router.back()                      // history.back()
router.forward()                   // history.forward()

console.log(router.path)           // '/about'
console.log(router.query)          // { q: 'hello' } for ?q=hello

Route Parameters in Components

Function components receive matched params as props:

export default function UserProfile({ id }) {
  return <h1>User {id}</h1>
}

Class components receive them via created(props) and template(props).

Related Packages

  • @geajs/mobile — Mobile UI primitives: views, navigation, gestures, sidebar, tabs, pull-to-refresh, infinite scroll.
  • @geajs/vite-plugin — Vite plugin that powers compile-time JSX transforms, reactivity wiring, and HMR.
  • create-gea — Project scaffolder: npm create gea@latest.

Documentation

Full documentation: docs

AI-Assisted Development

This repository includes agent skills that teach AI coding assistants how to work with Gea. If you use Cursor, Codex, or a similar AI-enabled editor, it will automatically pick up the skill files and understand Gea's stores, components, JSX conventions, and reactivity model — so you can scaffold and iterate on Gea apps with full AI assistance out of the box.

License

MIT — Copyright (c) 2017-present Armagan Amcalar