1.5 — Plugins do Vite

Entenda o sistema de plugins, conheça os mais úteis e crie seu próprio plugin.

Objetivos da Aula

  • Entender como o sistema de plugins funciona
  • Conhecer os plugins oficiais e populares
  • Criar um plugin customizado simples
  • Integrar múltiplos plugins no projeto

Como Plugins Funcionam

Plugins do Vite são baseados na interface de plugins do Rollup, com extensões específicas para desenvolvimento.

CICLO DE VIDA DO PLUGIN
1 CONFIG Modificar configuracao do Vite
2 CONFIG RESOLVED Configuracao final resolvida
3 CONFIG SERVER Configurar servidor dev (apenas dev)
4 BUILD START Inicio do build
5 RESOLVE ID Resolver imports (ex: '@/foo' → '/src/foo')
6 LOAD Carregar conteudo de modulos virtuais
7 TRANSFORM Transformar codigo fonte
8 BUILD END Fim do build
9 CLOSE BUNDLE Finalizacao

Estrutura Básica de um Plugin

// Um plugin Vite é um objeto com nome e hooks
const meuPlugin = {
  // Nome do plugin (obrigatório)
  name: 'meu-plugin',

  // Hooks do Vite (específicos)
  configureServer(server) {
    // Adiciona middleware ao servidor dev
  },

  // Hooks do Rollup (compartilhados)
  transform(code, id) {
    // Transforma código fonte
    return code
  }
}

// Usar no vite.config.js
export default defineConfig({
  plugins: [meuPlugin]
})

Plugins Oficiais

@vitejs/plugin-legacy

Suporte para navegadores antigos:

npm install @vitejs/plugin-legacy -D
// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime']
    })
  ]
})

@vitejs/plugin-vue

Para projetos Vue:

import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()]
})

@vitejs/plugin-react

Para projetos React:

import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()]
})

@sveltejs/vite-plugin-svelte

Para projetos Svelte (usaremos muito!):

import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [svelte()]
})

Plugins Populares da Comunidade

vite-plugin-compression

Comprime assets para produção:

npm install vite-plugin-compression -D
import compression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    compression({
      algorithm: 'gzip',
      ext: '.gz'
    }),
    compression({
      algorithm: 'brotliCompress',
      ext: '.br'
    })
  ]
})

vite-plugin-pwa

Transforma seu app em PWA:

npm install vite-plugin-pwa -D
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'Meu App',
        short_name: 'App',
        theme_color: '#646cff',
        icons: [
          { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icon-512.png', sizes: '512x512', type: 'image/png' }
        ]
      }
    })
  ]
})

vite-plugin-inspect

Debug de transformações de plugins:

npm install vite-plugin-inspect -D
import Inspect from 'vite-plugin-inspect'

export default defineConfig({
  plugins: [
    Inspect() // Acesse localhost:3000/__inspect/
  ]
})

rollup-plugin-visualizer

Visualiza o bundle:

npm install rollup-plugin-visualizer -D
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    visualizer({
      open: true,
      filename: 'bundle-stats.html',
      gzipSize: true
    })
  ]
})

unplugin-auto-import

Auto-import de funções comuns:

npm install unplugin-auto-import -D
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
      imports: ['vue', 'vue-router'],
      // ou para Svelte
      imports: [
        {
          svelte: ['onMount', 'onDestroy', 'createEventDispatcher']
        }
      ]
    })
  ]
})

Criando Plugins Customizados

Plugin Simples: Log de Builds

// plugins/build-logger.js
export function buildLogger() {
  let startTime

  return {
    name: 'build-logger',

    // Início do build
    buildStart() {
      startTime = Date.now()
      console.log('\n🚀 Build iniciado...\n')
    },

    // Fim do build
    closeBundle() {
      const duration = Date.now() - startTime
      console.log(`\n✅ Build completo em ${duration}ms\n`)
    }
  }
}
// vite.config.js
import { buildLogger } from './plugins/build-logger.js'

export default defineConfig({
  plugins: [buildLogger()]
})

Plugin: Transformar Código

// plugins/banner.js
export function banner(text) {
  return {
    name: 'banner',

    // Transforma arquivos JS
    transform(code, id) {
      // Só processa arquivos JS/TS
      if (!id.match(/.[jt]sx?$/)) return

      // Adiciona comentário no topo
      const bannerComment = `/**\n * ${text}\n * Gerado em: ${new Date().toISOString()}\n */\n`

      return {
        code: bannerComment + code,
        map: null // Sem sourcemap para esta transformação
      }
    }
  }
}

