Every Vite plugin is a factory function returning a plugin object:
ts
import type { Plugin } from 'vite'
export interface MyPluginOptions {
include?: string | RegExp | (string | RegExp)[]
exclude?: string | RegExp | (string | RegExp)[]
}
export default function myPlugin(options: MyPluginOptions = {}): Plugin {
return {
name: 'vite-plugin-my', // required, unique name
enforce: 'pre', // 'pre' | 'post' | undefined
apply: 'build', // 'build' | 'serve' | ((config, env) => bool)
// lifecycle hooks below...
}
}
HookPhasePurposeconfigBothModify resolved configconfigResolvedBothRead final configbuildStartBothInitialize plugin stateresolveIdBothCustom module resolutionloadBothCustom module contenttransformBothCode transformationgenerateBundleBuildPost-process outputconfigureServerServeExtend dev serverhandleHotUpdateServeCustom HMR logic
ts
transform(code, id) {
if (!id.endsWith('.myext')) return null // return null = skip
const transformed = doTransform(code)
return {
code: transformed,
map: null, // provide sourcemap when possible
}
},
ts
const VIRTUAL_ID = 'virtual:my-module'
const RESOLVED_ID = '\0' + VIRTUAL_ID // prefix \0 to avoid collisions
resolveId(id) {
if (id === VIRTUAL_ID) return RESOLVED_ID
},
load(id) {
if (id === RESOLVED_ID) {
return `export const data = ${JSON.stringify(myData)}`
}
},
ts
handleHotUpdate({ file, server }) {
if (file.endsWith('.myext')) {
server.ws.send({ type: 'full-reload' })
return [] // prevent default HMR
}
},
options.ssr in transform / load hooksmoduleSideEffects: falsevite build --ssrts
// Use vite's createServer for integration tests
import { createServer } from 'vite'
const server = await createServer({ plugins: [myPlugin()] })
const mod = await server.ssrLoadModule('/src/test.myext')
package.json main + module + types fields setpeerDependencies: "vite": "^5.0.0 || ^6.0.0"vite-plugin- prefix in package name (ecosystem convention)