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 🙏

© 2025 – Pkg Stats / Ryan Hefner

auwla

v0.2.5

Published

A lightweight, reactive UI framework with blazing-fast performance

Readme

⚡ NW-App Framework

A lightweight, reactive UI framework built from scratch with TypeScript. Features reactive state management, efficient DOM diffing, lifecycle hooks, and a full SPA router—all with zero dependencies.

✨ Features

  • 🔄 Reactive State - Proxy-based reactivity with subscription model
  • 🧩 Component System - Chainable DSL for building UIs
  • 🎯 Lifecycle Hooks - onMount / onUnmount with automatic cleanup
  • 📊 Efficient Lists - Keyed diffing for O(1) updates
  • 🚦 SPA Router - Dynamic routes, guards, query params, caching
  • 🌐 Data Fetching - Built-in fetch helper with loading/error states
  • 🎨 Tailwind CSS v4 - Modern styling with minimal setup
  • 📦 TypeScript - Full type safety throughout
  • 🚀 Bun Runtime - Lightning-fast development and building

🚀 Quick Start

import { Component } from "./src/dsl"
import { ref } from "./src/state"

const count = ref(0)

const App = Component((ui) => {
  ui.Div({ className: "p-8" }, (ui) => {
    ui.Text({ 
      value: count,
      formatter: (c) => `Count: ${c}`,
      className: "text-2xl mb-4"
    })
    
    ui.Button({
      text: "Increment",
      on: { click: () => count.value++ },
      className: "px-4 py-2 bg-blue-500 text-white rounded"
    })
  })
})

document.querySelector("#app")!.appendChild(App)

🧰 Create Auwla App (JSX)

  • Scaffold a new project with the zero-dependency initializer and minimal JSX template:
npm create auwla@latest my-app
# Answer prompts, then:
cd my-app
npm install
npm run dev
  • The template uses Vite + TypeScript with Auwla’s automatic JSX runtime (jsxImportSource: "auwla").
  • Routing is ready out of the box: edit src/routes.tsx to add pages; src/main.tsx initializes Router and sets the global router for Link.

Scaffold with Vite (Recommended)

  • Non-React baseline (vanilla TS):
npm create vite@latest auwla -- --template vanilla-ts
  • React variant (when needed):
npm create vite@latest auwla-react -- --template react-ts

After scaffolding the non‑React app, install and wire Auwla:

cd auwla
npm i auwla

Update tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "auwla",
    "moduleResolution": "Bundler",
    "types": ["vite/client", "auwla/jsx-runtime"]
  }
}

Optional (editor reliability): add to the top of .tsx files:

/** @jsxImportSource auwla */

📚 Documentation

Start learning the framework step by step:

  1. Beginner Guide - Learn reactivity basics, refs, components, and UI building
  2. Intermediate Guide - Computed values, lists, conditional rendering, lifecycle
  3. Advanced Guide - Router, state caching, performance optimization, advanced patterns
  4. API Reference - Complete API documentation
  5. JSX Guide - Use Auwla with JSX (setup, lists, conditionals)

🏗️ Architecture

Reactivity (src/state.ts)

// Create reactive values
const name = ref("Alice")
const age = ref(30)

// Computed values
const greeting = watch([name, age], ([n, a]) => `${n} is ${a} years old`)

// Subscribe to changes
name.subscribe((newName) => {
  console.log("Name changed to:", newName)
})

Components (src/dsl.ts)

const UserCard = Component((ui) => {
  ui.Div({ className: "border rounded p-4" }, (ui) => {
    ui.Text({ value: "John Doe", className: "font-bold" })
    ui.Text({ value: "Developer", className: "text-gray-600" })
  })
})

Lists (src/dsl.ts)

const todos = ref([
  { id: 1, text: "Learn framework", done: ref(false) },
  { id: 2, text: "Build app", done: ref(false) }
])

ui.List({
  items: todos,
  key: (todo) => todo.id,  // Efficient updates!
  render: (todo, i, ui) => {
    ui.Input({
      type: "checkbox",
      checked: todo.done,
      on: { change: () => todo.done.value = !todo.done.value }
    })
    ui.Text({ value: todo.text })
  }
})

Lifecycle (src/lifecycle.ts)

Component((ui) => {
  onMount(() => {
    console.log("Component mounted!")
    
    const interval = setInterval(() => console.log("Tick"), 1000)
    
    // Cleanup
    return () => clearInterval(interval)
  })
  
  ui.Text({ value: "Hello" })
})

Data Fetching (src/fetch.ts)

import { fetch as fetchData } from "./src/fetch"

Component((ui) => {
  const { data, loading, error } = fetchData<User[]>(
    '/api/users',
    { cacheKey: 'users' }  // Optional caching
  )
  
  ui.When(loading, (ui) => {
    ui.Text({ value: "Loading..." })
  })
  
  ui.List({
    items: data as Ref<User[]>,
    key: (user) => user.id,
    render: (user, i, ui) => {
      ui.Text({ value: user.name })
    }
  })
})

Router (src/router.ts)

import { Router, Link } from "./src/router"

const router = new Router()

