A modern JavaScript library for building user interfaces with generator-based state management, efficient DOM updates, and streaming server-side rendering.
- Generator-Based Components: Use JavaScript generator functions (
function*) for stateful components with built-in lifecycle management - Efficient DOM Updates: In-place DOM reconciliation minimizes DOM manipulation and maximizes performance
- Declarative JSX: Write components using familiar JSX syntax with full TypeScript support
- Performance Optimization: Built-in
memoattribute for fine-grained performance optimization - Context API: Share state across component trees without prop drilling
- Server-Side Rendering: Complete SSR solution with streaming and hydration support
- Islands Architecture: Selective hydration for maximum performance with minimal client-side JavaScript
Install Ajo using your preferred package manager:
npm install ajoCreate your first component:
import { render } from 'ajo'
// Stateless component
const Greeting = ({ name }) => <p>Hello, {name}!</p>
// Stateful component
function* Counter() {
let count = 0
const increment = () => this.next(() => count++)
while (true) yield (
<button set:onclick={increment}>
Count: {count}
</button>
)
}
// Render to DOM
render(<Counter />, document.body)Stateless Components are pure functions:
const UserCard = ({ user }) => (
<div class="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);Stateful Components use generator functions with automatic wrapper elements:
function* TodoList() {
let todos = []
const addTodo = text => this.next(() => todos.push({ id: Date.now(), text }))
while (true) yield (
<>
<input set:onkeydown={e => e.key === 'Enter' && addTodo(e.target.value)} />
<ul>
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
</>
)
}The generator structure provides a natural mental model:
- Before the loop: Persistent state, handlers, and utilities
- Inside the loop: Re-executed on each render for derived values
function* ShoppingCart(args) {
// Persistent state (like useState)
let items = []
// Persistent handlers (like useCallback)
const addItem = product => this.next(() => items.push(product))
// Main render loop
while (true) {
// Derived values computed fresh each render
const total = items.reduce((sum, item) => sum + item.price, 0)
const itemCount = items.length
yield (
<>
<h2>Cart ({itemCount} items)</h2>
<p>Total: ${total}</p>
{/* ... */}
</>
)
}
}key: List reconciliationref: DOM element accessmemo: Performance optimizationskip: Third-party DOM managementset:: Direct property settingattr:: Force HTML attributes
function* MapComponent(args) {
let mapRef = null
while (true) yield (
<div
ref={el => {
if (el && !mapRef) {
mapRef = el
// Third-party map library controls this DOM
new GoogleMap(el, args.config)
}
}}
skip
>
{/* Google Maps API manages children elements */}
</div>
)
}import { render } from 'ajo/html'
const html = render(<App />)import { stream } from 'ajo/stream'
for await (const chunk of stream(<App />)) response.write(chunk)- Use fragments in stateful components to avoid unnecessary DOM nesting
- Don't destructure props in function signatures - use
argsparameter or destructure inside the render loop - Leverage the generator structure for natural state and lifecycle management
- Use TypeScript for enhanced developer experience
Renders JSX into a DOM container element. When child and ref are provided, only nodes between them (inclusive of child, exclusive of ref) are reconciled, leaving the rest of the container untouched.
import { render } from 'ajo'
render(<App />, document.getElementById('root'))
// Update only the <main> region without touching header/footer
const container = document.body
render(<main>Updated</main>, container, container.querySelector('main'), container.querySelector('footer'))JSX factory function (rarely used directly).
JSX fragment component for rendering multiple elements without a wrapper.
const List = () => (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
)Stateful components have access to several instance methods through this:
Triggers a re-render of the component by advancing to the next yield point. Optionally accepts a callback function that receives the component's current props/args as the first parameter.
function* Counter(args) {
let count = 0
const increment = () => {
// Simple re-render
this.next(() => count++)
}
const incrementByStep = () => {
// Access current props in callback
this.next(({ step }) => count += step)
}
// ... rest of component
}Throws an error that can be caught by parent error boundaries.
Terminates the generator and triggers cleanup (rarely used directly).
Creates a context for sharing data across component trees.
import { context } from 'ajo/context'
const ThemeContext = context('light')
// Set value
ThemeContext('dark')
// Get value
const theme = ThemeContext() // 'dark'Renders JSX to an HTML string for static site generation.
import { render } from 'ajo/html'
const html = render(<HomePage title="Welcome" />)Low-level HTML streaming function with custom hooks.
Renders components to an async stream for progressive SSR.
import { stream } from 'ajo/stream'
for await (const chunk of stream(<App />)) response.write(chunk)Client-side function for applying streamed patches during hydration.
import { hydrate } from 'ajo/stream'
window.$stream = { push: hydrate }import type {
Args,
Children,
Stateful,
StatefulArgs,
StatefulElement,
Stateless,
WithChildren,
} from 'ajo'
// Base types
type Args = Record<string, unknown>
type Children = unknown
// Component types
type Stateless<TArguments extends Args = {}> = (args: TArguments) => Children
type Stateful<TArguments extends Args = {}, TTag extends Tag = 'div'> = {
(this: StatefulElement<TArguments, TTag>, args: StatefulArgs<TArguments, TTag>): Iterator<Children>
is?: TTag
attrs?: Record<string, unknown>
args?: Partial<TArguments>
src?: string // for islands
fallback?: Children // for async components
}
// Stateful component instance (wrapper element + control methods)
type StatefulElement<TArguments, TTag> = HTMLElement & {
next: (fn?: (this: StatefulElement<TArguments, TTag>, args: StatefulArgs<TArguments, TTag>) => void) => void
throw: (value?: unknown) => void
return: () => void
}
// Helper for components with children
type WithChildren<T extends Args = {}> = T & { children?: Children }
// Usage examples
type CardArgs = WithChildren<{ title: string }>
const Card: Stateless<CardArgs> = ({ title, children }) => ...
type ModalArgs = WithChildren<{ open: boolean }>
const Modal: Stateful<ModalArgs> = function* (args) { ... }For comprehensive guides, advanced patterns, and detailed examples, see documentation.md.
ISC © Cristian Falcone
