Padrões Vite
Padrões de ferramenta de build e servidor de desenvolvimento para projetos Vite 5+. Abrange configuração, variáveis de ambiente, proxy, modo biblioteca, pré-agrupamento de dependências e armadilhas comuns de produção.
Quando Usar
- Configurando
vite.config.tsouvite.config.js - Configurando variáveis de ambiente ou arquivos
.env - Configurando proxy do servidor de desenvolvimento para backends de API
- Otimizando a saída do build (chunks, minificação, assets)
- Publicando bibliotecas com
build.lib - Resolvendo problemas de pré-agrupamento de dependências ou interoperabilidade CJS/ESM
- Depurando erros de HMR, servidor de desenvolvimento ou build
- Escolhendo ou ordenando plugins Vite
Como Funciona
- Modo dev serve arquivos fonte como ESM nativo — sem bundling. As transformações acontecem sob demanda por requisição de módulo, razão pela qual as inicializações a frio são rápidas e o HMR é preciso.
- Modo build usa o Rolldown (v7+) ou Rollup (v5–v6) para agrupar o aplicativo para produção com tree-shaking, divisão de código e minificação baseada em Oxc.
- Pré-agrupamento de dependências converte dependências CJS/UMD para ESM uma vez via esbuild e armazena o resultado em cache sob
node_modules/.vite, para que inicializações subsequentes pulem o trabalho. - Plugins compartilham uma interface unificada entre dev e build — o mesmo objeto de plugin funciona tanto para as transformações sob demanda do servidor de desenvolvimento quanto para o pipeline de produção.
- Variáveis de ambiente são inlineadas estaticamente no momento do build. Variáveis prefixadas com
VITE_tornam-se constantes públicas no bundle; tudo sem prefixo é invisível para o código cliente.
Exemplos
Estrutura de Configuração
Configuração Básica
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': new URL('./src', import.meta.url).pathname },
},
})
Configuração Condicional
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd()) // apenas prefixo VITE_ (seguro)
return {
plugins: [react()],
server: command === 'serve' ? { port: 3000 } : undefined,
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
Opções de Configuração Principais
| Chave | Padrão | Descrição |
|---|---|---|
root | '.' | Raiz do projeto (onde index.html reside) |
base | '/' | Caminho base público para assets implantados |
envPrefix | 'VITE_' | Prefixo para variáveis de ambiente expostas ao cliente |
build.outDir | 'dist' | Diretório de saída |
build.minify | 'oxc' | Minificador ('oxc', 'terser' ou false) |
build.sourcemap | false | true, 'inline' ou 'hidden' |
Plugins
Plugins Essenciais
A maioria das necessidades de plugins é coberta por um punhado de pacotes bem mantidos. Recorra a estes antes de escrever os seus próprios.
| Plugin | Propósito | Quando usar |
|---|---|---|
@vitejs/plugin-react-swc | React HMR + Fast Refresh via SWC | Padrão para aplicativos React (mais rápido que a variante Babel) |
@vitejs/plugin-react | React HMR + Fast Refresh via Babel | Apenas se precisar de plugins Babel (emotion, decoradores MobX) |
@vitejs/plugin-vue | Suporte a Vue 3 SFC | Aplicativos Vue |
vite-plugin-checker | Executa tsc + ESLint em thread worker com overlay HMR | Qualquer aplicativo TypeScript — Vite NÃO faz verificação de tipos durante vite build |
vite-tsconfig-paths | Honra aliases de paths do tsconfig.json | Sempre que você já tiver aliases em tsconfig.json |
vite-plugin-dts | Emite arquivos .d.ts no modo biblioteca | Publicando bibliotecas TypeScript |
vite-plugin-svgr | Importa SVGs como componentes React | Aplicativos React usando SVGs como componentes |
rollup-plugin-visualizer | Relatório de treemap/sunburst do bundle | Auditorias periódicas de tamanho do bundle (use enforce: 'post') |
vite-plugin-pwa | PWA com configuração zero + Workbox | Aplicativos com capacidade offline |
Chamada crítica: vite build transpila, mas NÃO verifica tipos. Erros de tipo chegam silenciosamente à produção a menos que você adicione vite-plugin-checker ou execute tsc --noEmit no CI.
Autorizando Plugins Customizados
Autorizar é raro — a maioria das necessidades é coberta por plugins existentes. Quando precisar de um, comece inline em vite.config.ts e extraia apenas se for reutilizado.
// vite.config.ts — plugin inline mínimo
function myPlugin(): Plugin {
return {
name: 'my-plugin', // obrigatório, deve ser único
enforce: 'pre', // 'pre' | 'post' (opcional)
apply: 'build', // 'build' | 'serve' (opcional)
transform(code, id) {
if (!id.endsWith('.custom')) return
return { code: transformCustom(code), map: null }
},
}
}
Hooks principais: transform (modificar fonte), resolveId + load (módulos virtuais), transformIndexHtml (injetar no HTML), configureServer (adicionar middleware de desenvolvimento), hotUpdate (HMR personalizado — substitui o obsoleto handleHotUpdate na v7+).
Módulos virtuais usam a convenção de prefixo \0 — resolveId retorna '\0virtual:my-id' para que outros plugins o ignorem. O código do usuário importa 'virtual:my-id'.
Para a API completa de plugins, veja vite.dev/guide/api-plugin. Use vite-plugin-inspect durante o desenvolvimento para depurar o pipeline de transformação.
API HMR
Plugins de framework (@vitejs/plugin-react, @vitejs/plugin-vue, etc.) lidam com HMR automaticamente. Recorra a import.meta.hot diretamente apenas ao construir stores de estado personalizadas, ferramentas de desenvolvimento ou utilitários agnósticos de framework que precisam persistir estado entre atualizações.
// src/store.ts — HMR manual para um módulo vanilla
if (import.meta.hot) {
// Persistir estado entre atualizações (deve MUTAR, nunca reatribuir .data)
import.meta.hot.data.count = import.meta.hot.data.count ?? 0
// Limpar efeitos colaterais antes do módulo ser substituído
import.meta.hot.dispose((data) => clearInterval(data.intervalId))
// Aceitar atualizações deste próprio módulo
import.meta.hot.accept()
}
Todo código import.meta.hot é tree-shaken nos builds de produção — nenhuma remoção de guarda é necessária.
Variáveis de Ambiente
Vite carrega .env, .env.local, .env.[mode] e .env.[mode].local nessa ordem (posterior substitui anterior); arquivos *.local são gitignorados e destinados a segredos locais.
Acesso do Lado do Cliente
Apenas variáveis prefixadas com VITE_ são expostas ao código cliente:
import.meta.env.VITE_API_URL // string
import.meta.env.MODE // 'development' | 'production' | custom
import.meta.env.BASE_URL // valor da configuração base
import.meta.env.DEV // booleano
import.meta.env.PROD // booleano
import.meta.env.SSR // booleano
Usando Env na Configuração
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd()) // apenas prefixo VITE_ (seguro)
return {
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
Segurança
Prefixo VITE_ NÃO é um Limite de Segurança
Qualquer variável prefixada com VITE_ é inlineada estaticamente no bundle do cliente no momento do build. Minificação, codificação base64 e desabilitação de source maps NÃO a ocultam. Um atacante determinado pode extrair qualquer variável VITE_ do JavaScript enviado.
Regra: Apenas valores públicos (URLs de API, flags de funcionalidade, chaves públicas) vão em variáveis VITE_. Segredos (tokens de API, URLs de banco de dados, chaves privadas) DEVEM residir no lado do servidor por trás de uma API ou função serverless.
A Armadilha loadEnv('')
// RUIM: passar '' como terceiro argumento carrega TODAS as variáveis de ambiente — incluindo segredos do servidor —
// e as disponibiliza para inline no código cliente via `define`.
const env = loadEnv(mode, process.cwd(), '')
// BOM: lista explícita de prefixos
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
Source Maps em Produção
Source maps de produção vazam seu código fonte original. Desabilite-os a menos que envie para um rastreador de erros (Sentry, Bugsnag) e os exclua localmente depois:
build: {
sourcemap: false, // padrão — mantenha assim
}
Checklist .gitignore
.env.local,.env.*.local— substituições de segredos locaisdist/— saída do buildnode_modules/.vite— cache de pré-agrupamento (entradas obsoletas causam erros fantasmas)
Proxy do Servidor
// vite.config.ts — server.proxy
server: {
proxy: {
'/foo': 'http://localhost:4567', // string abreviada
'/api': {
target: 'http://localhost:8080',
changeOrigin: true, // necessário para backends com host virtual
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}
Para proxy WebSocket, adicione ws: true à configuração da rota.
Otimização de Build
Chunks Manuais
// vite.config.ts — build.rolldownOptions
build: {
rolldownOptions: {
output: {
// Forma de objeto: agrupar pacotes específicos
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
},
},
},
}
// Forma de função: dividir por heurística
manualChunks(id) {
if (id.includes('node_modules/react')) return 'react-vendor'
if (id.includes('node_modules')) return 'vendor'
}
Performance
Evite Arquivos Barrel
Arquivos barrel (index.ts reexportando tudo de um diretório) forçam o Vite a carregar cada arquivo reexportado mesmo quando você importa um único símbolo. Esta é a #1 lentidão do servidor de desenvolvimento apontada pela documentação oficial.
// RUIM — importar um util força o Vite a carregar todo o barrel
import { slash } from '@/utils'
// BOM — importação direta, apenas o arquivo é carregado
import { slash } from '@/utils/slash'
Seja Explícito com Extensões de Importação
Cada extensão implícita força até 6 verificações de sistema de arquivos via resolve.extensions. Em codebases grandes, isso se acumula.
// RUIM
import Component from './Component'
// BOM
import Component from './Component.tsx'
Reduza tsconfig.json allowImportingTsExtensions + resolve.extensions para apenas as extensões que você realmente usa.
Pré-Aqueça Rotas de Caminho Quente
server.warmup.clientFiles pré-transforma entradas quentes conhecidas antes do navegador solicitá-las — eliminando a cascata de requisições de carga a frio em aplicativos grandes.
// vite.config.ts
server: {
warmup: {
clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
},
}
Perfilando Servidores de Desenvolvimento Lentos
Quando vite dev parece lento, comece com vite --profile, interaja com o aplicativo, depois pressione p+enter para salvar um .cpuprofile. Carregue no Speedscope para descobrir quais plugins estão consumindo tempo — geralmente hooks buildStart, config ou configResolved em plugins da comunidade.
Modo Biblioteca
Ao publicar um pacote npm, use build.lib. Duas armadilhas são mais importantes que detalhes de configuração:
- Tipos não são emitidos — adicione
vite-plugin-dtsou executetsc --emitDeclarationOnlyseparadamente. - Dependências pares DEVEM ser externalizadas — pares não listados são agrupados em sua biblioteca, causando erros de runtime duplicado em consumidores.
// vite.config.ts
build: {
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => `my-lib.${format}.js`,
},
rolldownOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'], // toda dependência par
},
}
Externalizações SSR
Configurações simples de createServer({ middlewareMode: true }) são território de autores de framework. A maioria dos aplicativos deve usar Nuxt, Remix, SvelteKit, Astro ou TanStack Start. O que você ajustará como usuário de framework é a configuração de externalizações quando dependências quebram em SSR:
// vite.config.ts — opções ssr
ssr: {
external: ['node-native-package'], // manter como require() no bundle SSR
noExternal: ['esm-only-package'], // forçar agrupamento na saída SSR (corrige a maioria dos erros SSR)
target: 'node', // 'node' ou 'webworker'
}
Pré-Agrupamento de Dependências
Vite pré-agrupa dependências para converter CJS/UMD para ESM e reduzir o número de requisições.
// vite.config.ts — optimizeDeps
optimizeDeps: {
include: [
'lodash-es', // forçar pré-agrupamento de dependências pesadas conhecidas
'cjs-package', // dependências CJS que causam problemas de interoperabilidade
'deep-lib/components/**', // glob para importações profundas
],
exclude: ['local-esm-package'], // deve ser ESM válido se excluído
force: true, // ignorar cache, re-otimizar (depuração temporária)
}
Armadilhas Comuns
Dev Não Corresponde ao Build
Dev usa esbuild/Rolldown para transformações; build usa Rolldown para bundling. Bibliotecas CJS podem se comportar de forma diferente entre os dois. Sempre verifique com vite build && vite preview antes de implantar.
Chunks Obsoletos Após Implantação
Novos builds produzem novos hashes de chunk. Usuários com sessões ativas solicitam nomes de arquivos antigos que não existem mais. Vite não tem solução embutida. Mitigações:
- Manter arquivos antigos de
dist/assets/ativos por uma janela de implantação - Capturar erros de importação dinâmica no seu roteador e forçar um recarregamento de página
Docker e Contêineres
Vite vincula a localhost por padrão, que é inacessível de fora de um contêiner:
// vite.config.ts — configuração Docker/contêiner
server: {
host: true, // vincular 0.0.0.0
hmr: { clientPort: 3000 }, // se por trás de um proxy reverso
}
Acesso a Arquivos em Monorepo
Vite restringe o serviço de arquivos à raiz do projeto. Pacotes fora da raiz são bloqueados:
// vite.config.ts — acesso a arquivos em monorepo
server: {
fs: {
allow: ['..'], // permitir diretório pai (raiz do workspace)
},
}
Anti-Padrões
// RUIM: Definir envPrefix como '' expõe TODAS as variáveis de ambiente (incluindo segredos) ao cliente
envPrefix: ''
// RUIM: Assumir que require() funciona em módulos ESM — Vite usa ESM nativo no modo dev
// Use importações estáticas com import, ou dynamic imports com await import()
// RUIM: Modificar o objeto options retornado após o retorno da função de configuração
// Pode causar comportamento imprevisível. Sempre retorne um novo objeto.
// RUIM: Usar import.meta.env diretamente em código servidor (SSR node) sem verificar guardas
// No SSR node, import.meta.env.BASE_URL é resolvido, mas a verificação de guarda é recomendada.