zfb
GitHub repository

Type to search...

to open search from anywhere

definePlugin

Identity helper that types a plugin module's default export as a ZfbPlugin, surfacing hook signatures and field-level typos at compile time.

Signature

definePlugin(plugin: ZfbPlugin): ZfbPlugin

definePlugin is exported from @takazudo/zfb/plugins. Like defineConfig, it is an identity helper: it returns its argument unchanged. Its only job is to type the object literal as ZfbPlugin so an editor surfaces field-level types and hook-signature typos at compile time.

// plugins/my-plugin.ts
import { definePlugin } from "@takazudo/zfb/plugins";

export default definePlugin({
  name: "my-plugin",
  async preBuild({ outDir, logger }) {
    logger.info(`generating index into ${outDir}`);
  },
});

A plugin module's default export must be the object returned by definePlugin (or an equivalent ZfbPlugin-shaped literal) — zfb.config.ts references the module by name, and the Rust-side plugin host loads it via dynamic import().

The ZfbPlugin shape

interface ZfbPlugin {
  name: string;
  setup?(ctx: ZfbSetupContext): Promise<void> | void;
  preBuild?(ctx: ZfbBuildHookContext): Promise<void> | void;
  postBuild?(ctx: ZfbBuildHookContext): Promise<void> | void;
  devMiddleware?(ctx: ZfbDevMiddlewareContext): Promise<void> | void;
  previewMiddleware?(ctx: ZfbPreviewMiddlewareContext): Promise<void> | void;
}

name is informational — the resolved module specifier, not name, is what zfb uses to identify the plugin internally; name only surfaces in log/error lines. All five hooks are optional and independent; a plugin declares any subset. Declaration order in zfb.config.ts's plugins array matters when multiple plugins touch the same surface.

See Plugins for the full lifecycle narrative — hook ordering, setup's four registration methods, the devMiddleware/previewMiddleware request/response contract, and worked examples.

Hook signatures

  • setup?(ctx: ZfbSetupContext) — runs once per host boot (zfb build, zfb dev, and zfb preview), before preBuild. ctx exposes command ("build" | "dev" | "preview"), projectRoot, config, options, logger, and four registration methods: addAlias, addVirtualModule, injectRoute, addClientEntry. addVirtualModule(specifier, loader, options?) takes an optional third argument, ZfbVirtualModuleOptions, whose watchFiles?: string[] names absolute paths zfb dev watches on the loader's behalf — a change to one re-invokes that loader with its memoised source bypassed. Every watchFiles entry must be an absolute path (relative and empty entries throw) and must name a file, not a directory — a path that already exists as a directory throws during setup, since zfb watches only the file's parent directory and would never observe anything inside it. A path that does not exist yet is valid; the directory check runs only once the path exists. Registrations made under "preview" are accepted for shape-consistency but are inert — preview never re-enters the scan → bundle → render pipeline those registries feed. injectRoute(pattern, entrypoint, opts?) patterns follow the pages/ filename grammar — a pattern deriving a _-prefixed path segment (e.g. /_engine/tokens) is private by convention, so zfb build warns and materialises no page for it, though zfb dev still matches the pattern directly; see injectRoute patterns must not have a _-prefixed segment.

  • preBuild?(ctx: ZfbBuildHookContext) — file-generation work before the bundler/renderer/CSS/islands passes. Runs once per zfb build and once per zfb dev boot. Does not fire under zfb preview.

  • postBuild?(ctx: ZfbBuildHookContext) — finalisation after dist/ has been fully written. ctx.routes — the complete route manifest — is present only here (undefined on preBuild). Does not fire under zfb preview.

  • devMiddleware?(ctx: ZfbDevMiddlewareContext) — registers per-request HTTP handlers for zfb dev via ctx.register(path, handler). Fires only during zfb dev.

  • previewMiddleware?(ctx: ZfbPreviewMiddlewareContext) — the zfb preview (static mode) counterpart of devMiddleware; same registration call and request/response shape, but a separate per-mode opt-in — a plugin registering only devMiddleware gets no handlers under zfb preview.

Both preBuild/postBuild receive { projectRoot, scratchDir, outDir, config, options, logger } (plus routes on postBuild); devMiddleware/previewMiddleware receive { projectRoot, scratchDir, config, options, logger, register }; setup receives { command, projectRoot, scratchDir, config, options, logger, addAlias, addVirtualModule, injectRoute, addClientEntry }. options on every context is the plugin's own PluginConfig.options block, copied verbatim.

ZfbBuildHookContext, ZfbDevMiddlewareContext, ZfbPreviewMiddlewareContext, ZfbSetupContext, ZfbPluginLogger, ZfbRouteEntry, ZfbRouteManifest, ZfbVirtualModuleLoader, and ZfbVirtualModuleOptions are all exported from @takazudo/zfb/plugins alongside ZfbPlugin and definePlugin.

scratchDir is an always-present absolute path for a plugin's opaque intermediates, on all four context types. It is <projectRoot>/.zfb-build/plugins by default and <scratch dir>/plugins under --scratch-dir / ZFB_SCRATCH_DIR. It is not an importable route location, and route modules generated there are not staged into the bundle. zfb never creates it: call mkdir(scratchDir, { recursive: true }) and use a <scratchDir>/<plugin-name>/ subdirectory. Plugin isolation under --scratch-dir only holds if the plugin adopts this directory; a plugin that keeps writing to its own project-root directory (for example .zudo-doc/) is still shared between concurrent commands. See Scratch space for intermediates for an example.

See also

  • Plugins — the full plugin hook contract, worked examples, and conflict-detection errors (AliasConflict, VirtualModuleConflict, InjectRouteConflict, ClientEntryConflict, InvalidClientEntry).

  • defineConfig — wiring plugins: [{ name: "...", options: {...} }] in zfb.config.ts.

  • definePreset — stamping preset-contributed plugins with provenance so relative-path plugin specifiers resolve against the preset package.

Revision History

CreatedUpdated