Plugin: Módulo Virtual

Módulos virtuais existem apenas em memória:

// plugins/virtual-config.js
export function virtualConfig(config) {
  const virtualModuleId = 'virtual:config'
  const resolvedVirtualModuleId = '' + virtualModuleId

  return {
    name: 'virtual-config',

    // Resolve o import
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },

    // Carrega o conteúdo
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return `export default ${JSON.stringify(config)}`
      }
    }
  }
}

Uso:

// vite.config.js
import { virtualConfig } from './plugins/virtual-config.js'

export default defineConfig({
  plugins: [
    virtualConfig({
      appName: 'Meu Dashboard',
      version: '1.0.0',
      features: ['dark-mode', 'notifications']
    })
  ]
})
// No seu código
import config from 'virtual:config'

console.log(config.appName) // "Meu Dashboard"
console.log(config.features) // ["dark-mode", "notifications"]

Plugin: Middleware do Servidor Dev

// plugins/api-mock.js
export function apiMock() {
  return {
    name: 'api-mock',

    configureServer(server) {
      // Adiciona middleware ANTES do Vite
      server.middlewares.use((req, res, next) => {
        // Mock de API
        if (req.url === '/api/status') {
          res.setHeader('Content-Type', 'application/json')
          res.end(JSON.stringify({
            status: 'ok',
            timestamp: Date.now(),
            env: 'development'
          }))
          return
        }

        if (req.url === '/api/users') {
          res.setHeader('Content-Type', 'application/json')
          res.end(JSON.stringify([
            { id: 1, name: 'Alice' },
            { id: 2, name: 'Bob' }
          ]))
          return
        }

        // Passa para o próximo middleware
        next()
      })
    }
  }
}

Plugin Completo: Build Info

Vamos criar um plugin útil que injeta informações de build:

// plugins/build-info.js
import { execSync } from 'child_process'

export function buildInfo() {
  const virtualModuleId = 'virtual:build-info'
  const resolvedVirtualModuleId = '' + virtualModuleId

  return {
    name: 'build-info',

    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },

    load(id) {
      if (id === resolvedVirtualModuleId) {
        // Coleta informações
        let gitCommit = 'unknown'
        let gitBranch = 'unknown'

        try {
          gitCommit = execSync('git rev-parse --short HEAD')
            .toString().trim()
          gitBranch = execSync('git rev-parse --abbrev-ref HEAD')
            .toString().trim()
        } catch (e) {
          // Não está em um repositório git
        }

        const info = {
          buildTime: new Date().toISOString(),
          nodeVersion: process.version,
          gitCommit,
          gitBranch,
          env: process.env.NODE_ENV || 'development'
        }

        return `export default ${JSON.stringify(info, null, 2)}`
      }
    }
  }
}

Uso no código:

import buildInfo from 'virtual:build-info'

console.log(`
  Build: ${buildInfo.buildTime}
  Commit: ${buildInfo.gitCommit}
  Branch: ${buildInfo.gitBranch}
  Node: ${buildInfo.nodeVersion}
`)

🎯 Mini-Projeto: Plugin de Métricas

Vamos criar um plugin customizado para nosso Dashboard:

Plugin: Métricas de Build

// src/plugins/metrics-plugin.js

export function metricsPlugin() {
  let buildStartTime
  const moduleCount = { js: 0, css: 0, other: 0 }

  return {
    name: 'metrics-plugin',

    // Início do build
    buildStart() {
      buildStartTime = Date.now()
      moduleCount.js = 0
      moduleCount.css = 0
      moduleCount.other = 0
    },

    // Conta módulos transformados
    transform(code, id) {
      if (id.includes('node_modules')) return

      if (id.match(/.[jt]sx?$/)) {
        moduleCount.js++
      } else if (id.match(/.css$/)) {
        moduleCount.css++
      } else {
        moduleCount.other++
      }

      return null // Não modifica o código
    },

    // Relatório final
    closeBundle() {
      const buildTime = Date.now() - buildStartTime

      console.log('\n' + '═'.repeat(50))
      console.log('📊 MÉTRICAS DE BUILD')
      console.log('═'.repeat(50))
      console.log(`⏱️  Tempo total: ${buildTime}ms`)
      console.log(`📄 Módulos JS/TS: ${moduleCount.js}`)
      console.log(`🎨 Módulos CSS: ${moduleCount.css}`)
      console.log(`📦 Outros: ${moduleCount.other}`)
      console.log(`📁 Total: ${moduleCount.js + moduleCount.css + moduleCount.other}`)
      console.log('═'.repeat(50) + '\n')
    }
  }
}

