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

bangala

v0.5.0

Published

The full-stack framework that ships minimal JavaScript.

Readme

bangala.js

The full-stack framework that ships minimal JavaScript.

License: MIT npm PRs Welcome


Why bangala.js?

Most frameworks ship a runtime to the browser even when the page is plain content. bangala.js inverts the default: your pages are HTML, and JavaScript only travels to the client where you explicitly ask for it.

  • HTML-first. A .bangala file is HTML enriched with a server frontmatter, {expr} interpolation, and {#if} / {#each} blocks. The compiler emits an ESM module whose render() is pure string concatenation — the fastest SSR primitive there is.
  • Framework-agnostic islands. Interactive components are isolated islands. Each island is just a module that exports mount(el, props, ctx). Use vanilla DOM, Preact, Lit, htmx, or a web component — bangala does not impose a UI library.
  • Near-zero JS by default. A static page ships zero framework bytes. Only islands fetch their own module, on demand, with the strategy you pick (load, idle, or visible).

Status

v0.5.0 ships sub-project 1 (the .bangala compiler and server runtime), sub-project 2 (the client islands runtime), sub-project 3 (file-based routing), and sub-project 4 (Vite dev server + build helpers). It also ships sub-project 5: CLI, scaffolding, and deploy adapters. Design specs live under docs/superpowers/specs/.

Install

npm install bangala

Requires Node.js 22 or newer.

Quickstart

1. Create a project.

npx bangala create my-site --adapter netlify
cd my-site
npm install
npm run dev

2. Write a .bangala page. pages/index.bangala:

---
const { user } = props
---
<h1>Hello {user.name}</h1>

3. Build or deploy from the CLI.

npx bangala build
npx bangala deploy vercel --force

4. Compile on the server. compile() takes a source string and returns the generated ESM module text plus the island manifest:

import { compile } from "bangala";

const source = await fs.readFile("pages/index.bangala", "utf8");
const result = compile(source, { filename: "pages/index.bangala" });

// result.code         — the ESM module source (string)
// result.islands      — [{ componentPath, strategy }, ...]
// result.dependencies — paths of imported .bangala files (for watch mode)

5. Execute the compiled module. It exports render(props) returning a Promise of HTML. How you transpile and import the emitted code is your call (Vite, esbuild, tsx, or another ESM loader). Once you have the module:

const html = await page.render({ user: { name: "Ada" } });
// <h1>Hello Ada</h1>

6. Build a route manifest. The routing core is published as bangala/router. It is framework-agnostic and does not start an HTTP server; the Vite helpers and CLI wire it into dev/build tooling:

import { discoverRoutes, matchRoute } from "bangala/router";

const routes = await discoverRoutes("pages");
const match = matchRoute(routes, "/blog/hello-world");

// pages/index.bangala          -> /
// pages/blog/[slug].bangala    -> /blog/:slug
// pages/docs/[...parts].bangala -> /docs/*parts

7. Wire the client runtime in the page HTML. The runtime is published as bangala/client/auto. The Vite build helper bundles it automatically; if you are wiring your own bundler, include it as a module script:

<script type="module" src="/bangala-client.js"></script>

Or call hydrate() yourself for fine-grained control:

import { hydrate } from "bangala/client";

hydrate(document, {
  onError: (error) => reportToMonitoring(error),
});

8. Use the Vite helpers. bangala/vite ships the plugin and programmable dev/build helpers used by the CLI:

import { createBangalaDevServer, buildBangala } from "bangala/vite";

const dev = await createBangalaDevServer({ root: process.cwd() });
await dev.listen(5173);

await buildBangala({
  root: process.cwd(),
  outDir: "dist",
  prerender: ["/blog/hello-world"], // dynamic routes opt in explicitly
});

API reference

compile(source, options)

Compiles a .bangala source string into an ESM module.

| Parameter | Type | Description | |---|---|---| | source | string | The .bangala source. | | options.filename | string | File path, used in error messages. |

Returns CompileResult:

interface CompileResult {
  code: string;             // generated ESM module source
  islands: IslandRef[];     // islands found in this file
  dependencies: string[];   // absolute/relative paths of imported .bangala files
}

interface IslandRef {
  componentPath: string;
  strategy: "client:load" | "client:idle" | "client:visible";
}

The compiled module exports render(props): Promise<string>.

discoverRoutes(root, options?)

Walks a directory and returns a sorted manifest of .bangala page routes. Private files and folders starting with _ or . are ignored.

import { discoverRoutes, createRoutes, matchRoute } from "bangala/router";

const routes = await discoverRoutes("pages");
const match = matchRoute(routes, "/docs/install");

Supported file conventions:

| File | Route | |---|---| | pages/index.bangala | / | | pages/about.bangala | /about | | pages/blog/index.bangala | /blog | | pages/blog/[slug].bangala | /blog/:slug | | pages/docs/[...parts].bangala | /docs/*parts |

matchRoute(routes, pathname) returns { route, pathname, params } or null. Dynamic params are strings; catch-all params are string arrays.

bangala() Vite plugin

The Vite plugin compiles .bangala files on demand, resolves component imports, and installs a route middleware when pages is enabled.

import { defineConfig } from "vite";
import { bangala } from "bangala/vite";

export default defineConfig({
  plugins: [bangala({ pages: "pages" })],
});

createBangalaDevServer(options?)

Creates a Vite dev server configured for Bangala routes. It does not call listen() for you, so CLIs and custom servers can decide the host/port.

const server = await createBangalaDevServer({ root: process.cwd() });
await server.listen(5173);

buildBangala(options?)

Bundles bangala/client/auto and prerenders HTML files into outDir. Static routes are prerendered by default. Dynamic routes must be passed through prerender.

await buildBangala({
  root: process.cwd(),
  pages: "pages",
  outDir: "dist",
  prerender: ["/blog/first-post"],
});

CLI

The package exposes a bangala binary:

bangala dev --port 5173
bangala build --out-dir dist --prerender /blog/first-post
bangala create my-site --adapter netlify
bangala deploy cloudflare-pages --force

The generated project uses the same Vite helpers under the hood:

{
  "scripts": {
    "dev": "bangala dev",
    "build": "bangala build"
  }
}

bangala/adapters

Deployment adapters are plain file writers for static hosts:

import { applyDeployAdapter, listDeployAdapters } from "bangala/adapters";

console.log(listDeployAdapters());
await applyDeployAdapter(process.cwd(), "vercel", { outDir: "dist" });

Built-in adapters: static, netlify, vercel, cloudflare-pages.

hydrate(root?, options?)

Scans root (default: document) for <bangala-island> markers and hydrates each one according to its data-strategy. The call is idempotent — already hydrated markers are skipped.

interface HydrateOptions {
  onError?: (error: HydrationError) => void;
}

interface HydrationError {
  el: HTMLElement;
  code: ErrorCode;
  entry?: string;
  cause?: unknown;
}

Every error also dispatches a bubbling bangala:island-error CustomEvent on the affected <bangala-island> element, whose detail matches the HydrationError passed to onError. A throw from onError is swallowed so it cannot break the rest of the page.

Island module contract

An island is an ESM module that exports an async mount function:

export async function mount(
  el: HTMLElement,
  props: Record<string, unknown>,
  ctx: { strategy: "load" | "idle" | "visible"; entry: string },
): Promise<void> {
  // `el` still contains the SSR HTML — read it, augment it, or replace it.
  el.querySelector("button")!.addEventListener("click", () => {
    // ...
  });
}

The runtime guarantees that props has been parsed from data-props before mount is called. The return value is currently ignored; a cleanup function return is reserved for a future unmount/HMR signal.

Hydration strategies

| Directive | When it hydrates | |---|---| | client:load | Immediately, in the same tick as the scan. | | client:idle | At requestIdleCallback with a 2s timeout. Safari falls back to setTimeout(fn, 1). | | client:visible | When the element enters the viewport, with a 200px rootMargin. Falls back to immediate hydration when IntersectionObserver is unavailable. |

Error codes

Each error sets data-hydrated="error" and data-hydration-error="<code>" on the <bangala-island> element, logs to console.error, invokes onError, then dispatches bangala:island-error.

| Code | When it fires | |---|---| | missing-entry | The <bangala-island> element has no data-entry (or it is empty). | | invalid-props | JSON.parse(data-props) threw. (A missing data-props defaults to {} and is not an error.) | | unknown-strategy | data-strategy is present but not one of load, idle, visible. | | import-failed | import(data-entry) rejected (404, syntax error, network failure). | | missing-mount | The imported module did not expose a callable mount export. | | mount-failed | mount(el, props, ctx) threw or returned a rejected promise. |

The string codes are stable across versions; the human messages logged alongside them are free to evolve.

Roadmap

bangala.js is structured as five sub-projects, each with its own spec/plan cycle. v0.5.0 delivers all five planned v1 foundations:

| # | Sub-project | Status | |---|---|---| | 1 | .bangala compiler + server runtime | Shipped in v0.1.0 | | 2 | Client islands runtime | Shipped in v0.1.0 | | 3 | File-based routing | Shipped in v0.2.0 | | 4 | Dev server + build (Vite) | Shipped in v0.3.0 | | 5 | CLI + scaffolding + deploy adapters | Shipped in v0.5.0 |

Designs live in docs/superpowers/specs/.

Contributing

bangala.js is being built in the open. See CONTRIBUTING.md and the Code of Conduct.

License

MIT (c) bangala.js contributors