DevToolBoxKOSTENLOS
Blog

Vite Plugin Entwicklung: Benutzerdefinierte Plugins von Scratch 2026

13 Min.von DevToolBox

Vite Plugin-Entwicklung: Benutzerdefinierte Transforms und Dev-Tools

Vites Plugin-API ist eine dĂŒnne Schicht ĂŒber Rollup, erweitert mit Dev-Server-Hooks.

Plugin-Anatomie: Hooks und Lebenszyklus

Ein Vite-Plugin ist ein einfaches Objekt mit einem Namen und Hook-Methoden.

// Minimal Vite plugin structure
// A plugin is just an object with a name and hooks
export default function myPlugin(options = {}) {
  return {
    name: 'vite-plugin-my-plugin',   // required, shown in warnings/errors
    enforce: 'pre',                   // 'pre' | 'post' | undefined

    // Called once when Vite starts
    configResolved(config) {
      console.log('Vite mode:', config.mode);
    },

    // Modify Vite config before it resolves
    config(config, { command }) {
      if (command === 'build') {
        return { build: { sourcemap: true } };
      }
    },

    // Transform file contents
    transform(code, id) {
      if (!id.endsWith('.vue')) return null; // skip non-Vue files
      return { code: transformedCode, map: sourceMap };
    },

    // Resolve module imports to file paths
    resolveId(source, importer) {
      if (source === 'virtual:my-module') {
        return 'virtual:my-module';  // '' prefix = virtual module
      }
    },

    // Load virtual module content
    load(id) {
      if (id === 'virtual:my-module') {
        return 'export const message = "Hello from virtual module!"';
      }
    }
  };
}

transform-Hook: Quelldateien modifizieren

Der transform-Hook ist der am hÀufigsten verwendete Hook.

// Real-world transform: inject build metadata into source files
// Similar to how vite-plugin-inspect or vite-plugin-svgr work
import { readFileSync } from 'fs';
import { createHash } from 'crypto';

export default function buildMetaPlugin() {
  let config;
  const buildTime = new Date().toISOString();

  return {
    name: 'vite-plugin-build-meta',
    enforce: 'post',

    configResolved(resolvedConfig) {
      config = resolvedConfig;
    },

    transform(code, id) {
      // Only process files that import the meta module
      if (!code.includes('__BUILD_META__')) return null;

      const hash = createHash('sha256')
        .update(code)
        .digest('hex')
        .slice(0, 8);

      const meta = JSON.stringify({
        buildTime,
        mode: config.mode,
        hash,
        file: id.replace(config.root, ''),
      });

      const transformed = code.replace(
        /__BUILD_META__/g,
        meta
      );

      return {
        code: transformed,
        // Return null map to inherit original sourcemap
        map: null,
      };
    },
  };
}

// Usage in your app:
// import meta from 'virtual:build-meta';
// console.log(meta.buildTime);

resolveId-Hook: Benutzerdefinierte Modulauflösung

Der resolveId-Hook fÀngt Import-Pfade ab und leitet sie um.

// Advanced resolveId: path aliases + conditional resolution
// Replaces tsconfig paths in runtime (Vite handles build, but not always HMR)
import path from 'path';
import fs from 'fs';

export default function aliasPlugin(aliases = {}) {
  // aliases = { '@': './src', '~utils': './src/utils' }
  const resolvedAliases = Object.fromEntries(
    Object.entries(aliases).map(([key, value]) => [
      key,
      path.resolve(process.cwd(), value),
    ])
  );

  return {
    name: 'vite-plugin-advanced-alias',

    resolveId(source, importer, options) {
      for (const [alias, target] of Object.entries(resolvedAliases)) {
        if (source === alias || source.startsWith(alias + '/')) {
          const resolved = source.replace(alias, target);

          // Try multiple extensions
          for (const ext of ['', '.ts', '.tsx', '.js', '/index.ts']) {
            const candidate = resolved + ext;
            if (fs.existsSync(candidate)) {
              return candidate;
            }
          }
        }
      }

      // Return null to let other resolvers handle it
      return null;
    },
  };
}

// vite.config.ts usage:
// plugins: [aliasPlugin({ '@': './src', '~hooks': './src/hooks' })]

configureServer: Dev-Server-Middleware

Der configureServer-Hook gibt vollen Zugriff auf den Vite-Dev-Server.

// Custom dev server middleware: mock API + WebSocket HMR notifications
export default function devApiPlugin(routes = {}) {
  return {
    name: 'vite-plugin-dev-api',
    apply: 'serve',   // Only active in dev server, not build

    configureServer(server) {
      // Add middleware BEFORE Vite's internal middlewares
      server.middlewares.use('/api', (req, res, next) => {
        const handler = routes[req.url];
        if (!handler) return next();

        res.setHeader('Content-Type', 'application/json');
        res.setHeader('Access-Control-Allow-Origin', '*');

        // Support async handlers
        Promise.resolve(handler(req))
          .then(data => res.end(JSON.stringify(data)))
          .catch(err => {
            res.statusCode = 500;
            res.end(JSON.stringify({ error: err.message }));
          });
      });

      // Hook into Vite's WebSocket for custom HMR events
      server.ws.on('custom:ping', (data, client) => {
        client.send('custom:pong', { timestamp: Date.now() });
      });

      // Send custom event when a file changes
      server.watcher.on('change', (file) => {
        if (file.endsWith('.mock.ts')) {
          server.ws.send({ type: 'custom', event: 'mock-updated', data: { file } });
        }
      });
    },
  };
}

