Vite Plugin Basics

Understanding Vite’s plugin system matters because:

  • Your electron-vite project (markdown-task-planner) uses Vite plugins
  • Vitest reuses Vite’s plugin pipeline for test transforms
  • Many Vite contribution opportunities are in the built-in plugins

Plugin Interface

A Vite plugin is an object with named hooks:

import type { Plugin } from "vite";

function myPlugin(): Plugin {
  return {
    name: "my-plugin",

    // Rollup-compatible hooks (run in both dev and build)
    resolveId(source, importer) {
      // Resolve import paths
      if (source === "virtual:my-module") {
        return "\0virtual:my-module"; // \0 prefix = virtual module
      }
    },

    load(id) {
      // Provide module content
      if (id === "\0virtual:my-module") {
        return 'export const value = 42;';
      }
    },

    transform(code, id) {
      // Transform module content
      if (id.endsWith(".custom")) {
        return { code: transformCustomFormat(code), map: null };
      }
    },

    // Vite-specific hooks
    configureServer(server) {
      // Add middleware to dev server
      server.middlewares.use((req, res, next) => {
        if (req.url === "/api/health") {
          res.end("ok");
          return;
        }
        next();
      });
    },

    transformIndexHtml(html) {
      // Transform index.html
      return html.replace("</head>", '<script>/*injected*/</script></head>');
    },
  };
}

Hook Execution Order

Dev Server Request:
  1. resolveId  - "react" -> "/node_modules/react/index.js"
  2. load       - read the file (or provide virtual content)
  3. transform  - JSX -> JS, TS -> JS, etc.
  4. (cached for subsequent requests)

Build:
  1. buildStart
  2. resolveId -> load -> transform (for each module)
  3. renderChunk (modify output chunks)
  4. generateBundle (write files)

Plugin Ordering

// vite.config.ts
export default defineConfig({
  plugins: [
    pluginA(),  // runs first
    pluginB(),  // runs second
  ],
});

Plugins can force ordering with enforce:

{
  name: "must-run-first",
  enforce: "pre",    // before normal plugins
  transform(code) { ... }
}

{
  name: "must-run-last",
  enforce: "post",   // after normal plugins
  transform(code) { ... }
}

Execution order: enforce: 'pre' -> Vite internal plugins -> normal -> enforce: 'post'

Plugins You Already Use

@vitejs/plugin-react (mysukari.com, markdown-task-planner)

import react from "@vitejs/plugin-react";

// What it does:
// 1. transform: JSX/TSX -> React.createElement calls (via Babel or SWC)
// 2. transform: injects React Refresh (HMR for components)
// 3. resolveId: handles React imports

vite-tsconfig-paths (mysukari.com)

import tsconfigPaths from "vite-tsconfig-paths";

// What it does:
// 1. resolveId: reads tsconfig.json paths and resolves aliases
// Example: "@/components/Button" -> "/src/components/Button.tsx"

electron-vite (markdown-task-planner)

electron-vite is a wrapper that runs three Vite configs:

  • Main process (Node.js target)
  • Preload scripts (Node.js target, sandboxed)
  • Renderer (browser target, your React UI)

Each has its own plugin pipeline, which is why your @vitejs/plugin-react only applies to the renderer.

Built-in Plugins (Vite Core)

These run automatically. Understanding them helps with Vite contributions:

PluginFileWhat it does
vite:resolveplugins/resolve.tsModule resolution (bare imports, aliases)
vite:cssplugins/css.tsCSS modules, PostCSS, preprocessors
vite:oxcplugins/oxc.tsTS/JSX transforms via OXC (replaced esbuild in Vite 8)
vite:htmlplugins/html.tsHTML entry processing, asset injection
vite:import-analysisplugins/importAnalysis.tsRewrite bare imports for browser
vite:optimized-depsoptimizer/Pre-bundle node_modules deps

Contribution opportunities like #18061 (fetchpriority attribute) and #20979 (importmap placement) are in plugins/html.ts.

Writing a Minimal Plugin

// Log every transformed module
function logTransforms(): Plugin {
  return {
    name: "log-transforms",
    enforce: "post",
    transform(code, id) {
      if (!id.includes("node_modules")) {
        console.log(`Transformed: ${id} (${code.length} chars)`);
      }
    },
  };
}

Next Steps