Plugin: Módulo Virtual de Métricas Dev

// src/plugins/dev-metrics.js

export function devMetricsPlugin() {
  const virtualId = 'virtual:dev-metrics'
  const resolvedId = '' + virtualId

  let requestCount = 0
  let transformCount = 0
  const transformedFiles = []

  return {
    name: 'dev-metrics',

    // Só em desenvolvimento
    apply: 'serve',

    configureServer(server) {
      // Conta requisições
      server.middlewares.use((req, res, next) => {
        requestCount++
        next()
      })

      // Endpoint de métricas
      server.middlewares.use((req, res, next) => {
        if (req.url === '/__metrics') {
          res.setHeader('Content-Type', 'application/json')
          res.end(JSON.stringify({
            requests: requestCount,
            transforms: transformCount,
            files: transformedFiles.slice(-20) // Últimos 20
          }))
          return
        }
        next()
      })
    },

    transform(code, id) {
      if (!id.includes('node_modules')) {
        transformCount++
        transformedFiles.push({
          file: id.split('/').pop(),
          time: new Date().toISOString()
        })
      }
      return null
    },

    resolveId(id) {
      if (id === virtualId) return resolvedId
    },

    load(id) {
      if (id === resolvedId) {
        return `
          export async function getDevMetrics() {
            const res = await fetch('/__metrics')
            return res.json()
          }
        `
      }
    }
  }
}

Integrar no vite.config.js

// vite.config.js
import { defineConfig } from 'vite'
import path from 'path'
import { metricsPlugin } from './src/plugins/metrics-plugin.js'
import { devMetricsPlugin } from './src/plugins/dev-metrics.js'

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@utils': path.resolve(__dirname, './src/utils'),
    }
  },

  plugins: [
    metricsPlugin(),
    devMetricsPlugin()
  ],

  server: {
    port: 3000,
    open: true
  }
})

Usar as Métricas no Dashboard

// src/main.js
import { getDevMetrics } from 'virtual:dev-metrics'

// Adicionar card de métricas do servidor
async function exibirMetricasServidor() {
  const metricas = await getDevMetrics()

  const container = document.querySelector('#metricas-servidor')
  if (container) {
    container.innerHTML = `
      <div class="performance-card">
        <h3 class="card-title">Requisições</h3>
        <p class="card-value">${metricas.requests}</p>
      </div>
      <div class="performance-card">
        <h3 class="card-title">Transformações</h3>
        <p class="card-value">${metricas.transforms}</p>
      </div>
    `
  }
}

// Atualiza a cada 2 segundos
setInterval(exibirMetricasServidor, 2000)

✅ Desafio da Aula

Objetivo

Criar um plugin que adiciona um watermark nos arquivos JS em produção.

Instruções

  1. Crie um plugin chamado watermarkPlugin
  2. O plugin deve adicionar um comentário no topo de cada arquivo JS
  3. O comentário deve incluir: nome do projeto, versão e data de build
  4. O plugin só deve funcionar em build de produção (apply: 'build')

Spec de Verificação

  • Rode npm run build
  • Abra qualquer arquivo .js em dist/
  • O arquivo deve começar com o comentário de watermark

Solução

🔍 Clique para ver a solução
// plugins/watermark.js
export function watermarkPlugin(options = {}) {
  const {
    projectName = 'Meu Projeto',
    version = '1.0.0'
  } = options

  return {
    name: 'watermark',

    // Só em build
    apply: 'build',

    // Gera o banner antes de emitir
    generateBundle(_, bundle) {
      const watermark = `/**
 * ${projectName} v${version}
 * Build: ${new Date().toISOString()}
 * Este arquivo foi gerado automaticamente.
 */
`
      // Adiciona watermark em cada chunk JS
      for (const fileName in bundle) {
        const chunk = bundle[fileName]
        if (chunk.type === 'chunk' && fileName.endsWith('.js')) {
          chunk.code = watermark + chunk.code
        }
      }
    }
  }
}
// vite.config.js
import { watermarkPlugin } from './plugins/watermark.js'
import pkg from './package.json'

export default defineConfig({
  plugins: [
    watermarkPlugin({
      projectName: 'Dashboard Vite',
      version: pkg.version
    })
  ]
})

Próxima aula: 1.6 — Variáveis de Ambiente e Modos