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.54

Published

Custom jsx runtime

Downloads

4,848

Readme

Flash

Version 0.0.46 — Fine-grained reactive JSX framework with zero VDOM overhead.

Flash is a lightweight JSX framework built on Legend State observables for fine-grained reactivity. Write familiar JSX, get direct DOM updates with no virtual DOM diffing. Ships with first-class SSR, hydration, SEO, MPA build utilities, and exit animations.


Table of contents


Installation

npm install @mateosuarezdev/flash

Configure tsconfig.json:

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

@legendapp/state is a bundled dependency — no separate install needed.


Quick Start

import { render, onMount } from "@mateosuarezdev/flash";
import { observable } from "@mateosuarezdev/flash";

const count = observable(0);

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

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

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

Observables

Flash re-exports Legend State's observable primitives from the main entry point and from @mateosuarezdev/flash/signals (same module, two import paths).

import {
  observable,    // create an observable
  observe,       // run a reaction (re-runs when deps change)
  isObservable,  // type guard
  OpaqueObject,  // type wrapper — prevents Legend State from deep-transforming a value
} from "@mateosuarezdev/flash";

Creating observables

const name  = observable("World");          // Observable<string>
const count = observable(0);               // Observable<number>
const user  = observable<{ name: string; age: number } | null>(null);

Reading and writing

name.get();          // tracked read — subscribes the current observer
name.peek();         // untracked read — no subscription
name.set("Flash");   // write

// Nested objects are automatically observable
user.set({ name: "Alice", age: 25 });
user.name.get();     // "Alice"
user.age.set(26);    // granular update — only age subscribers re-run

Reactions

import { observe } from "@mateosuarezdev/flash";

const dispose = observe(() => {
  console.log("name changed to:", name.get());
});

dispose(); // stop observing

OpaqueObject

Prevents Legend State from deeply transforming a type (useful for storing DOM event objects or URL instances inside observables):

import { observable, OpaqueObject } from "@mateosuarezdev/flash";

type ChangeEvent = {
  oldURL: OpaqueObject<URL>;
  newURL: OpaqueObject<URL>;
};

const lastChange = observable<ChangeEvent | null>(null);

Signals API

Flash ships a small compatibility layer in @mateosuarezdev/flash (re-exported from the state module) for handling props that may be static values, observables, or accessor functions.

Hybrid<T> — the unified prop type

import type { Hybrid } from "@mateosuarezdev/flash";

type Hybrid<T> = T | Observable<T> | (() => T);

Any Flash prop can accept all three forms. The JSX runtime normalizes them automatically when binding to DOM elements — no adapter required at the call site.

const label = observable("Click me");

// All three are valid — runtime handles the rest:
<button>{() => label.get()}</button>   // accessor
<button>{label}</button>               // Observable directly
<button>{"Click me"}</button>          // static string

ReactiveProps<T> — reactive component props

Makes every prop on a custom component accept Hybrid<T>. Skips children, key, and ref.

import type { ReactiveProps } from "@mateosuarezdev/flash";

type ButtonProps = { label: string; disabled: boolean };

function Button(props: ReactiveProps<ButtonProps>) {
  // props.label  → string | Observable<string> | (() => string)
  // props.disabled → boolean | Observable<boolean> | (() => boolean)
  return <button disabled={props.disabled}>{props.label}</button>;
}

// Caller can mix static, observable, and accessor freely:
<Button label="Save" disabled={false} />
<Button label={labelObs} disabled={() => form.get().submitting} />

$() — read inside reactive boundaries

When a Hybrid prop is an Observable, passing it into a () => reactive boundary as-is would pass the Observable object, not its value. $() converts it to () => obs.get() so the enclosing boundary tracks it.

import { $ } from "@mateosuarezdev/flash";

function Toggle(props: ReactiveProps<{ disabled: boolean; label: string }>) {
  // $(props.disabled) is () => obs.get() if it's an Observable,
  // or the raw value/function if it isn't.
  return () => $(props.disabled)
    ? null
    : <button>{$(props.label)}</button>;
}

