npm.io
2.11.0 • Published 27m agoCLI

@takazudo/zfb

Licence
MIT
Version
2.11.0
Deps
0
Size
488 kB
Vulns
0
Weekly
0

@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. Passing ssrFallback switches 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(), and unmountIslands(...) — 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), and parseFrontmatter(raw) — content collection helpers exported from zfb/content. parseFrontmatter is 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's htmlOverrides convention. h1 is 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 a components prop 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), plus PaginatedPage<T> / PaginateRoute<T> — exported from zfb/paginate.

  • defineConfig(config) — exported from zfb/config for the zfb.config.ts form (the recommended way to author a zfb project's configuration; the back-compat zfb.config.json form is still supported).

  • definePlugin(plugin) — identity helper exported from zfb/plugins and 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) and SlugAllocator — exported from the root barrel and the zfb/slugify subpath 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:

  1. 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
      },
    });
  2. 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,
        },
      },
    });
  3. Omitted entirely — the parser uses the conservative default. ~~text~~ parses as <del>text</del>, pipe tables render as <table>, and bare URLs like https://example.com become 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.

Keywords