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.
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
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.
const target = await this.resolve("@/utils", id);
if (target) {
// target.id is the absolute path
}emitFile
emitFile(file: EmittedAsset): string;- Build only. Emit an asset into the output. Returns a reference id you can pass to
getFileNameonce the final name (with hash) is known.
buildStart() {
const ref = this.emitFile({
type: "asset",
name: "generated.json",
source: JSON.stringify({ built: Date.now() }),
});
// later: this.getFileName(ref)
}EmittedAsset:
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
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
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.
transform(code, id) {
this.addWatchFile("/abs/path/to/schema.json");
return null;
}warn
warn(message: string): void;Emit a warning through Vantris's logger, attributed to your plugin's name. Use it for recoverable issues.
error
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.
if (!resolved) this.error(`required module not found: ${source}`);
// after this line, `resolved` is known to be non-nullmeta
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:
transform(code, id) {
if (this.meta.command === "build") {
// safe to emitFile here
}
return null;
}The full interface
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:
| Hook | this 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.