Static values and accessor functions pass through $() unchanged — always safe to call unconditionally.

peek() — non-tracking read

Read any Hybrid<T> exactly once without creating a subscription. Safe in event handlers and one-off logic.

import { peek } from "@mateosuarezdev/flash";

function Form(props: ReactiveProps<{ initialValue: string }>) {
  let saved = "";

  onMount(() => {
    saved = peek(props.initialValue); // no subscription
  });

  return <input value={props.initialValue} />;
}

| Utility | Observable | () => T | Static value | | -------------------- | ----------------- | ------------------- | ------------ | | $(value) | () => obs.get() | passed through | passed through | | peek(value) | obs.peek() | calls it once | returns as-is |


Reactive Boundaries

Reactive boundaries are plain () => functions in JSX. Flash subscribes to any .get() calls made inside them and re-runs only that boundary when a dep changes.

const name = observable("World");

function Greeting() {
  return (
    <div>
      {/* Re-runs when name changes */}
      <h1>Hello {() => name.get()}!</h1>

      {/* Conditional rendering — entire branch swaps on change */}
      {() =>
        name.get() === "World"
          ? <p>Welcome!</p>
          : <p>Hello, {name.get()}!</p>
      }
    </div>
  );
}

Reactive props on DOM elements are also boundaries — Flash wraps them automatically:

const isDark = observable(false);

<button
  class={() => isDark.get() ? "dark" : "light"}
  disabled={() => !isDark.get()}
>
  Toggle
</button>

Lifecycle Hooks

onMount

Runs once after the component's DOM is attached.

function Component() {
  let el: HTMLDivElement;

  onMount(() => {
    console.log("mounted:", el);
  });

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

onUnmount

Runs when the component is removed from the DOM.

function Component() {
  onUnmount(() => {
    clearInterval(timer);
  });

  return <div>Content</div>;
}

onBeforeExit

Runs before unmounting and pauses the entire unmount tree until the async callback resolves. Use this for exit animations, saving data, or confirming before removal.

import { animate } from "framer-motion";

function FadeBox() {
  let ref: HTMLElement;

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

    // Handle rapid re-mount: cancel and reverse the animation
    token.onCancel(() => {
      animation.stop();
      animate(ref, { opacity: 1 }, { duration: 0.15 });
    });

    await animation.finished;
  });

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

DOM resurrection — if the component is toggled back on while exiting, Flash cancels the onBeforeExit callback via token.onCancel and reuses the existing DOM, giving you smooth reversals for free.


Controlled Inputs

For <input> elements with a reactive value or checked, Flash sets the DOM property (not the HTML attribute), keeping the input controlled:

const text = observable("hello");

<input
  value={text}
  onInput={(e) => text.set((e.target as HTMLInputElement).value)}
/>

Keyed Lists

Use the key prop for efficient list rendering. Flash handles insert, remove, reorder, and update without re-rendering unaffected items.

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

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

Context API

Share values across the component tree without prop drilling.

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

const ThemeContext = createContext<"light" | "dark">("light");

function App() {
  ThemeContext.provide("dark");
  return <Child />;
}

function Child() {
  const theme = useContext(ThemeContext); // "dark"
  return <div class={theme}>Content</div>;
}

provide() sets the value for the subtree rendered below the calling component. useContext() walks up to the nearest provider.


Router

Import from @mateosuarezdev/flash/router. The router is client-side only — wrap your app in <Router> to start tracking navigation.

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

Setup

function App() {
  return (
    <Router>
      {() => {
        switch (pathname.get()) {
          case "/":      return <Home />;
          case "/about": return <About />;
          default:       return <NotFound />;
        }
      }}
    </Router>
  );
}

Reactive pathname

pathname is an Observable<string> — read it inside a () => boundary to react to navigation:

import { pathname } from "@mateosuarezdev/flash/router";

function NavLink({ href, label }: { href: string; label: string }) {
  return (
    <a
      href={href}
      class={() => pathname.get() === href ? "active" : ""}
      onClick={(e) => { e.preventDefault(); push(href); }}
    >
      {label}
    </a>
  );
}

