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

@mateosuarezdev/flash

v0.0.22

Published

Custom jsx runtime

Readme

⚡ Flash

Fine-grained reactive JSX framework with zero VDOM overhead

Flash is a lightweight, performant JSX framework built on Preact Signals for fine-grained reactivity. Write familiar JSX, get blazing fast updates with direct DOM manipulation.


Features

Client-Side

  • Fine-grained reactivity - Powered by Preact Signals, updates only what changed
  • 🎯 No Virtual DOM - Direct DOM manipulation for maximum performance
  • 🎭 Flexible animations - Use any library (Framer Motion, GSAP, etc.) or vanilla CSS/classes
  • 🎪 DOM resurrection - Automatic animation reversal for rapid toggling
  • 🔑 Keyed list rendering - SolidJS-style efficient list updates (insert/update/delete/move)
  • 🎨 View Transitions API - Smooth page transitions out of the box
  • 🏎️ Frame scheduler - Prevent layout thrashing with read/update/render phases
  • 🎬 FLIP animations - Built-in utilities for performant layout animations
  • 🪄 Auto-animate - Automatic layout animations (in progress)
  • 🪝 First-class exit animations - onBeforeExit pauses unmounting (save data, animations, etc.)
  • 🌳 Context API - Share state across component trees
  • 🧭 Built-in Router - File-based routing with lazy loading (work in progress)
  • 📦 Tiny bundle size - No compiler required, minimal runtime
  • 🔄 Reactive props - Props can be signals for automatic updates

Server-Side

  • 🌐 Server-Side Rendering - Three rendering strategies (sync, async, streaming)
  • Parallel async resolution - Resolves async components efficiently
  • 📦 Pre-rendering & caching - Built-in static site generation
  • 🌊 Progressive enhancement - Stream HTML for better perceived performance
  • 🔒 Automatic XSS protection - HTML escaping by default

Installation

npm install @mateosuarezdev/flash @preact/signals-core

Configure your tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@mateosuarezdev/flash/runtime"
  }
}

Quick Start

import { render, onMount } from "@mateosuarezdev/flash";
import { signal } from "@preact/signals-core";

const count = signal(0);

function Counter() {
  onMount(() => {
    console.log("Counter mounted!");
  });

  return (
    <div>
      <h1>Count: {() => count.value}</h1>
      <button onClick={() => count.value++}>Increment</button>
    </div>
  );
}

render(<Counter />, document.getElementById("app"));

Core Concepts

Reactive Boundaries

Wrap expressions in functions to create reactive boundaries that update automatically when signals change:

import { signal } from "@preact/signals-core";

const name = signal("World");

function Greeting() {
  return (
    <div>
      {/* This updates when name changes */}
      <h1>Hello {() => name.value}!</h1>

      {/* Conditional rendering */}
      {() =>
        name.value === "World" ? (
          <p>Welcome!</p>
        ) : (
          <p>Hello, {() => name.value}!</p>
        )
      }
    </div>
  );
}

Reactive Props

Props can be functions that automatically update:

const isDark = signal(false)

<button
  className={() => isDark.value ? 'dark' : 'light'}
  disabled={() => !isDark.value}
>
  Toggle
</button>

Keyed Lists

Use the key prop for efficient list rendering:

const items = signal([
  { id: 1, name: "Apple" },
  { id: 2, name: "Banana" },
  { id: 3, name: "Cherry" },
]);

