Writing Vite plugins

Vite · Expert · 15 min

Custom plugins solve code generation, integrations and build-time transforms that no config option covers.

A plugin is an object with a name and hook functions, usually returned from a factory so it can take options.

Rollup-compatible hooks (run in dev and build):

  • resolveId(source, importer): map an import specifier to an id. Returning an id prefixed with \0 marks it as virtual so other plugins leave it alone.
  • load(id): return the code for an id, e.g. generated source for a virtual module.
  • transform(code, id): modify a module's code; return { code, map } with a source map when possible. Use hook filters (transform: { filter: { id: /\.md$/ }, handler }) so the native bundler can skip calling into JavaScript for irrelevant files, a big speed win with Rolldown.
  • buildStart, generateBundle and other build hooks.

Vite-specific hooks:

  • config / configResolved: modify or read the final config.
  • configureServer(server): add dev-server middleware (server.middlewares.use) or watch extra files.
  • transformIndexHtml: inject tags into index.html.
  • hotUpdate: customize HMR for certain files.

By convention, virtual module ids start with virtual: in user-facing imports. Name plugins vite-plugin-* (or rolldown-plugin-* if bundler-agnostic). Debug with vite-plugin-inspect, which shows every transform step per module.

Example

// vite.config.js
import { defineConfig } from "vite";

function buildInfo() {
  const id = "virtual:build-info";
  return {
    name: "build-info",
    resolveId(source) {
      if (source === id) return "\0" + id;         // virtual module id
    },
    load(resolved) {
      if (resolved === "\0" + id) {
        return `export default ${JSON.stringify({ builtAt: new Date().toISOString() })};`;
      }
    },
    transformIndexHtml(html) {
      return html.replace("</head>", `<meta name="generator" content="vite"></head>`);
    },
  };
}

export default defineConfig({ plugins: [buildInfo()] });
// in app code:  import info from "virtual:build-info";  info.builtAt

Remember

  • resolveId + load create virtual modules (\0 prefix)
  • transform rewrites code; return source maps
  • Hook filters let Rolldown skip JS calls for irrelevant files
  • Vite-only hooks: configureServer, transformIndexHtml, hotUpdate

Watch out for

  • Transforming every file with a regex check inside the hook instead of a hook filter.

Check yourself

  1. Why do virtual module ids returned from resolveId start with `\0`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: vite.dev/guide/api-plugin