Files
agent-skills/skills/vite/references/core-plugin-api.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

3.8 KiB

name, description
name description
vite-plugin-api Vite plugin authoring with Vite-specific hooks

Vite Plugin API

Vite plugins extend Rolldown's plugin interface with Vite-specific hooks.

Basic Structure

function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    // hooks...
  }
}

Vite-Specific Hooks

config

Modify config before resolution:

const plugin = () => ({
  name: 'add-alias',
  config: () => ({
    resolve: {
      alias: { foo: 'bar' },
    },
  }),
})

configResolved

Access final resolved config:

const plugin = () => {
  let config: ResolvedConfig
  return {
    name: 'read-config',
    configResolved(resolvedConfig) {
      config = resolvedConfig
    },
    transform(code, id) {
      if (config.command === 'serve') { /* dev */ }
    },
  }
}

configureServer

Add custom middleware to dev server:

const plugin = () => ({
  name: 'custom-middleware',
  configureServer(server) {
    server.middlewares.use((req, res, next) => {
      // handle request
      next()
    })
  },
})

Return function to run after internal middlewares:

configureServer(server) {
  return () => {
    server.middlewares.use((req, res, next) => {
      // runs after Vite's middlewares
    })
  }
}

transformIndexHtml

Transform HTML entry files:

const plugin = () => ({
  name: 'html-transform',
  transformIndexHtml(html) {
    return html.replace(/<title>(.*?)<\/title>/, '<title>New Title</title>')
  },
})

Inject tags:

transformIndexHtml() {
  return [
    { tag: 'script', attrs: { src: '/inject.js' }, injectTo: 'body' },
  ]
}

handleHotUpdate

Custom HMR handling:

handleHotUpdate({ server, modules, timestamp }) {
  server.ws.send({ type: 'custom', event: 'special-update', data: {} })
  return [] // empty = skip default HMR
}

Virtual Modules

Serve virtual content without files on disk:

const plugin = () => {
  const virtualModuleId = 'virtual:my-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"`
      }
    },
  }
}

Usage:

import { msg } from 'virtual:my-module'

Convention: prefix user-facing path with virtual:, prefix resolved id with \0.

Plugin Ordering

Use enforce to control execution order:

{
  name: 'pre-plugin',
  enforce: 'pre',  // runs before core plugins
}

{
  name: 'post-plugin',
  enforce: 'post', // runs after build plugins
}

Order: Alias → enforce: 'pre' → Core → User (no enforce) → Build → enforce: 'post' → Post-build

Conditional Application

{
  name: 'build-only',
  apply: 'build',  // or 'serve'
}

// Function form:
{
  apply(config, { command }) {
    return command === 'build' && !config.build.ssr
  }
}

Universal Hooks (from Rolldown)

These work in both dev and build:

  • resolveId(id, importer) - Resolve import paths
  • load(id) - Load module content
  • transform(code, id) - Transform module code
transform(code, id) {
  if (id.endsWith('.custom')) {
    return { code: compile(code), map: null }
  }
}

Client-Server Communication

Server to client:

configureServer(server) {
  server.ws.send('my:event', { msg: 'hello' })
}

Client side:

if (import.meta.hot) {
  import.meta.hot.on('my:event', (data) => {
    console.log(data.msg)
  })
}

Client to server:

// Client
import.meta.hot.send('my:from-client', { msg: 'Hey!' })

// Server
server.ws.on('my:from-client', (data, client) => {
  client.send('my:ack', { msg: 'Got it!' })
})