function List() {
  return (
    <ul>
      {() => items.value.map((item) => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

Flash efficiently handles:

  • Inserts - New items are rendered and inserted
  • Removals - Deleted items are unmounted and removed
  • Reordering - DOM nodes are moved to match new order
  • Updates - Existing items are reused (no re-render)

Lifecycle Hooks

onMount

Runs after the component is mounted to the DOM:

function Component() {
  let ref: HTMLDivElement;

  onMount(() => {
    console.log("Element:", ref);
    // Fetch data, start animations, etc.
  });

  return <div ref={(el) => (ref = el)}>Content</div>;
}

onUnmount

Runs when the component is removed from the DOM:

function Component() {
  onUnmount(() => {
    console.log("Cleaning up...");
    // Cancel subscriptions, clear timers, etc.
  });

  return <div>Content</div>;
}

onBeforeExit

Runs before unmounting - a first-class feature, not a workaround. You can pause the entire unmount process to:

  • Play exit animations
  • Save form data or state
  • Confirm user actions
  • Clean up async operations
  • Anything you need before removal

The unmount tree is held until your async callback completes:

import { animate } from "framer-motion";

function FadeBox() {
  let ref: HTMLElement;

  onBeforeExit(async (token) => {
    // Play exit animation
    const animation = animate(ref, { opacity: 0 }, { duration: 0.3 });

    // Handle cancellation (e.g., rapid toggling)
    token.onCancel(() => {
      animation.stop();
      animate(ref, { opacity: 1 }, { duration: 0.3 });
    });

    await animation.finished;
    // Component won't unmount until animation completes
  });

  return <div ref={(el) => (ref = el)}>Fading content</div>;
}

Save data before unmounting:

function Form() {
  const formData = signal({ name: "", email: "" });

  onBeforeExit(async () => {
    // Save to localStorage or API
    await saveFormData(formData.value);
    console.log("Data saved before unmount!");
  });

  return <form>...</form>;
}

Cancellation Example:

const show = signal(true)

// Rapid toggling: true → false → true
// Flash will cancel the exit animation and reuse the DOM!
<button onClick={() => show.value = !show.value}>
  Toggle
</button>

{() => show.value && <FadeBox />}

Context API

Share state across component trees without prop drilling:

import { createContext, useContext } from "@mateosuarezdev/flash";

const ThemeContext = createContext({ theme: "light" });

function App() {
  ThemeContext.provide({ theme: "dark" });

  return <Child />;
}

function Child() {
  const theme = useContext(ThemeContext);
  console.log(theme); // { theme: 'dark' }

  return <div>Theme: {theme.theme}</div>;
}

Router (Work in Progress)

Flash includes a built-in router with reactive pathname tracking and custom URL change events:

import { Router, pathname, push, replace, back, onUrlChange } from '@mateosuarezdev/flash/router'

function App() {
  return (
    <Router>
      <nav>
        <a href="/" onClick={(e) => { e.preventDefault(); push('/') }}>Home</a>
        <a href="/about" onClick={(e) => { e.preventDefault(); push('/about') }}>About</a>
      </nav>

      {/* Reactive routing based on pathname signal */}
      {() => {
        switch (pathname.value) {
          case '/':
            return <Home />
          case '/about':
            return <About />
          default:
            return <NotFound />
        }
      }}
    </Router>
  )
}

Core Router Features

Reactive Pathname Tracking:

import { pathname } from '@mateosuarezdev/flash/router'

// pathname is a signal that updates on navigation
function NavBar() {
  return (
    <nav>
      <a
        class={() => pathname.value === '/' ? 'active' : ''}
        href="/"
      >
        Home
      </a>
    </nav>
  )
}

Programmatic Navigation:

import { push, replace, back } from '@mateosuarezdev/flash/router'

// Navigate to a new route
push('/dashboard')

// Replace current route (no history entry)
replace('/login')

// Go back in history
back()

URL Change Events:

import { onUrlChange } from '@mateosuarezdev/flash/router'

function Component() {
  onUrlChange((event) => {
    if (event) {
      console.log('Navigation:', event.action) // 'pushState' | 'replaceState' | 'popstate' | 'beforeunload'
      console.log('From:', event.oldURL?.pathname)
      console.log('To:', event.newURL?.pathname)

      // Prevent navigation by calling event.preventDefault()
      // Great for unsaved changes warnings
    }
  })

  return <div>Content</div>
}

Custom UrlChangeEvent:

The router automatically intercepts and dispatches custom urlchangeevent for all navigation:

  • pushState - New history entry
  • replaceState - Replace current entry
  • popstate - Back/forward navigation
  • beforeunload - Page close/reload

Events can be prevented to block navigation (useful for form guards, unsaved changes, etc.)

Current Features:

  • ✅ Reactive pathname signal
  • ✅ Programmatic navigation (push, replace, back)
  • ✅ Custom URL change events with prevention
  • ✅ History state tracking
  • ✅ Lifecycle integration (auto-cleanup with onUnmount)

Planned Features:

  • 📁 File-based routing with automatic route generation
  • 🔀 Lazy loading and code splitting helpers
  • 🎨 Integrated View Transitions API support
  • 📱 Nested routes and layouts
  • 🎯 Route guards and middleware
  • 🔍 Path parameter extraction
  • 🔗 Link component with active state

Note: The router is currently in active development. The current implementation provides low-level primitives for building routing solutions!


Animations & Performance

Flash is designed to be a batteries-included framework with powerful animation and performance utilities built right in.

Flexible Animation Options

Flash gives you complete freedom to animate however you want:

1. Use Any Animation Library

import { animate } from 'framer-motion'
import { animate as animateJsAnimate } from 'animejs'

function Component() {
  let ref: HTMLElement

  onMount(() => {
    // Framer Motion
    animate(ref, { x: 100 }, { duration: 0.3 })

    // Anime.js, GSAP, Motion One, or any library!
  })

  onBeforeExit(async (token) => {
    const animation = animate(ref, { opacity: 0 }, { duration: 0.3 })

    token.onCancel(() => {
      animation.stop()
      animate(ref, { opacity: 1 }, { duration: 0.3 })
    })

    await animation.finished
  })

  return <div ref={(el) => ref = el}>Animated</div>
}

2. Vanilla CSS Transitions

function Component() {
  let ref: HTMLElement

  onMount(() => {
    ref.style.transition = 'opacity 300ms'
    ref.style.opacity = '0'
    setTimeout(() => ref.style.opacity = '1', 10)
  })

  onBeforeExit(async () => {
    ref.style.opacity = '0'
    await new Promise(resolve => setTimeout(resolve, 300))
  })

  return <div ref={(el) => ref = el}>CSS Animated</div>
}

3. Toggle CSS Classes

function Component() {
  let ref: HTMLElement

  onMount(() => {
    requestAnimationFrame(() => {
      ref.classList.add('enter-active')
      setTimeout(() => ref.classList.remove('enter-active'), 300)
    })
  })

  onBeforeExit(async () => {
    ref.classList.add('exit-active')
    await new Promise(resolve => setTimeout(resolve, 300))
  })

  return <div ref={(el) => ref = el} class="animated">Content</div>
}

Built-in Performance Utilities

Frame Scheduler (Prevent Layout Thrashing)

Inspired by Framer Motion's frame loop, Flash includes a high-performance scheduler that prevents layout thrashing by separating read/update/render phases:

import { frame } from '@mateosuarezdev/flash'

// Simple usage
frame.read(() => {
  const height = element.offsetHeight  // DOM reads
})

frame.update(() => {
  position += velocity  // Calculations
})

frame.render(() => {
  element.style.transform = `translateY(${position}px)`  // DOM writes
})

// Chained operations with type-safe data flow
frame.chain({
  read: () => element.offsetHeight,
  update: (height) => height * 2,
  render: (doubled) => element.style.height = `${doubled}px`
})

// Keep-alive for continuous animations
const animate = frame.render(() => {
  element.style.transform = `rotate(${rotation}deg)`
}, true) // true = runs every frame

// Cancel when done
frame.cancel(animate)

FLIP Animations

Built-in FLIP (First, Last, Invert, Play) utilities for performant layout animations:

import { flip, flipGroup } from '@mateosuarezdev/flash'

// Animate a single element
flip(element, () => {
  // Make DOM changes
  element.classList.add('expanded')
  element.style.width = '400px'
}, {
  duration: 300,
  easing: 'ease-out-cubic'
})

// Animate list reordering
const items = document.querySelectorAll('.item')

flipGroup(items, () => {
  // Reorder items
  container.appendChild(items[2])
}, {
  duration: 400,
  easing: 'ease-out-cubic'
})

Auto-Animate (Work in Progress)

Automatically animate layout changes (inspired by Framer Motion's layout animations):

<div autoanimate>
  {/* Children automatically animate when added/removed/reordered */}
  {() => items.value.map(item => (
    <div key={item.id}>{item.name}</div>
  ))}
</div>

Note: Auto-animate is currently in development and will provide automatic FLIP animations for layout changes without manual setup.

View Transitions API

Built-in support for the browser's View Transitions API:

import { startViewTransition } from '@mateosuarezdev/flash'

const expanded = signal(false)

<button onClick={() => {
  startViewTransition(() => {
    expanded.value = !expanded.value
  })
}}>
  Toggle
</button>

<div
  className={() => expanded.value ? 'expanded' : 'collapsed'}
  viewTransitionName="container"
>
  Content
</div>

Performance Best Practices

Use the Frame Scheduler:

// ❌ Bad: Layout thrashing
const height = element.offsetHeight  // Read
element.style.height = `${height * 2}px`  // Write
const width = element.offsetWidth  // Read (forces reflow!)
element.style.width = `${width * 2}px`  // Write

// ✅ Good: Batched reads and writes
frame.chain({
  read: () => ({
    height: element.offsetHeight,
    width: element.offsetWidth
  }),
  render: ({ height, width }) => {
    element.style.height = `${height * 2}px`
    element.style.width = `${width * 2}px`
  }
})

Use FLIP for Layout Changes:

// ❌ Bad: Animating layout properties directly
element.animate({ width: '400px', height: '300px' }, { duration: 300 })

// ✅ Good: Use FLIP to transform instead
flip(element, () => {
  element.style.width = '400px'
  element.style.height = '300px'
}, { duration: 300 })

Advanced Examples

Counter with Computed Values

import { signal, computed } from "@preact/signals-core";

const count = signal(0);
const double = computed(() => count.value * 2);

function Counter() {
  return (
    <div>
      <p>Count: {() => count.value}</p>
      <p>Double: {() => double.value}</p>
      <button onClick={() => count.value++}>Increment</button>
    </div>
  );
}

Dynamic List with Add/Remove

import { signal } from "@preact/signals-core";

const items = signal([
  { id: 1, name: "Task 1" },
  { id: 2, name: "Task 2" },
]);

let nextId = 3;

function TodoList() {
  const addItem = () => {
    items.value = [...items.value, { id: nextId++, name: `Task ${nextId}` }];
  };

  const removeItem = (id: number) => {
    items.value = items.value.filter((item) => item.id !== id);
  };

  return (
    <div>
      <button onClick={addItem}>Add Task</button>
      <ul>
        {() =>
          items.value.map((item) => (
            <li key={item.id}>
              {item.name}
              <button onClick={() => removeItem(item.id)}>Delete</button>
            </li>
          ))
        }
      </ul>
    </div>
  );
}

Nested Reactive Updates

const user = signal({ name: "John", age: 25 });

function Profile() {
  return (
    <div>
      <h1>{() => user.value.name}</h1>
      <p>Age: {() => user.value.age}</p>
      <button
        onClick={() => {
          user.value = { ...user.value, age: user.value.age + 1 };
        }}
      >
        Birthday
      </button>
    </div>
  );
}

Conditional Rendering with Animations

import { animate } from "framer-motion";

const show = signal(true);

function AnimatedBox() {
  let ref: HTMLElement;

  onMount(() => {
    animate(ref, { opacity: [0, 1], y: [-20, 0] }, { duration: 0.3 });
  });

  onBeforeExit(async (token) => {
    const animation = animate(ref, { opacity: 0, y: -20 }, { duration: 0.3 });

    token.onCancel(() => {
      animation.stop();
      animate(ref, { opacity: 1, y: 0 }, { duration: 0.3 });
    });

    await animation.finished;
  });

  return <div ref={(el) => (ref = el)}>Animated content</div>;
}

function App() {
  return (
    <div>
      <button onClick={() => (show.value = !show.value)}>Toggle</button>
      {() => show.value && <AnimatedBox />}
    </div>
  );
}

Server-Side Rendering

Flash provides three rendering strategies for different use cases:

renderToString() - Synchronous

Fast synchronous rendering for static content:

import { renderToString } from '@mateosuarezdev/flash/server'

const html = renderToString(<App />)
// Returns: Complete HTML string (no async support)

renderToStringAsync() - Complete HTML

Waits for all async components, perfect for SEO and pre-rendering:

import { renderToStringAsync } from '@mateosuarezdev/flash/server'

const html = await renderToStringAsync(<App />)
// Returns: Complete HTML with all async resolved

renderToStream() - Progressive Enhancement

Stream HTML for better perceived performance:

import { renderToStream } from '@mateosuarezdev/flash/server'

const stream = renderToStream(<App />)

for await (const chunk of stream) {
  response.write(chunk)
}
// Streams: Initial HTML + progressive updates

Pre-rendering & Caching

import { prerenderer } from '@mateosuarezdev/flash/server/prerender'

// Save pre-rendered HTML
await prerenderer.save('/', html)

// Load from cache
const cached = await prerenderer.load('/')
if (cached) return new Response(cached)

Learn more: Check out the Server Architecture Guide for detailed information about:

  • Rendering strategies comparison
  • Async component resolution
  • Streaming architecture
  • Caching and pre-rendering
  • Security best practices

API Reference

Core

  • render(element, container) - Mount your app to the DOM
  • Fragment - Render multiple children without a wrapper

Lifecycle

  • onMount(callback) - Run after component mounts
  • onUnmount(callback) - Run when component unmounts
  • onBeforeExit(callback) - Run before unmounting (pauses unmount tree for animations, data saving, etc.)

Context

  • createContext(defaultValue) - Create a context
  • useContext(context) - Consume context value

Animations & Performance

  • startViewTransition(callback) - Trigger View Transition API
  • frame.read(callback) - Schedule DOM reads (measurements)
  • frame.update(callback) - Schedule calculations
  • frame.render(callback) - Schedule DOM writes (mutations)
  • frame.chain({ read, update, render }) - Chain operations with data flow
  • flip(element, applyChanges, options) - FLIP animation for single element
  • flipGroup(elements, applyChanges, options) - FLIP animation for groups
  • flipMove(element, newParent, options) - Animate element to new container
  • autoAnimate(element, options) - Enable auto layout animations (WIP)

Special Props

  • ref={(el) => ...} - Get reference to DOM element
  • key={value} - Unique identifier for list items
  • viewTransitionName={name} - Named view transition target
  • autoanimate={true} - Enable auto-layout animations (WIP)

Performance Tips

  1. Use signals at module level for shared state
  2. Wrap dynamic expressions in functions {() => signal.value} not {signal.value}
  3. Always use keys for list items
  4. Minimize reactive boundaries - only wrap what needs to update
  5. Use computed signals for derived state
  6. Use frame for DOM operations - Prevent layout thrashing by batching reads/writes
  7. Use FLIP for layout animations - Animate transforms instead of layout properties
  8. Leverage DOM resurrection - Flash automatically reuses DOM for rapid toggles

Comparison to Other Frameworks

| Feature | Flash | React | SolidJS | Vue | | ------------------------ | ------- | ----- | ------- | ------- | | Reactivity | Signals | VDOM | Signals | Proxies | | Bundle Size | ~10KB | ~40KB | ~7KB | ~30KB | | Fine-grained Updates | ✅ | ❌ | ✅ | ✅ | | Keyed Lists | ✅ | ✅ | ✅ | ✅ | | Built-in FLIP Utils | ✅ | ❌ | ❌ | ❌ | | Frame Scheduler | ✅ | ❌ | ❌ | ❌ | | DOM Resurrection | ✅ | ❌ | ❌ | ❌ | | Animation Flexibility | Any lib | Any lib | Any lib | Any lib | | SSR Support | ✅ | ✅ | ✅ | ✅ | | SSR Streaming | ✅ | ✅ | ✅ | ✅ | | No Compiler | ✅ | ✅ | ❌ | ❌ |


FAQ

Q: Do I need a compiler? A: No! Flash works with standard JSX transformation. Just configure jsxImportSource.

Q: Can I use TypeScript? A: Yes! Flash is written in TypeScript with full type support.

Q: How does reactivity work? A: Flash uses Preact Signals. When you wrap an expression in a function {() => signal.value}, Flash creates a reactive boundary that auto-updates when the signal changes.

Q: What about SSR? A: Yes! Flash has full SSR support with three rendering strategies (sync, async, streaming). See the Server Architecture Guide.

Q: Why functions for reactive values? A: Functions create clear boundaries for reactivity and work without a compiler. It's explicit and simple.

Q: What is DOM resurrection? A: When a component is exiting (playing exit animation) but gets toggled back on, Flash cancels the animation and reuses the existing DOM instead of creating a new one. This provides smooth animation reversals without any setup.


Examples

Check out the examples directory for more demos:

  • Basic counter and computed values
  • Enter/exit animations with cancellation
  • View Transitions API integration
  • Keyed list rendering with add/remove
  • Context API usage
  • Reactive props and class names

Architecture

Want to understand how Flash works under the hood?

Client Architecture Guide - Deep dive into:

  • JSX transformation flow
  • VNode types and rendering pipeline
  • Reactivity system internals
  • Content replacement strategies (resurrection, text optimization)
  • Keyed list reconciliation algorithm

Server Architecture Guide - Deep dive into:

  • Three rendering strategies (sync, async, streaming)
  • Async component resolution (parallel execution)
  • Streaming architecture and progressive enhancement
  • Pre-rendering and caching system
  • Security best practices (XSS protection)

Contributing

Flash is in active development. Contributions are welcome!


License

MIT © Mateo Suarez


⚡ Built with Flash - Fine-grained reactivity meets familiar JSX