router.addRoute("/", HomePage)
router.addRoute("/products/:id", ProductDetail)
router.addRoute("/admin", AdminPage, authGuard)  // With guard

router.start()

// Navigation
const Nav = Component((ui) => {
  ui.append(Link("/", "Home"))
  ui.append(Link("/products/123", "Product"))
})

📁 Project Structure

nw-app/
├── src/
│   ├── state.ts          # Reactive state (ref, watch)
│   ├── dsl.ts           # UI builder (Component, List, When)
│   ├── lifecycle.ts     # Lifecycle hooks (onMount, onUnmount)
│   ├── fetch.ts         # Data fetching with caching
│   ├── router.ts        # SPA router with state management
│   ├── main.ts          # Demo application
│   └── style.css        # Tailwind imports
├── docs/
│   ├── 01-beginner.md
│   ├── 02-intermediate.md
│   ├── 03-advanced.md
│   └── 04-api-reference.md
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
└── bunfig.toml

🛠️ Development

Prerequisites

Setup

# Install dependencies
bun install

# Start dev server
bun run dev

# Build for production
bun run build

Tech Stack

  • Runtime: Bun
  • Bundler: Vite
  • Language: TypeScript
  • Styling: Tailwind CSS v4
  • Framework: Custom (built from scratch)

🎯 Core Concepts

1. Reactivity

Built on a subscription model using JavaScript Proxies:

const state = ref(0)

// Subscribes automatically
ui.Text({ value: state })

// Manual subscription
const unsub = state.subscribe((val) => console.log(val))
unsub()  // Unsubscribe

2. Component Lifecycle

Components have a defined lifecycle with hooks:

Component((ui) => {
  onMount(() => {
    // Runs after mount
    return () => {
      // Cleanup on unmount
    }
  })
})

3. Keyed Lists

Lists use keys for efficient updates:

ui.List({
  items: myArray,
  key: (item) => item.id,  // Unique key
  render: (item, i, ui) => { /* ... */ }
})

4. Router State

Router includes built-in state for caching:

const { data } = fetch('/api/data', { cacheKey: 'myData' })
// First visit: fetches
// Return visit: uses router.state.myData

🔥 Real-World Example

import { Component } from "./src/dsl"
import { ref, watch } from "./src/state"
import { Router, Link, useParams } from "./src/router"
import { fetch as fetchData } from "./src/fetch"

type Product = { id: number; name: string; price: number }

// Products list page
function ProductsPage() {
  return Component((ui) => {
    const { data, loading } = fetchData<Product[]>(
      '/api/products',
      { cacheKey: 'products' }
    )
    
    ui.Div({ className: "p-8" }, (ui) => {
      ui.Text({ value: "Products", className: "text-4xl font-bold mb-6" })
      
      ui.When(loading, (ui) => {
        ui.Text({ value: "Loading products..." })
      })
      
      ui.When(watch(data, d => d !== null) as Ref<boolean>, (ui) => {
        ui.List({
          items: data as Ref<Product[]>,
          key: (p) => p.id,
          className: "grid grid-cols-3 gap-4",
          render: (product, i, ui) => {
            ui.Div({ className: "border rounded p-4" }, (ui) => {
              ui.Text({ value: product.name, className: "font-bold" })
              ui.Text({ 
                value: `$${product.price}`,
                className: "text-green-600 text-xl"
              })
              ui.append(
                Link(`/products/${product.id}`, "View Details", {
                  className: "text-blue-600 hover:underline"
                })
              )
            })
          }
        })
      })
    })
  })
}

// Product detail page
function ProductDetailPage() {
  return Component((ui) => {
    const params = useParams()
    const { data, loading } = fetchData<Product>(
      `/api/products/${params.id}`,
      { cacheKey: `product-${params.id}` }
    )
    
    ui.Div({ className: "p-8" }, (ui) => {
      ui.append(Link("/products", "← Back", { className: "text-blue-600" }))
      
      ui.When(watch(data, d => d !== null) as Ref<boolean>, (ui) => {
        ui.Text({ 
          value: data as Ref<Product>,
          formatter: (p) => p.name,
          className: "text-4xl font-bold my-6"
        })
        ui.Text({
          value: data as Ref<Product>,
          formatter: (p) => `Price: $${p.price}`,
          className: "text-2xl text-green-600"
        })
      })
    })
  })
}

// Setup router
const router = new Router()
router.addRoute("/products", ProductsPage)
router.addRoute("/products/:id", ProductDetailPage)
router.start()

document.querySelector("#app")!.appendChild(router.render())

🚀 Performance

  • Reactive updates: Only changed components re-render
  • Keyed lists: O(1) updates for list items
  • Router caching: Prevents redundant API calls
  • Automatic cleanup: No memory leaks

🤝 Contributing

This is a learning project built from scratch to understand:

  • Reactive programming
  • Virtual DOM alternatives (direct DOM manipulation)
  • Component lifecycles
  • SPA routing
  • State management

Feel free to explore the code and learn from it!

📝 License

MIT

🙏 Acknowledgments

Built with inspiration from modern frameworks but implemented from first principles to deeply understand reactive UI systems.