Programmatic navigation

push("/dashboard");       // new history entry
replace("/login");        // replace current entry
back();                   // history.back()

URL change events

import { onUrlChange } from "@mateosuarezdev/flash/router";

function UnsavedGuard() {
  onUrlChange((event) => {
    if (event && hasUnsavedChanges()) {
      // event.preventDefault() blocks the navigation
      event.preventDefault();
    }
  });

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

The router intercepts pushState, replaceState, popstate, and beforeunload and dispatches a unified urlchangeevent. onUrlChange auto-disposes when the component unmounts.


Head Management

Import head utilities from @mateosuarezdev/flash/framework.

setSeo()

Works in both environments:

  • Server-side (SSR): writes into the SeoCollector provided by WithModules — collected data is later injected by injectFlashInternals.
  • Client-side: upserts <meta>, <link>, and <title> tags directly in the DOM.
import { setSeo } from "@mateosuarezdev/flash/framework";

function ProductPage({ product }: { product: Product }) {
  setSeo({
    title: product.name,
    description: product.description,
    canonical: `https://example.com/products/${product.slug}`,
    og: {
      type: "product",
      image: product.imageUrl,
    },
    jsonLd: {
      "@context": "https://schema.org",
      "@type": "Product",
      name: product.name,
    },
  });

  return <main>...</main>;
}

Merging vs replacing:

// Merge with existing tags (default)
setSeo({ title: "Updated title" });

// Replace all managed tags (use when navigating to a completely new page)
setSeo({ title: "New page", description: "..." }, { merge: false });

Full SeoData shape:

type SeoData = {
  title?: string;
  description?: string;
  keywords?: string;
  author?: string;
  robots?: string;
  language?: string;
  canonical?: string;
  alternate?: { hrefLang?: string; href?: string }[];
  revisitAfter?: string;
  geo?: { region?: string; placename?: string; position?: string; ICBM?: string };
  og?: {
    type?: string; url?: string; title?: string; description?: string;
    image?: string; imageAlt?: string; locale?: string; siteName?: string;
  };
  twitter?: {
    card?: string; site?: string; creator?: string;
    url?: string; title?: string; description?: string; image?: string;
  };
  jsonLd?: JsonLdData;
};

Seo / Styles / Scripts

Server-only JSX components — emit collected data during SSR and produce no output on the client (hydration skips them).

import { Seo, Styles, Scripts } from "@mateosuarezdev/flash/framework";

function EntryServer() {
  return (
    <html>
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Seo />      {/* → collected <meta>, <title>, <link>, JSON-LD */}
        <Styles />   {/* → <link rel="stylesheet" href="__FLASH_STYLES__" /> */}
        <Scripts />  {/* → <script type="module" src="__FLASH_SCRIPTS__"></script> */}
      </head>
      <body>...</body>
    </html>
  );
}

__FLASH_STYLES__ and __FLASH_SCRIPTS__ are placeholder strings replaced by injectFlashInternals.

WithModules

Root SSR wrapper that provides language and SEO context to the render tree. Must wrap renderToString / renderToStringAsync / renderToStream calls.

import {
  WithModules,
  createSeoCollector,
} from "@mateosuarezdev/flash/framework";
import { renderToString } from "@mateosuarezdev/flash/server";

const seoCollector = createSeoCollector();

const rawHtml = renderToString(
  <WithModules lang="en" seoCollector={seoCollector}>
    <EntryServer pathname={pathname} />
  </WithModules> as any,
);

After rendering, seoCollector.current contains all SEO data accumulated by setSeo() calls in the tree.

injectFlashInternals()

Replaces the __FLASH_STYLES__, __FLASH_SCRIPTS__, and __FLASH_SEO__ placeholder strings in the rendered HTML with the actual asset paths and collected SEO tags. In development mode it also injects a window.process polyfill so browser-served node_modules (like Legend State) have access to process.env.NODE_ENV.

import { injectFlashInternals } from "@mateosuarezdev/flash/framework";

const finalHtml = injectFlashInternals(rawHtml, {
  stylesPath: "/assets/styles.css",
  scriptsPath: "/src/entry-client.tsx",
  seoData: seoCollector?.current,
});

| Option | Type | Description | | ------------ | --------- | -------------------------------------------------------- | | stylesPath | string | Replaces __FLASH_STYLES__ in <Styles /> | | scriptsPath| string | Replaces __FLASH_SCRIPTS__ in <Scripts /> | | seoData | SeoData?| Replaces __FLASH_SEO__ placeholder from <Seo /> |


Server-Side Rendering

Import from @mateosuarezdev/flash/server.

renderToString() — synchronous

import { renderToString } from "@mateosuarezdev/flash/server";

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

renderToStringAsync() — waits for async components

import { renderToStringAsync } from "@mateosuarezdev/flash/server";

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

renderToStream() — progressive

import { renderToStream } from "@mateosuarezdev/flash/server";

const stream = renderToStream(<App />);

for await (const chunk of stream) {
  response.write(chunk);
}

Full dev server example

// index.tsx
import { renderToString } from "@mateosuarezdev/flash/server";
import {
  WithModules,
  createSeoCollector,
  injectFlashInternals,
} from "@mateosuarezdev/flash/framework";
import {
  buildSrcFileMap,
  buildRouteMap,
  matchRoute,
  createTranspiler,
  resolveImports,
} from "@mateosuarezdev/flash/bun";
import EntryServer from "./src/entry-server";

const transpiler = createTranspiler();
const srcFiles   = await buildSrcFileMap("src");
const routes     = await buildRouteMap("src/routes");

const server = Bun.serve({
  port: 5173,
  routes: {
    "/*": async (req) => {
      const url      = new URL(req.url);
      const pathname = url.pathname;

      const match = matchRoute(routes, pathname);
      if (match) {
        const seoCollector = createSeoCollector();
        const rawHtml = renderToString(
          <WithModules lang="en" seoCollector={seoCollector}>
            <EntryServer pathname={pathname} params={match.params} />
          </WithModules> as any,
        );
        const finalHtml = injectFlashInternals(rawHtml, {
          stylesPath: "/assets/styles.css",
          scriptsPath: "/src/entry-client.tsx",
          seoData: seoCollector?.current,
        });
        return new Response(finalHtml, {
          headers: { "Content-Type": "text/html" },
        });
      }

      // Serve transpiled source files
      const filePath = srcFiles.get(pathname);
      if (!filePath) return new Response("Not Found", { status: 404 });

      const code      = await Bun.file(filePath).text();
      const loader    = filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? "tsx" : "ts";
      let transpiled  = transpiler.transformSync(code, loader);
      transpiled      = await resolveImports(transpiled, filePath);

      return new Response(Bun.gzipSync(transpiled), {
        headers: {
          "Content-Type": "application/javascript",
          "Content-Encoding": "gzip",
        },
      });
    },
  },
});

Bun Utilities

Import from @mateosuarezdev/flash/bun. These utilities require a Bun runtime (optional peer dependency).

Dev Server Helpers

createTranspiler()

Creates a Bun.Transpiler pre-configured for Flash JSX, browser target, and public env defines.

import { createTranspiler } from "@mateosuarezdev/flash/bun";

const transpiler = createTranspiler({
  jsxImportSource: "@mateosuarezdev/flash", // default
  define: { "MY_CONSTANT": JSON.stringify("value") },
});

const js = transpiler.transformSync(tsxSource, "tsx");

getPublicEnvDefines()

Returns a Bun-compatible define map with process.env.NODE_ENV plus any PUBLIC_* environment variables. Safe to pass to Bun.build or Bun.Transpiler.

import { getPublicEnvDefines } from "@mateosuarezdev/flash/bun";

const define = getPublicEnvDefines();
// { "process.env.NODE_ENV": '"development"', "process.env.PUBLIC_API_URL": '"..."' }

buildSrcFileMap()

Scans a source directory and builds a Map<urlPath, filePath> for serving files from the dev server. Registers paths with extension, without extension, and directory-index shorthands.

import { buildSrcFileMap } from "@mateosuarezdev/flash/bun";

const files = await buildSrcFileMap("src");
// "/src/routes/_route.tsx"  → "src/routes/_route.tsx"
// "/src/routes/_route"      → "src/routes/_route.tsx"

resolveImports()

Rewrites bare-specifier and relative imports in transpiled JS to browser-fetchable URLs. Resolves node_modules exports maps and adds file extensions to extensionless relative imports.

import { resolveImports } from "@mateosuarezdev/flash/bun";

let code = transpiler.transformSync(source, "tsx");
code = await resolveImports(code, filePath);
// from "@mateosuarezdev/flash" → from "/node_modules/@mateosuarezdev/flash/dist/index.js"
// from "./utils"               → from "./utils.ts"

generateHash()

Hashes a file's contents using Bun's built-in hasher — useful for cache-busting asset filenames.

import { generateHash } from "@mateosuarezdev/flash/bun";

const hash = await generateHash("./public/assets/styles.css");
// "a3f8b2c1"

distPathToHref()

Converts an absolute output file path into a web-relative href from the dist folder.

import { distPathToHref } from "@mateosuarezdev/flash/bun";

distPathToHref("C:/project/dist/assets/index-abc123.js");
// → "/assets/index-abc123.js"

Route Map

File-system-based route scanning. Directory names wrapped in [brackets] become :params.

src/routes/
  _route.tsx              → "/"
  review/_route.tsx       → "/review"
  blog/[slug]/_route.tsx  → "/blog/:slug"
  shop/[id]/edit/         → "/shop/:id/edit"

buildRouteMap()

Scans for _route.{tsx,jsx} files and returns a sorted route manifest. More specific routes (fewer dynamic segments) sort first, so /products/featured always beats /products/:id.

import { buildRouteMap } from "@mateosuarezdev/flash/bun";

const routes = await buildRouteMap("src/routes");
// [
//   { pattern: "/products/featured", filePath: "..." },
//   { pattern: "/products/:id",      filePath: "..." },
//   { pattern: "/review",            filePath: "..." },
//   { pattern: "/",                  filePath: "..." },
// ]

Specificity score: staticSegments × 1000 + totalSegments × 10 − dynamicSegments

matchRoute()

Matches a pathname against the route manifest, returning the first match and any extracted params.

import { matchRoute } from "@mateosuarezdev/flash/bun";

const match = matchRoute(routes, "/blog/hello-world");
// { route: { pattern: "/blog/:slug", ... }, params: { slug: "hello-world" } }

const miss = matchRoute(routes, "/does-not-exist");
// null

buildMpa()

Orchestrates a full multi-page app build: cleans dist, copies public, runs the setup hook (CSS build, etc.), bundles client JS with Bun.build, discovers routes, renders each page to HTML, and writes index.html files.

import { buildMpa } from "@mateosuarezdev/flash/bun";
import {
  WithModules,
  createSeoCollector,
  injectFlashInternals,
} from "@mateosuarezdev/flash/framework";
import { renderToString } from "@mateosuarezdev/flash/server";
import EntryServer from "../src/entry-server";

await buildMpa({
  entrypoints: ["./src/entry-client.tsx"],
  outDir: "./dist",
  publicDir: "./public",
  routesDir: "./src/routes",

  // Runs after public copy, before JS bundling — good for CSS
  async setup({ outDir }) {
    await Bun.build({ entrypoints: ["./src/styles.css"], outdir: outDir });
  },

  // Called once per route — return the final HTML for that page
  async renderPage(pathname, { bundleHref }) {
    const seoCollector = createSeoCollector();
    const rawHtml = renderToString(
      <WithModules lang="en" seoCollector={seoCollector}>
        <EntryServer pathname={pathname} />
      </WithModules> as any,
    );
    return injectFlashInternals(rawHtml, {
      stylesPath: "/assets/styles.css",
      scriptsPath: bundleHref,
      seoData: seoCollector?.current,
    });
  },
});

Options:

| Option | Type | Default | | ------------ | -------------------------------------------------------------------- | ------------------- | | entrypoints| string[] | required | | outDir | string | "./dist" | | publicDir | string | "./public" | | routesDir | string | "./src/routes" | | define | Record<string, string> | getPublicEnvDefines() | | setup | (ctx: { outDir: string }) => Promise<void> \| void | — | | renderPage | (pathname: string, ctx: { bundleHref: string }) => Promise<string> \| string | required |


Animations & Performance

onBeforeExit with any animation library

Flash's exit animation model is library-agnostic:

// Framer Motion
import { animate } from "framer-motion";

onBeforeExit(async (token) => {
  const anim = animate(ref, { opacity: 0 }, { duration: 0.3 });
  token.onCancel(() => { anim.stop(); animate(ref, { opacity: 1 }); });
  await anim.finished;
});

// CSS transitions
onBeforeExit(async () => {
  ref.style.opacity = "0";
  await new Promise((r) => setTimeout(r, 300));
});

// CSS classes
onBeforeExit(async () => {
  ref.classList.add("exit");
  await new Promise((r) => setTimeout(r, 300));
});

Frame Scheduler

Batches DOM reads and writes into separate phases to prevent layout thrashing:

import { frame } from "@mateosuarezdev/flash";

// Separated phases
frame.read(()   => { const h = el.offsetHeight; /* read */ });
frame.update(() => { position += velocity; });
frame.render(() => { el.style.transform = `translateY(${position}px)`; });

// Chained with data flow
frame.chain({
  read:   () => ({ h: el.offsetHeight, w: el.offsetWidth }),
  render: ({ h, w }) => {
    el.style.height = `${h * 2}px`;
    el.style.width  = `${w * 2}px`;
  },
});

// Continuous animation loop
const stop = frame.render(() => {
  el.style.transform = `rotate(${angle++}deg)`;
}, true);

frame.cancel(stop); // stop when done

FLIP Animations

Performant layout animations via the FLIP technique (animate transforms instead of layout properties):

import { flip, flipGroup } from "@mateosuarezdev/flash";

// Single element
flip(el, () => { el.classList.add("expanded"); }, { duration: 300 });

// Group reorder
flipGroup(
  document.querySelectorAll(".item"),
  () => { container.appendChild(items[2]); },
  { duration: 400, easing: "ease-out-cubic" },
);

View Transitions API

import { startViewTransition } from "@mateosuarezdev/flash";

const expanded = observable(false);

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

<div
  class={() => expanded.get() ? "expanded" : "collapsed"}
  viewTransitionName="container"
>
  Content
</div>

Package Entry Points

| Entry point | Contents | | ----------------------------------- | ---------------------------------------------------------------------------- | | @mateosuarezdev/flash | Runtime, context API, lifecycle hooks, observables, frame scheduler, FLIP | | @mateosuarezdev/flash/server | renderToString, renderToStringAsync, renderToStream | | @mateosuarezdev/flash/framework | setSeo, createSeoCollector, Seo, Styles, Scripts, WithModules, injectFlashInternals | | @mateosuarezdev/flash/router | Router, pathname, push, replace, back, onUrlChange | | @mateosuarezdev/flash/bun | buildRouteMap, matchRoute, buildMpa, createTranspiler, buildSrcFileMap, resolveImports, getPublicEnvDefines, generateHash, distPathToHref | | @mateosuarezdev/flash/utils | Shared utility types | | @mateosuarezdev/flash/debug | Debug logging helpers |


API Reference

Core (@mateosuarezdev/flash)

Runtime

| Export | Description | | --------------------------- | ----------------------------------------------------- | | render(el, container) | Mount component tree to the DOM | | hydrate(el, container) | Hydrate server-rendered HTML | | Fragment | Render multiple children without a wrapper |

Lifecycle

| Export | Description | | ---------------------------------------------- | ---------------------------------------------------------------------------- | | onMount(cb) | Runs after component's DOM is attached | | onUnmount(cb) | Runs when component is removed from DOM | | onBeforeExit(async (token) => {}) | Pauses unmount tree — use for exit animations, data saving |

Context

| Export | Description | | --------------------------- | ----------------------------------------------------- | | createContext(default) | Create a typed context | | useContext(ctx) | Read nearest context value |

Observables (re-exported from Legend State)

| Export | Description | | ------------------- | ----------------------------------------------------- | | observable(value) | Create an Observable<T> | | observe(fn) | Run a reactive effect, returns dispose function | | isObservable(v) | Type guard for Observable<T> | | OpaqueObject<T> | Type wrapper preventing deep Legend State transform |

Signals helpers

| Export | Description | | -------------------- | ----------------------------------------------------- | | Hybrid<T> | T \| Observable<T> \| (() => T) | | HybridReactive<T> | T \| (() => T) — what applyProps understands | | ReactiveProps<T> | Maps all props to Hybrid<T>, skips children/key/ref | | $(value) | Convert Hybrid<T>HybridReactive<T> for () => boundaries | | peek(value) | Non-tracking read of any Hybrid<T> |

Animations & Performance

| Export | Description | | ----------------------------------- | ---------------------------------------------------- | | startViewTransition(cb) | Trigger View Transitions API | | frame.read(cb) | Schedule DOM reads | | frame.update(cb) | Schedule computations | | frame.render(cb, keepAlive?) | Schedule DOM writes | | frame.chain({ read, update, render }) | Chained phases with typed data flow | | frame.cancel(cb) | Cancel a keep-alive render callback | | flip(el, applyFn, opts) | FLIP animation for a single element | | flipGroup(els, applyFn, opts) | FLIP animation for a list of elements | | flipMove(el, newParent, opts) | Animate an element moving to a new container |

Special JSX props

| Prop | Description | | ----------------------- | ----------------------------------------------------- | | ref={(el) => ...} | DOM element reference callback | | key={value} | Unique identifier for keyed list items | | viewTransitionName | Named view transition target |

Framework (@mateosuarezdev/flash/framework)

| Export | Description | | ------------------------------- | -------------------------------------------------------------------- | | setSeo(data, options?) | Accumulate SEO data (SSR) or update <head> tags (client) | | createSeoCollector() | Create an empty SeoCollector to pass to WithModules | | <Seo /> | Server-only — renders collected SEO tags into HTML | | <Styles /> | Server-only — renders <link rel="stylesheet" href="__FLASH_STYLES__" /> | | <Scripts /> | Server-only — renders <script> tag with __FLASH_SCRIPTS__ | | WithModules | Root SSR wrapper — provides lang and seoCollector context | | injectFlashInternals(html, opts) | Replace Flash placeholders with real paths and inject SEO + process polyfill |

Router (@mateosuarezdev/flash/router)

| Export | Description | | --------------------- | ------------------------------------------------ | | Router | Provider component — starts tracking navigation | | pathname | Observable<string> — current pathname | | push(to) | history.pushState | | replace(to) | history.replaceState | | back() | history.back() | | onUrlChange(cb) | Subscribe to URL changes, auto-disposes on unmount |

Bun (@mateosuarezdev/flash/bun)

| Export | Description | | ----------------------------------- | ------------------------------------------------------- | | getPublicEnvDefines() | define map for Bun.build / Bun.Transpiler | | createTranspiler(opts?) | Bun transpiler pre-configured for Flash JSX | | buildSrcFileMap(dir) | URL → file path map for dev server | | resolveImports(code, filePath) | Rewrite imports to browser-fetchable URLs | | generateHash(filePath) | 8-char content hash for cache-busting | | distPathToHref(filePath) | Absolute dist path → /-relative web href | | buildRouteMap(routesDir?) | Scan _route.tsx files → sorted Route[] manifest | | matchRoute(routes, pathname) | Match pathname → { route, params } or null | | buildMpa(options) | Full MPA build pipeline |


License

MIT © Mateo Suarez