@takazudo/zfb
Rust-built static-site engine for Astro and Next.js users — millisecond rebuilds, single binary.
The public SDK module for zfb: islands, content collections,
pagination, config, plugins, and frontmatter helpers. User pages reach this
package through the bare specifier "zfb" — the build pipeline aliases that
specifier to @takazudo/zfb so user TSX can write:
import { Island } from "zfb";
Full documentation: https://takazudomodular.com/pj/zudo-front-builder/. Source: https://github.com/Takazudo/zudo-front-builder.
Install
npm install @takazudo/zfb
# or: pnpm add @takazudo/zfb
# or: yarn add @takazudo/zfb
What lives here
This package is the canonical TypeScript source for the zfb SDK
surface. Today it covers:
<Island when="visible|idle|load|media" media="...">— JSX wrapper that marks a region for client-side hydration. PassingssrFallbackswitches to the SSR-skip marker (data-zfb-island-skip-ssr).scheduleHydrate(target, when, fire)— the runtime branching helper consumed by the hydration runtime.mountIslands(manifest),mountNewIslands(),cancelPendingIslands(), andunmountIslands(...)— public island lifecycle helpers used by the generated islands runtime and by the client router after body swaps.When,WHEN_VALUES,DEFAULT_WHEN,isWhen,resolveWhen— type and runtime utilities pinning the spelling of the four modes.getCollection(name),getEntry(name, slug), andparseFrontmatter(raw)— content collection helpers exported fromzfb/content.parseFrontmatteris part of the public SDK surface so consumers can write custom content loaders that reuse the v0 frontmatter parser without re-implementing it.defaultComponents— eleven-entry per-element override map (h2,h3,h4,p,a,strong,blockquote,ul,ol,table,code) ported from zudo-doc'shtmlOverridesconvention.h1is deliberately omitted because page titles render<h1>from frontmatter. Each entry is a thin passthrough and is also exported as a named const (ContentParagraph,ContentLink, …) so consumers can tree-shake-import a single override. Spread into acomponentsprop to compose with custom overrides:import { defaultComponents } from "zfb"; <entry.Content components={{ ...defaultComponents, h2: MyFancyH2 }} />mergeMdxComponents(globalSlot, perCall)— precedence merge helper for the MDX component map (defaultComponents< global slot < per-call overrides).paginate(items, opts), plusPaginatedPage<T>/PaginateRoute<T>— exported fromzfb/paginate.defineConfig(config)— exported fromzfb/configfor thezfb.config.tsform (the recommended way to author a zfb project's configuration; the back-compatzfb.config.jsonform is still supported).definePlugin(plugin)— identity helper exported fromzfb/pluginsand the root barrel so plugin authors get typed lifecycle hooks without changing runtime behavior.clientScript(name)— SSR helper that returns the stable URL for a named client-script asset.slugify(input)andSlugAllocator— exported from the root barrel and thezfb/slugifysubpath for heading-id parity with the Rust content pipeline.
The package is JSX-runtime-agnostic: the Island component does not
import preact or react, so it works under either framework adapter
without bundling the wrong runtime. react is listed as a peer
dependency but is optional (peerDependenciesMeta.react.optional)
— a preact/compat-only consumer does not need react installed and
does not need auto-install-peers=true.
Usage
import { Island } from "zfb";
import { Counter } from "../components/Counter.tsx"; // a "use client" component
export default function Page() {
return (
<>
<h1>Welcome</h1>
{/* Hydrate immediately on page load (default). */}
<Island>
<Counter />
</Island>
{/* Hydrate during the next idle callback. */}
<Island when="idle">
<Counter />
</Island>
{/* Hydrate only when the island first scrolls into view. */}
<Island when="visible">
<Counter />
</Island>
{/* Hydrate when a CSS media query first matches. */}
<Island when="media" media="(max-width: 720px)">
<Counter />
</Island>
{/* Skip SSR for the heavy child and render a placeholder instead. */}
<Island when="idle" ssrFallback={<div>Loading…</div>}>
<Counter />
</Island>
</>
);
}
The four when= modes
when |
Trigger | Fallback |
|---|---|---|
"load" |
Synchronous, immediate fire after registration. Default. | n/a |
"idle" |
requestIdleCallback |
setTimeout(0) when not available |
"visible" |
IntersectionObserver, threshold 0, first intersection only |
Immediate fire when IntersectionObserver is missing |
"media" |
matchMedia(media), first matching change only |
Immediate fire when matchMedia or media is missing |
Unknown values produce a console.warn in development builds and fall
back to "load".
when="media" requires a media prop containing a CSS media query
string. Supplying media with any other when value is ignored and
warns in development builds.
Build-time output
The wrapper is intentionally type-erased at the JSX boundary. It reads
the wrapped child's JSX type identity (displayName, then name, then
host tag name) and writes the component name immediately. At the call
site, <Island when="visible"><Counter count={1} /></Island> renders as:
<div data-zfb-island="Counter" data-when="visible" data-props='{"count":1}'>
<!-- rendered child output -->
</div>
data-props carries the wrapped child's serializable own props across
the SSR-to-hydration boundary. The wrapper omits children, omits the
attribute entirely when there is no useful props payload, and the runtime
falls back to {} when the attribute is missing or malformed.
When ssrFallback is supplied, the heavy child is not rendered at SSR
time. The wrapper writes data-zfb-island-skip-ssr="ComponentName"
instead of data-zfb-island, still includes data-when / data-media
and data-props when applicable, and renders the fallback markup inside
the wrapper. The client runtime treats that marker as a render target
rather than a hydration target.
Runtime helper
The hydration runtime imports (or inlines) scheduleHydrate from this
package:
import {
scheduleHydrate,
mountIslands,
mountNewIslands,
cancelPendingIslands,
unmountIslands,
} from "@takazudo/zfb/runtime";
for (const el of document.querySelectorAll<HTMLElement>("[data-zfb-island]")) {
const when = el.getAttribute("data-when") ?? "load";
scheduleHydrate(el, when, () => hydrateOne(el));
}
scheduleHydrate returns a cancel function that aborts the schedule
if hydration has not fired yet. After firing, calling cancel is a
no-op.
mountIslands(manifest) captures the generated island manifest and
mounts both hydrated markers (data-zfb-island) and SSR-skip markers
(data-zfb-island-skip-ssr). mountNewIslands() re-walks the current
document after a client-router body swap using that captured manifest.
cancelPendingIslands() cancels deferred idle / visible / media
schedules before a swap, and unmountIslands(root, incomingBody) runs
framework cleanup for discarded islands while preserving matching
data-zfb-transition-persist islands.
Post-mount marker
The runtime writes data-zfb-island-mounted after the generated mount() function
returns. It applies to both hydrated data-zfb-island markers and SSR-skip
data-zfb-island-skip-ssr markers. Consumers can gate pre-hydration styling before the
runtime's mount function returns, with the practical CSS selector:
[data-zfb-island]:not([data-zfb-island-mounted]) {
opacity: 0.7;
}
This means "the runtime called the mount function and it returned", not "the
component is interactive". React's hydrateRoot is internally concurrent, and the
generated mount() can silently no-op, so the attribute is a lifecycle signal rather
than proof that interaction is ready.
The marker is removed when an island is unmounted. During a body swap, an unchanged
data-zfb-transition-persist island keeps its mounted instance and marker when the
same id exists in the incoming body; a discarded island has its marker cleared and is
mounted again from the incoming markup. A props-changed persisted island loses the
marker while its old instance is torn down and receives it again after the forced
remount returns. A fresh runtime module strips stale markers from elements it has not
mounted before, then writes its own marker after mounting.
Markdown / GFM config
ZfbConfig.markdown.gfm controls which GitHub-Flavored-Markdown
constructs the MDX parser recognises. The field accepts three shapes:
Shorthand boolean — turn every GFM construct on or off in one step. Use this when you want the full GFM surface.
// zfb.config.ts import { defineConfig } from "zfb/config"; export default defineConfig({ markdown: { gfm: true, // strikethrough + table + autolink-literal + task-list-item + footnote-definition }, });Partial object — toggle individual constructs. Fields you omit fall back to the conservative default (
strikethrough: true,table: true,autolinkLiteral: true, everything else off).// zfb.config.ts import { defineConfig } from "zfb/config"; export default defineConfig({ markdown: { gfm: { strikethrough: true, table: true, autolinkLiteral: false, // explicit opt-out taskListItem: false, footnoteDefinition: false, }, }, });Omitted entirely — the parser uses the conservative default.
~~text~~parses as<del>text</del>, pipe tables render as<table>, and bare URLs likehttps://example.combecome links; task lists and footnote definitions stay off.export default defineConfig({ // no `markdown` field — strikethrough + table + autolink literals on, // task lists + footnotes off });
The five constructs you can toggle are: strikethrough, table,
autolinkLiteral, taskListItem, footnoteDefinition.
Projects that previously relied on raw ~~text~~ passing through as
literal characters should set markdown: { gfm: { strikethrough: false } }
or markdown: { gfm: false } to restore the old behaviour.
Tests
pnpm --filter @takazudo/zfb test
The tests run under vitest with happy-dom as the DOM implementation; no real browser is required.