https://github.com/hrutavmodha/twiggle
A basic frontend framework for building UI through JSX
https://github.com/hrutavmodha/twiggle
framework frontend jsx reactive web
Last synced: 6 months ago
JSON representation
A basic frontend framework for building UI through JSX
- Host: GitHub
- URL: https://github.com/hrutavmodha/twiggle
- Owner: hrutavmodha
- License: mit
- Created: 2025-09-12T17:18:27.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-10-05T13:43:57.000Z (6 months ago)
- Last Synced: 2025-10-05T15:24:38.046Z (6 months ago)
- Topics: framework, frontend, jsx, reactive, web
- Language: TypeScript
- Homepage:
- Size: 224 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# Twiggle
Tiny, focused front-end primitives: a custom JSX runtime, minimal DOM renderer, a tiny router, and a reactive state primitive.
[](#license)
[](https://www.npmjs.com/package/twiggle)
> **Note:** Twiggle is currently in its early stage and should not be used in the production apps.
Maintainer: Hrutav Modha
License: MIT
---
## Table of Contents
- [Why Twiggle](#why-twiggle)
- [Installation](#installation)
- [Quick start](#quick-start)
- [Core concepts](#core-concepts)
- [JSX & createElement](#jsx--createelement)
- [Renderer](#renderer)
- [State primitive](#state-primitive)
- [Router](#router)
- [Vite plugin](#vite-plugin)
- [API reference](#api-reference)
- [Development](#development)
- [Contributing](#contributing)
- [FAQ](#faq)
- [License](#license)
---
## Why Twiggle
Twiggle is intentionally minimal. It focuses on clarity and a small runtime surface for use-cases like:
- Learning how frameworks work under the hood.
- Small demos, playgrounds, documentation sites.
- Projects that prefer direct DOM manipulation and minimal dependencies.
Design principles:
- Minimal and transparent (small, well-commented code).
- Simple reactivity — explicit effect tracking and subscription.
- Works with modern tooling via a small Vite plugin.
---
## Installation
Install from npm:
```bash
npm install twiggle
```
Or use the package from the monorepo during local development (root of workspace):
```bash
npm install
cd packages/twiggle
npm run start
```
Available scripts (in `packages/twiggle/package.json`):
- `start` — start Vite dev server
- `build` — run Vite build
- `test` — run Vitest
- `test:ui` — run Vitest UI
---
## Quick Start
Minimal example (TypeScript / TSX):
```tsx
/** src/main.tsx */
import render from 'twiggle'
import Home from './pages/Home'
const root = document.getElementById('root')!
render(, root)
```
Function component example:
```tsx
function Greeting(props: { name: string }) {
return
Hello, {props.name}!
}
render(, document.getElementById('root'))
```
Note: The JSX transform must target Twiggle's runtime (see Vite plugin section) or you can import the runtime directly in your build config.
---
## Core Concepts
### JSX & CreateElement
Twiggle implements a small JSX runtime. When JSX is compiled it calls `createElement(type, props)`. Supported `type` values:
- string tag (e.g. `'div'`) — creates a DOM element and applies props/children
- `'Fragment'` — returns a `DocumentFragment`
- function component — `type(props)` is invoked and must return a DOM node or fragment
Props convention:
- `children` may be string, number, element, array of elements
- DOM event handlers use lowercase names (e.g. `onclick`, not `onClick`)
### Renderer
`render(element, parent)` mounts a DOM node or fragment to the `parent` node. The current implementation clears `parent.innerHTML` and appends the element. This is intentionally simple to keep the runtime small.
### State Primitive
API:
- `createState(initial)` — returns `{ get: () => T, set: (v: T) => void }`
- `runSideEffect(fn)` — runs `fn` and tracks any `get()` calls performed during the execution. When a tracked state updates, the effect is re-run.
This is a minimal reactive system built around an effect stack and per-state subscriber lists. It is not a full reactive framework but is tiny and easy to reason about.
Example counter:
```tsx
import {
createState,
runSideEffect
} from 'twiggle'
const counter = createState(0)
export default function Counter() {
runSideEffect(() => {
// reads counter.get() to subscribe
const value = counter.get()
// re-run when counter.set is called
console.log('counter', value)
})
return (
{counter.get()}
counter.set(counter.get() + 1)}>Increment
)
}
```
### Router
Twiggle ships a tiny client-side router built on the History API.
Components and functions:
- `Routes` — collects `Route` children and registers them via `setRoutes`
- `Route({ to, element })` — lightweight route descriptor; `element` is a function that returns the element to render
- `Link({ to, children })` — anchor element that prevents default navigation and calls `navigate`
- `navigate(to)` — programmatic navigation; pushes a new history entry and renders the route
Example:
```tsx
} />
} />
About
```
---
## Vite Plugin
Use the official Vite plugin in this monorepo to compile JSX for Twiggle and enable reactive transforms.
Install and add to `vite.config.js`:
```js
import { defineConfig } from 'vite'
import twiggle from 'vite-plugin-twiggle'
export default defineConfig({
plugins: [twiggle()]
})
```
What the plugin does:
- Runs Babel to transform JSX using `@babel/preset-react` configured with `runtime: 'automatic'` and `importSource: 'twiggle/jsx'`.
- Applies a small custom Babel plugin bundled in `packages/vite-plugin-twiggle` to support reactive expression transforms used by Twiggle's runtime.
If you don't use the plugin, ensure your JSX compiler targets the Twiggle runtime or import the proper runtime functions directly.
---
## API Reference
Renderer
- `createElement(type, props)` — internal JSX entry point.
- `render(element: HTMLElement | DocumentFragment, parent: HTMLElement): void` — mount node to parent.
Router
- `navigate(to: string): void`
- `setRoutes(routes: Record HTMLElement>): void` — internal
- `Routes(props: { children: any[] }): null`
- `Route(props: { to: string, element: () => HTMLElement }): any`
- `Link(props: { to: string, children: any }): HTMLElement`
State
- `createState(value: T): { get: () => T; set: (v: T) => void }`
- `runSideEffect(fn: () => void): void`
See the `src` folder for implementation details and comments.
---
## Development
Clone and install dependencies from the monorepo root:
```bash
git clone https://github.com/hrutavmodha/twiggle.git
npm install
```
Run the example/dev server for the `twiggle` package:
```bash
cd packages/twiggle
npm run start
```
Build the package:
```bash
cd packages/twiggle
npm run build
```
Run tests:
```bash
cd packages/twiggle
npm run test
```
Quality checks you should run before opening a PR:
- Build: `npm run build`
- Tests: `npm run test`
- Type checks (optional): `tsc --noEmit` from the package directory
---
## Contributing
Contributions are welcome. Please follow these guidelines:
1. Fork the repo and create a descriptive branch name.
2. Keep changes small and focused. Add tests where applicable.
3. Run the test suite and ensure the build passes.
4. Open a pull request describing the problem, approach, and any migration notes.
See `CONTRIBUTING.md` for the project's broader contribution rules.
---
## FAQ
1. Is Twiggle a full framework?
No. Twiggle is a tiny set of primitives meant for learning, demos, and small apps.
It lacks many features of full frameworks (advanced component lifecycles, SSR, etc.)
2. Can I use Twiggle in production?
You can, but consider the tradeoffs (small feature set, simpler reactivity). For production apps that need scaling, a more feature-complete framework is recommended.
---
## License
MIT — see the `LICENSE` file for details.
---
Maintainer: Hrutav Modha — please open issues for bugs or feature requests.