npm stats
  • Search
  • About
  • Repo
  • Sponsor
  • more
    • Search
    • About
    • Repo
    • Sponsor

Made by Antonio Ramirez

@lukeed/bongo

0.0.12

@lukeed

npmHomeRepoSnykSocket
Downloads:2
$ npm install @lukeed/bongo
DailyWeeklyMonthlyYearly

bongo

A static site generator built on Rolldown and LightningCSS.

Published as @lukeed/bongo, but projects should install and reference it through the bongo alias:

bun add bongo@npm:@lukeed/bongo

All examples below use bongo.

Overview

bongo processes content files through a loader pipeline, applies layouts and partials with a tempura-based template engine, and bundles client assets with Rolldown for JavaScript and LightningCSS for CSS.

Usage

bongo build
bongo dev

CLI options:

bongo build -C site --minify
bongo dev -C site --host --port 3000

How It Works

When you run bongo build:

  1. Config is loaded from bongo.config.{ts,mjs,cjs,js} by walking upward from cwd. Bun and Deno import TypeScript config files natively. Node bundles the config through Rolldown to a temp .mjs and dynamic-imports it.
  2. Layouts and partials are collected from layouts/ and partials/. Handlebars files (.hbs) are compiled with tempura. JavaScript and TypeScript partials are loaded as modules.
  3. Content files are passed through registered loaders, then rendered through the matching layout chain.
  4. Rendered HTML is scanned for <bongo:style>, <bongo:script>, and <bongo:client> elements. Assets are registered and replaced with placeholder tokens.
  5. Asset groups are flushed. JavaScript goes through Rolldown; CSS goes through LightningCSS. Output filenames include content hashes.
  6. Placeholders are substituted with external asset URLs or inline asset contents.
  7. Pages are written to public/.

Config

import { markdown } from '@bongo/markdown';
import { shiki } from '@bongo/markdown/highlight';

export default {
	$site: {
		title: 'My Site',
		url: 'https://example.com/',
		lang: 'en',
	},

	$utils: {
		excerpt(text: string, len: number) {
			return text.slice(0, len);
		},
	},

	plugins: [
		markdown({
			highlighter: shiki({
				theme: 'github-dark',
				langs: ['js', 'ts', 'css', 'bash'],
			}),
		}),
	],
};

Config fields:

  • $site — site metadata; available in templates as {{$site.title}}. The url field also drives publicPath.
  • $assets — loader map keyed by file extension.
  • $utils — helper functions available in templates.
  • $partials — programmatic partial functions.
  • plugins — array of partial config objects, merged in order.

Loaders

Loaders transform content files by file extension. Register them with $assets:

export const $assets = {
	md(filename, content, utils) {
		return {
			filename: filename + '.html',
			content: renderedHtml,
			metadata: { title: '...' },
		};
	},
};

Note: You can use @bongo/markdown for a ready-made solution.

Loaders are chained by extension. A .md file produces .md.html, the .html extension stops the chain and enters the template pipeline.

Loader utilities:

  • utils.decode(uint8array) — convert a buffer to a string.
  • utils.render(partialName, args) — invoke a registered partial.
  • utils.frontmatter(content) — parse YAML-like frontmatter delimited by ---.

publicPath

