Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 47 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Build & Test](https://github.com/eugenioenko/kasper-js/actions/workflows/node.js.yml/badge.svg)](https://github.com/eugenioenko/kasper-js/actions)

A lightweight component framework with fine-grained Signal-based reactivity. No virtual DOM. No magic compiler. Just components, signals, and surgical DOM updates.
A signal, a class, and an HTML template. That's all you need, whether you're writing the code yourself or working with an AI agent.

- **[Documentation](https://kasperjs.top)**
- **[Live Demos](https://stackblitz.com/github/eugenioenko/kasper-js/tree/main/demos)**
Expand All @@ -14,18 +14,49 @@ A lightweight component framework with fine-grained Signal-based reactivity. No

## Why Kasper.js

Fine-grained signals are not new — SolidJS pioneered direct signal-to-DOM binding, Angular adopted signals in v17, and Vue's Composition API moves in the same direction. The signal primitive itself is a solved problem.
Kasper exists because building reactive UIs shouldn't require understanding a compiler, a scheduler, a virtual DOM reconciler, and a hook dependency system.

Kasper's position is different: it is a complete, self-contained framework — components, router, slots, lifecycle, expression evaluator, template parser — in under 4000 lines of TypeScript with zero runtime dependencies. No virtual DOM, no scheduler, no compiler that requires a dedicated language server. The entire dependency graph at runtime is the framework itself.
**No build step required.** Drop a 16KB script tag and you have a complete reactive component framework — signals, router, slots, lazy loading, the works. The only thing a build pipeline adds is the `.kasper` single-file component format, which needs the Vite plugin to transform. Everything else runs directly in the browser.

The design constraints are deliberate. Directives are valid HTML attributes, so templates parse as plain HTML without editor plugins or custom syntax highlighting. The expression evaluator is a custom recursive-descent parser — not `eval`, not `new Function`, not a third-party AST library — supporting arrow functions, optional chaining, nullish coalescing, pipeline operator, and spread without pulling in a single parse dependency. Each `{{expression}}`, `@if`, and `@each` binding registers its own effect directly against the DOM node it controls. When a signal changes, only that node is updated — no component re-render, no diffing pass, no scheduler queue.
**Templates that read like HTML.** Kasper directives are standard HTML attributes: `@if`, `@each`, `@on:click`. Write `@if` where you'd write `if`, `@each` where you'd write a loop. Any template is readable by anyone who knows HTML, with no framework knowledge required. The expression evaluator is a custom recursive-descent parser, not `eval` and not `new Function`, so it works under strict Content Security Policies too.

The result is a framework you can read in an afternoon, understand completely, and trust in production. It is not trying to replace React or Angular for large teams with complex tooling requirements. It is for projects where the right answer is fewer moving parts.
**Components that clean up after themselves.** A component is a class. `this.watch()`, `this.effect()`, and `this.computed()` are all released automatically when the component is destroyed, via a single `AbortController` the class owns. No dependency arrays. No `return () => unsubscribe()`. No stale closure warnings. The component lifecycle is the class lifecycle.

Under 4000 lines of TypeScript. 95% test coverage. Zero runtime dependencies. Built to stay that way.

---

## Quick Start

**Option 1: Script tag, no build step.**

```html
<!DOCTYPE html>
<html>
<body>
<counter></counter>
<script type="module">
import { App, Component, signal } from 'https://cdn.jsdelivr.net/npm/kasper-js/dist/kasper.min.js';

class Counter extends Component {
count = signal(0);
}

Counter.template = `
<div>
<p>Count: {{count.value}}</p>
<button @on:click="count.value++">+</button>
</div>
`;

App({ root: document.body, entry: 'counter', registry: { counter: { component: Counter } } });
</script>
</body>
</html>
```

**Option 2: Vite project with single-file components.**

```bash
npm create kasper-app@latest my-project
cd my-project
Expand All @@ -37,6 +68,8 @@ npm run dev

## What it looks like

With the Vite plugin, using single-file components:

```html
<!-- Counter.kasper -->
<template>
Expand Down Expand Up @@ -81,18 +114,19 @@ App({

## Features

- **No build step** — runs from a CDN import or a local dist file. Signals, router, slots, and lazy loading all work without a bundler. The only thing a build adds is the `.kasper` single-file component format.
- **HTML-first templates** — directives are standard HTML attributes: `@if`, `@elseif`, `@else`, `@each`, `@let`, `@on:event`, `@attr`, `@class`, `@style`, `@ref`. Structural directives use DOM boundaries — lightweight comment-node bookmarks that surgically replace only their own content when dependencies change.
- **Automatic cleanup** — components are classes. `this.watch()`, `this.effect()`, and `this.computed()` are all released automatically when the component is destroyed via a shared `AbortController`. No dependency arrays, no manual unsubscribe.
- **Fine-grained signals** — `signal()`, `computed()`, `effect()`, `batch()`, `peek()`. Reactivity is tracked at the binding level, not the component level. When a signal changes, Kasper updates only the specific DOM text nodes, attributes, and structural boundaries that depend on it. Siblings, parent elements, and unrelated components are untouched.
- **Single-file components** — `<template>`, `<script>`, `<style>` in one `.kasper` file. The Vite plugin transforms them at build time into standard ES modules with zero runtime overhead. Imports in the script block are automatically hoisted into template scope via `$imports` — no need to re-declare imported functions or classes as component fields.
- **Template directives** — `@if`, `@elseif`, `@else`, `@each`, `@let`, `@on:event`, `@attr`, `@class`, `@style`, `@ref` as standard HTML attributes. Structural directives (`@if`, `@each`) use DOM Boundaries — lightweight comment-node bookmarks that surgically replace only their own content when dependencies change.
- **Event modifiers** — `@on:submit.prevent`, `@on:click.stop`, `@on:click.once`, `@on:scroll.passive`, `@on:click.capture`. All `@on:` listeners are registered via the component's `AbortController` and removed automatically on destroy.
- **Client-side router** — built-in `<router>`, `<route>`, `<guard>` components. Supports static paths, dynamic `:param` segments, catch-all `*` routes, per-route guards, and `<guard>` groups for protecting multiple routes under a single async check. Routes are declared in the template no configuration object needed.
- **Client-side router** — built-in `<router>`, `<route>`, `<guard>` components. Supports static paths, dynamic `:param` segments, catch-all `*` routes, per-route guards, and `<guard>` groups for protecting multiple routes under a single async check. Routes are declared in the template, no configuration object needed.
- **Slots** — default and named content transclusion via `<slot />` and `@slot`. Named slots use `@name` / `@slot` for attribute consistency across the framework.
- **Lifecycle hooks** — `onMount`, `onRender`, `onChanges`, `onDestroy` with clear execution ordering. `onMount` fires once after the first render with the DOM ready and `args` populated. `onRender` fires after every render cycle — after `onMount` on first render, then after each reactive update. `onChanges` fires before each reactive re-render, not on first mount. The standard `constructor` covers anything that needs to run before the framework touches the component.
- **State management** — global signals as plain ES modules. No store class, no reducers, no context API, no provider tree. Import a signal from any file; any component that reads it in its template will update automatically.
- **Manual rendering** — `this.render()` triggers a full template teardown and rebuild for cases where imperative updates are preferred over reactive bindings. Prefer signals for normal use; `render()` exists for third-party library integration and non-reactive data sources.
- **Expression language** — custom recursive-descent parser supporting arrow functions, ternary, optional chaining, nullish coalescing, pipeline operator (`|>`), spread, array/object literals, and method calls. Evaluated against component scope with fallback to `$imports` and then `window`. No `eval`, no `new Function`compatible with strict Content Security Policies out of the box.
- **Lifecycle hooks** — `onMount`, `onRender`, `onChanges`, `onDestroy` with clear execution ordering. `onMount` fires once after the first render with the DOM ready and `args` populated. `onRender` fires after every render cycle. `onChanges` fires before each reactive re-render, not on first mount.
- **State management** — global signals as plain ES modules. No store class, no reducers, no context API, no provider tree. Import a signal from any file; any component that reads it in its template will update automatically.
- **Single-file components (optional)** — with the Vite plugin, write `<template>`, `<script>`, and `<style>` in one `.kasper` file. Imports in the script block are hoisted into template scope via `$imports` automatically.
- **Expression language** — custom recursive-descent parser supporting arrow functions, ternary, optional chaining, nullish coalescing, pipeline operator (`|>`), spread, array/object literals, and method calls. No `eval`, no `new Function`, compatible with strict Content Security Policies.
- **TypeScript** — fully typed throughout. Declaration files generated separately. `.kasper` files typed via ambient module declaration.
- **Zero dependencies** — no runtime dependencies. Ships as a single ES module. Vite plugin is the only dev-time tooling required.
- **Zero dependencies** — no runtime dependencies. Ships as a single ES module.

---

Expand Down
3 changes: 2 additions & 1 deletion docs-web/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default defineConfig({
starlight({
customCss: ['./src/styles/custom.css'],
title: 'Kasper.js',
description: 'A lightweight component framework with fine-grained signal-based reactivity.',
description: 'Reactive components without the complexity. A signal, a class, and an HTML template — for developers and AI agents.',
logo: {
src: './src/assets/kasper.svg',
},
Expand All @@ -30,6 +30,7 @@ export default defineConfig({
{ label: 'Pipes', link: '/guides/pipes/' },
{ label: 'Routing', link: '/guides/routing/' },
{ label: 'Lazy Loading', link: '/guides/lazy-loading/' },
{ label: 'Without a Build Pipeline', link: '/guides/without-a-build/' },
{ label: 'AI-Driven Development', link: '/guides/agents/' },
{ label: 'Vite Integration', link: '/guides/vite/' }
]
Expand Down
22 changes: 12 additions & 10 deletions docs-web/src/content/docs/getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,28 @@ sidebar:
order: 1
---

Kasper.js is a lightweight component framework for building reactive web UIs. It sits between simple templating engines and full frameworks like Vue or React — small enough to understand completely, powerful enough for real applications.
Kasper.js is a reactive component framework that runs from a single 16KB CDN file. Templates use standard HTML attributes. Components are classes with signals that clean up after themselves.

Kasper exists because building reactive UIs shouldn't require understanding a compiler, a scheduler, a virtual DOM reconciler, and a hook dependency system.

## Core ideas

**Signals, not a virtual DOM.** Kasper uses fine-grained reactivity. When a signal changes, only the exact DOM nodes that depend on it update. No diffing, no full re-renders.
**No build step required.** The core framework is a single CDN file. Signals, router, slots, and lazy loading all work directly in the browser. The Vite plugin adds the `.kasper` single-file component format — colocated `<template>`, `<script>`, and `<style>` — when you want it.

**Valid HTML templates.** Template directives are standard HTML attributes (`@if`, `@each`, `@on:click`). Your editor won't complain, and templates are readable without knowing the framework.
**HTML-first templates.** Directives are standard HTML attributes: `@if`, `@each`, `@on:click`. Write `@if` where you'd write `if`, `@each` where you'd write a loop. Templates are readable by anyone who knows HTML.

**Single-file components.** With the Vite plugin, you write `<template>`, `<script>`, and `<style>` in a single `.kasper` file — similar to Vue SFCs but without a compiler or build-time transform for most features.
**Components that clean up after themselves.** Components are classes. `this.watch()`, `this.effect()`, and `this.computed()` all release automatically when the component is destroyed. No manual unsubscribe, no cleanup functions, no dependency arrays. The component lifecycle is the class lifecycle.

**TypeScript first.** The framework is written in TypeScript and ships declaration files. You get full type checking and autocomplete out of the box.
**Fine-grained signals.** When a signal changes, only the exact DOM nodes that depend on it update. No diffing, no full re-renders.

## When to use Kasper

- You want Vue/Angular-like reactivity without the bundle size or build complexity
- You're building a medium-sized interactive UI and plain JS is getting unwieldy
- You want to learn how signals and template compilers work from the inside
- You want to add interactivity to an existing page without setting up a build config
- You've outgrown plain JS but a full framework feels like too much
- You're building with AI tools and want code that stays readable and easy to review

## When not to use Kasper

- You need SSR Kasper is browser-only
- You need SSR (Kasper is browser-only)
- You need a large ecosystem of third-party components
- You're building a very simple static page plain HTML is fine
- You're building a simple static page (plain HTML is fine)
2 changes: 1 addition & 1 deletion docs-web/src/content/docs/guides/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Using with AI Agents
description: Using Kasper.js with AI coding agents and LLMs.
---

Kasper.js is small enough that an AI agent can hold its entire API surface in a single context window. This makes it well-suited for AI-assisted development — agents can generate correct, idiomatic Kasper code without hallucinating patterns from larger frameworks.
Kasper.js works well with AI coding agents for two reasons. First, HTML-first templates mean agents generate valid markup rather than composing JSX or managing tagged template literal syntax. Second, the entire API surface fits in a single context window, so agents can write correct, idiomatic Kasper code without guessing patterns from larger frameworks.

## llms.txt

Expand Down
Loading
Loading