Plugins & virtual modules

Extend the build with a nowaki.config.mjs. Plugins run in a Node host during dev and build (never in the production request path) and stay out of the way when unused.

The config

// nowaki.config.mjs
export default {
  plugins: [
    {
      name: "my-plugin",
      transform(code, id) { /* return new code or null */ },
      resolveId(source, importer) { /* claim a specifier or return null */ },
      load(id) { /* return source for a virtual id or null */ },
    },
  ],
};

Transform hook

transform(code, id) runs before oxc on every transformable source. Return new code, or null to leave it unchanged (the fast path).

transform(code, id) {
  if (!code.includes("__BUILD_DATE__")) return null;
  return code.replaceAll("__BUILD_DATE__", JSON.stringify(new Date().toISOString()));
}

Virtual modules v0.9

Provide modules that don't exist on disk. resolveId claims a bare specifier;load returns its source. Useful for generated config, build info, or a routes manifest.

{
  name: "build-info",
  resolveId(source) {
    return source === "virtual:build-info" ? source : null;
  },
  load(id) {
    if (id !== "virtual:build-info") return null;
    return `export const builtAt = ${Date.now()};`;
  },
}
// any island
import { builtAt } from "virtual:build-info";

On the client the generated source is bundled inline into the island chunk; for SSR it's inlined as a self-contained data: module. Resolution only calls into the plugin when the normal resolver fails, so there's no overhead for ordinary imports. (For SSR, keep virtual modules self-contained — they shouldn't have relative imports.)

The .tsrx bridge

If @tsrx/preact is installed, .tsrx files are compiled to standard JSX before joining the oxc pipeline — an optional, app-level dependency.

Plugins execute as Node code during dev/build with your project's privileges — treat them like any build dependency. They do not run in production serving.