Skip to main content

Command Palette

Search for a command to run...

Vite Plugin API: Extend Vite Build Tool

Updated
3 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Vite Plugin API: Extend Vite Build Tool

Overview

The Vite Plugin API allows developers to extend Vite's build capabilities by hooking into its core processes. Vite plugins are based on Rollup's plugin interface with Vite-specific extensions.

Core Concepts

Plugin Structure

export default function myPlugin(options = {}) {
  return {
    name: 'my-plugin', // required, used for warnings and errors
    apply: 'build', // 'build' | 'serve' | function
    enforce: 'pre', // 'pre' | 'post'

    // Vite-specific hooks
    config(config, env) {},
    configResolved(resolvedConfig) {},
    configureServer(server) {},
    transformIndexHtml(html) {},
    handleHotUpdate(ctx) {},

    // Rollup hooks
    resolveId(id) {},
    load(id) {},
    transform(code, id) {},
  }
}

Key Hooks

Universal Hooks

HookPurposeTiming
configModify Vite configBefore resolution
configResolvedStore final configAfter resolution
applyConditional plugin applicationPlugin initialization
enforceHook execution orderPlugin initialization

Vite-Specific Hooks

configureServer(server)

Customize dev server:

configureServer(server) {
  return () => {
    server.middlewares.use('/custom', (req, res) => {
      res.end('Custom response')
    })
  }
}

transformIndexHtml(html)

Transform index.html:

transformIndexHtml(html) {
  return html.replace(
    /<title>(.*?)<\/title>/,
    `<title>Modified: $1</title>`
  )
}

handleHotUpdate(ctx)

Handle HMR updates:

handleHotUpdate(ctx) {
  if (ctx.file.endsWith('.custom')) {
    console.log(`${ctx.file} changed, full reload`)
    ctx.server.ws.send({
      type: 'full',
      event: 'special',
      event_payload: {},
    })
    return []
  }
}

Rollup Hooks

resolveId(id)

Custom module resolution:

resolveId(id) {
  if (id === 'virtual-module') {
    return id
  }
}

load(id)

Custom module loading:

load(id) {
  if (id === 'virtual-module') {
    return 'export default "virtual content"'
  }
}

transform(code, id)

Transform module code:

transform(code, id) {
  if (id.endsWith('.custom')) {
    return {
      code: transformedCode,
      map: null
    }
  }
}

Practical Examples

Virtual Module Plugin

export default function virtualModulePlugin() {
  const virtualModuleId = 'virtual-module'
  const resolvedId = '\0' + virtualModuleId

  return {
    name: 'virtual-module',
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedId
      }
    },
    load(id) {
      if (id === resolvedId) {
        return `export const msg = "from virtual module"`
      }
    }
  }
}

Framework Integration Plugin

export default function frameworkPlugin() {
  return {
    name: 'framework-plugin',

    resolveId(id) {
      if (id.endsWith('.framework')) {
        return id
      }
    },

    load(id) {
      if (id.endsWith('.framework')) {
        const content = fs.readFileSync(id, 'utf-8')
        return compileFramework(content)
      }
    },

    transform(code, id) {
      if (id.endsWith('.framework')) {
        return {
          code: optimizeCode(code),
          map: null
        }
      }
    }
  }
}

Environment-Specific Plugin

export default function envPlugin() {
  let config

  return {
    name: 'env-plugin',
    apply: 'build',
    enforce: 'pre',

    configResolved(resolvedConfig) {
      config = resolvedConfig
    },

    transform(code, id) {
      if (config.command === 'build') {
        return code.replace(
          /process\.env\.NODE_ENV/g,
          JSON.stringify(config.env.NODE_ENV)
        )
      }
    }
  }
}

Advanced Patterns

Conditional Plugin Application

apply: 'build', // only during build
apply: 'serve', // only during dev
apply: (config, env) => env.command === 'build' && config.build.ssr

Plugin Ordering

{
  enforce: 'pre',  // runs before core plugins
  // ... plugin code
}

{
  enforce: 'post', // runs after core plugins
  // ... plugin code
}

Accessing Resolved Config

configResolved(resolvedConfig) {
  this.config = resolvedConfig
}

Best Practices

  1. Use \0 prefix for virtual modules to avoid conflicts
  2. Store config in configResolved hook for later use
  3. Return null from hooks when not applicable
  4. Use apply to limit plugin scope
  5. Document hook order with enforce
  6. Handle both dev and build modes appropriately
  7. Provide source maps for better debugging

Common Use Cases

  • Custom file format compilation
  • Virtual modules
  • Framework integration
  • Build optimization
  • Development server customization
  • Asset processing
  • Code generation
  • Environment variable injection

The Vite Plugin API provides powerful extensibility while maintaining simplicity and performance.