The publicPath prefix applied to external asset URLs is derived from $site.url:

  • Root-served site (https://example.com) -> publicPath = /
  • Subpath-served site (https://example.com/blog/) -> publicPath = /blog/

No manual configuration is required.

Templates

Templates use tempura syntax. Asset entrypoints are declared with <bongo:style>, <bongo:script>, and <bongo:client> custom elements.

{{! layouts/html.hbs }}
<!doctype html>
<html lang="{{$site.lang}}">
<head>
  <meta charset="utf-8"/>
  <title>{{ $page.title }}</title>

  {{! External CSS: emits <link rel="stylesheet" href="/style.hash.css"/> }}
  <bongo:style src="app.css"></bongo:style>

  {{! Inline CSS: splices built file contents into a <style> block }}
  <bongo:style src="print.css" inline></bongo:style>
</head>
<body>
  {{{ $page.content }}}

  {{! External JS: emits <script type="module" src="/main.hash.js"></script> }}
  <bongo:script src="main.ts"></bongo:script>

  {{! Async external JS }}
  <bongo:script src="analytics.ts" async></bongo:script>

  {{! Inline JS: file contents spliced into the <script> element }}
  <bongo:script src="boot.ts" inline></bongo:script>
</body>
</html>

<bongo:style> attributes:

  • src="..." — path relative to assets/.
  • inline — splice built CSS directly into a <style> block. Omit for an external stylesheet link.
  • global — disable CSS modules auto-detection on *.module.css sources.

<bongo:script> attributes:

  • src="..." — path relative to assets/.
  • inline — splice built JavaScript directly into a <script> block.
  • async — add the async attribute to an external script.
  • defer — add the defer attribute for CJS output. ESM modules are deferred by the browser.
  • format="esm"|"cjs" — output module format. Defaults to esm.
  • target="..." — browserslist target string.

<bongo:client> bundles bongo's browser runtime and emits a module script that calls client(...), plus the hidden aria-live region used to announce page-title changes:

<bongo:client />
<bongo:client transitions />
<bongo:client transitions="slide" />

Multiple <bongo:client> tags on a page share one bundled runtime.

View Transitions

Add transition:name and transition:animate to HTML elements in a layout or partial. bongo rewrites them at build time.

<header transition:name="site-header">...</header>
<nav transition:name="nav" transition:animate="slide">...</nav>
  • transition:name="NAME" — removed from the element; view-transition-name: NAME is merged into the element's style attribute.
  • transition:animate="KEYFRAME" — requires a named @keyframes KEYFRAME in your CSS. bongo injects a <style data-bongo-transitions> block in <head> that wires the keyframe to matching view-transition pseudo-elements.

If transition:animate is present without transition:name, an automatic name is generated.

Partials

{{#partial src="header" }}
{{#partial src="card" title="Hello" }}

Template Variables

  • $site — global site config.
  • $page — current page. Includes path, url, title, summary, params, section, tags, and content.
  • $pages — every page in the build, sorted by path.
  • $section — pages scoped to the current page's section directory.
  • $tags — array of { tag, pages } entries.
  • $assets — configured loader map.
  • $utils — configured helper functions.

Content files ending in .hbs are tempura-compiled with the same context as layouts: $site, $page, $pages, $section, $tags, $assets, $utils, plus params as an alias for $page.params.

The current page's own $page.content is empty while its body is compiling. It is the value produced by that compile.

CSS

All CSS is handled by LightningCSS: @import bundling, autoprefixing, nesting, and optional minification.

.module.css files automatically enable CSS modules. Non-module files can opt in explicitly when registering the entry.

Dev Server

bongo dev starts the HTTP server and runs the initial build behind it. File changes in assets/, layouts/, partials/, or content/ trigger a rebuild.

Rebuilds push one of these SSE messages to the browser:

MessageMeaning
{ type: 'reload' }Successful rebuild; browser reloads the page.
{ type: 'error', errors }Build failed; full-viewport error overlay mounts.
{ type: 'error-clear' }Next successful build; overlay unmounts, then the browser reloads.

The hmr message type exists in the type union for forward compatibility. Today, file changes trigger a full reload.

Client Runtime

bongo/client turns a statically generated site into a client-navigated SPA.

import { client, navigate, on, off } from 'bongo/client';

The simplest setup is the <bongo:client /> element:

{{! layouts/html.hbs }}
<bongo:client transitions />

That expands at build time to:

<div id="bongo:live" role="status" aria-live="polite" style="...visually-hidden..."></div>
<script type="module">
  import { client } from "/client.D59fAaym.js";
  client({ transitions: true });
</script>

The runtime updates #bongo:live with the new page title on navigation. If the element is missing, announcements are skipped silently.

If you also need custom event handlers or boot code, keep a separate <bongo:script>:

<bongo:client transitions />
<bongo:script src="main.ts"></bongo:script>
import { on } from 'bongo/client';

on('bongo:navigated', ({ to }) => {
	console.log('navigated to', to.pathname);
});

Or call client() yourself:

import { client } from 'bongo/client';

client({ transitions: true });

ClientOptions

OptionTypeDefaultDescription
transitionsboolean | 'fade' | 'initial' | 'slide' | stringfalseView-transition preset. true / 'fade' injects a cross-fade keyframe. 'slide' injects a slide keyframe. 'initial' wraps the swap without injecting CSS. Custom string wraps only; user CSS provides the rules. Any falsy value disables transitions. Opt a link out with transition:disable.
prefetchboolean | 'hover' | 'tap'falsePrefetch same-origin links into the in-memory HTML cache. true / 'hover' fires after hover settles, or immediately on touch. 'tap' fires on mousedown / touchstart. Opt a link out with prefetch:disable.

Events

Subscribe with on(event, handler). It returns an unsubscribe function. Remove a specific handler with off(event, handler).

on('bongo:navigated', ({ to }) => {
	console.log('navigated to', to.pathname);
});
EventDetail shapeFires when
bongo:ready{ url: URL }Once, immediately after client() is called.
bongo:navigate{ to: URL; from: URL }Navigation intercepted, before the fetch.
bongo:replace{ to: URL; from: URL; doc: Document }HTML fetched and parsed; swap has not happened yet.
bongo:replaced{ to: URL; from: URL }DOM swapped.
bongo:navigated{ to: URL; from: URL }Navigation settled.
bongo:error{ to: URL; error: Error }Navigation fetch or swap threw.

Programmatic Navigation

navigate('/about');
navigate('/old-url', { replace: true });

navigate() returns Promise<void> and resolves when navigation and any transition complete.

Opt Out

Force a full-page load:

<a href="/legacy" transition:disable>Legacy dashboard</a>

Disable prefetch without forcing a full-page load:

<a href="/logout" prefetch:disable>Log out</a>

Scroll Restoration

client() sets history.scrollRestoration = 'manual'.

  • Back / forward restores the saved {x, y} position for that URL.
  • Push / replace scrolls to the hash element if present, otherwise (0, 0).

Browser Support

The Navigation API is available in Chrome and Edge, and is behind a flag or unsupported in Firefox and Safari at time of writing. If the API is absent, client() emits bongo:ready and returns without installing navigation interception.

TypeScript

bongo ships ambient JSX types for its custom elements and attributes. Extend the bundled config:

{
	"extends": "bongo/tsconfig",
	"compilerOptions": {
		// your own settings
	}
}

The augmentation adds:

  • <bongo:client>, <bongo:script>, and <bongo:style> intrinsic elements.
  • transition:name and transition:animate on every HTML element.
  • transition:disable and prefetch:disable on anchors.

Or reference the types directly:

/// <reference types="bongo/jsx" />