// Usage:
// plugins: [devApiPlugin({
//   '/users': () => [{ id: 1, name: 'Alice' }],
//   '/auth/me': () => ({ id: 1, role: 'admin' }),
// })]

HMR: Hot Module Replacement Integration

Plugins können HMR-Code injizieren und benutzerdefinierte Update-Events verarbeiten.

// Plugin with HMR (Hot Module Replacement) support
// Allows modules to opt-in to custom update logic
export default function statePlugin() {
  return {
    name: 'vite-plugin-state-preserve',

    // Inject HMR handling code into every JS/TS file
    transform(code, id) {
      if (!/.(js|ts|jsx|tsx)$/.test(id)) return null;
      if (id.includes('node_modules')) return null;

      // Append HMR boundary acceptance
      const hmrCode = `
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // Module updated - newModule has the fresh exports
    console.log('[HMR] Module updated:', import.meta.url);
  });

  import.meta.hot.dispose((data) => {
    // Clean up side effects before module is replaced
    // data is passed to the next module version
    data.savedState = window.__APP_STATE__;
  });

  // Receive data from old module instance
  if (import.meta.hot.data.savedState) {
    window.__APP_STATE__ = import.meta.hot.data.savedState;
  }
}
`;

      return { code: code + hmrCode, map: null };
    },

    handleHotUpdate({ file, server, modules }) {
      // Custom logic: invalidate dependent modules when a .schema file changes
      if (file.endsWith('.schema.json')) {
        const affectedModules = server.moduleGraph.getModulesByFile(file);
        return [...(affectedModules || []), ...modules];
      }
    },
  };
}

VollstÀndiges Plugin-Konfigurationsbeispiel

Eine produktionsreife vite.config.ts mit mehreren Plugins.

// Complete vite.config.ts with multiple plugins
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';
import myPlugin from './plugins/my-plugin';

export default defineConfig(({ command, mode }) => ({
  plugins: [
    react(),
    myPlugin({ debug: mode === 'development' }),

    // Bundle analyzer - only in build mode
    command === 'build' && visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
    }),
  ].filter(Boolean),

  resolve: {
    alias: { '@': '/src' },
    extensions: ['.tsx', '.ts', '.jsx', '.js'],
  },

  build: {
    rollupOptions: {
      output: {
        // Manual chunk splitting for better caching
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom'],
          query: ['@tanstack/react-query'],
        },
      },
    },
  },

  server: {
    port: 5173,
    proxy: {
      '/api': { target: 'http://localhost:3000', changeOrigin: true },
    },
    hmr: {
      overlay: true,    // Show errors as overlay
      port: 24678,
    },
  },
}));

Vite Hooks Schnellreferenz

HookWhen CalledTypical UseApplies
configBefore config resolveModify Vite configBoth
configResolvedAfter config resolveRead final configBoth
configureServerBefore server startsAdd middlewareDev only
transformOn each file requestModify source codeBoth
resolveIdOn import resolutionRedirect importsBoth
loadAfter resolveIdSupply file contentBoth
handleHotUpdateOn file change (HMR)Custom HMR logicDev only
buildStartBuild beginsInitialize stateBuild only
generateBundleBundle generatedModify output assetsBuild only

HĂ€ufig gestellte Fragen

Wann sollte ich enforce: "pre" vs "post" verwenden?

Verwenden Sie "pre", wenn Ihr Plugin vor anderen Transforms ausgefĂŒhrt werden muss.

Wie erstelle ich ein virtuelles Modul in Vite?

Verwenden Sie resolveId mit \0-PrĂ€fix und load fĂŒr den Inhalt.

Funktionieren Vite-Plugins in Dev- und Build-Modus?

Ja, verwenden Sie die apply-Eigenschaft zur Steuerung.

Wie debugge ich ein Vite-Plugin?

FĂŒgen Sie DEBUG=vite:* zu Ihrer Umgebung hinzu.

Verwandte Tools

𝕏 Twitterin LinkedIn
War das hilfreich?

Bleiben Sie informiert

Wöchentliche Dev-Tipps und neue Tools.

Kein Spam. Jederzeit abbestellbar.

Verwandte Tools ausprobieren

{ }JSON FormatterB→Base64 Encode Online

Verwandte Artikel

Bun Package Manager: Die Schnellste JavaScript Runtime 2026

VollstÀndiger Bun-Guide 2026: Installation, Workspaces, Scripts und warum es schneller als npm/yarn/pnpm ist.

Monorepo Tools 2026: Turborepo vs Nx vs Lerna vs pnpm Workspaces

VollstĂ€ndiger Vergleich von Monorepo-Tools 2026: Turborepo, Nx, Lerna und pnpm Workspaces fĂŒr die richtige Wahl.

Webpack vs Vite 2026: Welches Build-Tool sollten Sie wÀhlen?

Umfassender Vergleich von Webpack und Vite in 2026: Performance, Ökosystem und Migrationsstrategien.