Skip to content

Plugin Context

The pipeline hooks — resolveId, load, transform, buildStart, buildEnd — run with a this plugin context. It's the toolbox a plugin uses to resolve specifiers, emit files, watch inputs, and report problems.

ts
import { definePlugin } from "vantris";

export default definePlugin({
  name: "x",
  async transform(code, id) {
    // `this` is the PluginContext
    const resolved = await this.resolve("./sibling", id);
    if (!resolved) this.warn(`could not resolve ./sibling from ${id}`);
    return null;
  },
});

Use a regular function, not an arrow

this is only bound in a normal method/function. If you write a hook as an arrow function, this won't be the plugin context. Use the method shorthand (as above) or a function.

Dev vs. build

In the build, the context is the bundler's own rich context (a superset of what's documented here). In dev, Vantris provides the same surface — but a few build-only methods (emitFile, getFileName) throw, because there's no output bundle to emit into. Guard those with this.meta.command === "build".

Members

resolve

ts
resolve(source: string, importer?: string): Promise<{ id: string } | null>;

Resolve a specifier the way Vantris/the bundler would — honouring aliases and the resolution rules. Returns the resolved id, or null if it can't be resolved.

ts
const target = await this.resolve("@/utils", id);
if (target) {
  // target.id is the absolute path
}

emitFile

ts
emitFile(file: EmittedAsset): string;
  • Build only. Emit an asset into the output. Returns a reference id you can pass to getFileName once the final name (with hash) is known.
ts
buildStart() {
  const ref = this.emitFile({
    type: "asset",
    name: "generated.json",
    source: JSON.stringify({ built: Date.now() }),
  });
  // later: this.getFileName(ref)
}

EmittedAsset:

ts
interface EmittedAsset {
  type: "asset";
  name?: string;              // preferred base name (hashed into the final name)
  fileName?: string;          // exact output file name (skips hashing)
  source: string | Uint8Array;
}

getFileName

ts
getFileName(referenceId: string): string;
  • Build only. The final output file name of a previously emitted file — after hashing. Use it to reference an emitted asset from generated code or HTML.

addWatchFile

ts
addWatchFile(id: string): void;

Watch an extra file so a change to it triggers a rebuild (build --watch) or a reload (dev). Use it when your plugin's output depends on a file that isn't part of the normal module graph — a config file, a data source, a template.

ts
transform(code, id) {
  this.addWatchFile("/abs/path/to/schema.json");
  return null;
}

warn

ts
warn(message: string): void;

Emit a warning through Vantris's logger, attributed to your plugin's name. Use it for recoverable issues.

error

ts
error(message: string): never;

Abort with an error attributed to your plugin. It never returns — it throws — so it doubles as a type-narrowing guard.

ts
if (!resolved) this.error(`required module not found: ${source}`);
// after this line, `resolved` is known to be non-null

meta

ts
meta: { command: "serve" | "build"; watchMode: boolean };

Tells you whether the current run is a serve (dev) or build, and whether the build is in watch mode. Branch on it to keep build-only calls out of dev:

ts
transform(code, id) {
  if (this.meta.command === "build") {
    // safe to emitFile here
  }
  return null;
}

The full interface

ts
interface PluginContext {
  resolve(source: string, importer?: string): Promise<{ id: string } | null>;
  emitFile(file: EmittedAsset): string;        // build only
  getFileName(referenceId: string): string;    // build only
  addWatchFile(id: string): void;
  warn(message: string): void;
  error(message: string): never;
  meta: { command: "serve" | "build"; watchMode: boolean };
}

Which hooks get the context

Only the pipeline hooks run with this: PluginContext:

Hookthis is PluginContext?
buildStart
resolveId
load
transform
buildEnd
config, configResolved, configureServer, transformIndexHtml, handleHotUpdate❌ (their args carry what they need)

See the Plugin API for every hook's full signature.

Released under the MIT License.