2.2 — Arquitetura do Vite
Entenda como o Vite funciona internamente: servidor de desenvolvimento, pré-bundling e o papel do esbuild e Rollup.
Objetivos da Aula
- Compreender a arquitetura interna do servidor de desenvolvimento
- Entender o processo de pré-bundling de dependências com esbuild
- Conhecer o fluxo de transformação de arquivos
- Diferenciar o funcionamento em desenvolvimento vs produção
Visão Geral da Arquitetura
O Vite possui duas arquiteturas distintas que trabalham em conjunto:
ARQUITETURA DO VITE
- Servidor ESM
- + esbuild
- + HMR nativo
- Rollup Bundler
- + Plugins
- + Otimizações
O Servidor de Desenvolvimento
Fluxo de uma Requisição
Quando você acessa http://localhost:5173, acontece o seguinte:
FLUXO DE REQUISIÇÃO
<script type="module" src="/src/main.ts">import './app.ts'Código Exemplo: Vendo na Prática
Crie estes arquivos no seu projeto:
// src/main.ts
import { saudacao } from './utils/saudacao'
import { formataData } from './utils/data'
console.log(saudacao('Mundo'))
console.log(formataData(new Date())) // src/utils/saudacao.ts
export function saudacao(nome: string): string {
return `Olá, ${nome}!`
} // src/utils/data.ts
export function formataData(data: Date): string {
return data.toLocaleDateString('pt-BR')
} Abra o DevTools do navegador → Network e observe:
Request 1: /src/main.ts (200 OK)
Request 2: /src/utils/saudacao.ts (200 OK)
Request 3: /src/utils/data.ts (200 OK) Cada arquivo é uma requisição separada! O navegador resolve os imports.
Pré-bundling de Dependências
O Problema com node_modules
Nem todas as dependências funcionam bem com ESModules nativos:
// lodash tem centenas de módulos internos
import { debounce } from 'lodash-es'
// Isso causaria CENTENAS de requisições HTTP!
// algumas libs usam CommonJS internamente
import dayjs from 'dayjs'
// CommonJS não funciona direto no navegador! A Solução: esbuild
O Vite usa o esbuild para pré-compilar dependências no primeiro start:
PRÉ-BUNDLING
Primeiro pnpm dev:
Vendo o Cache
Após rodar pnpm dev, observe a pasta criada:
ls node_modules/.vite/deps/ Você verá arquivos como:
_metadata.json
lodash-es.js
lodash-es.js.map
dayjs.js
dayjs.js.map Reescrita de Imports
O Vite reescreve seus imports automaticamente:
// Seu código (o que você escreve):
import { debounce } from 'lodash-es'
// O que o navegador recebe:
import { debounce } from '/node_modules/.vite/deps/lodash-es.js?v=abc123' Hot Module Replacement (HMR)
Como Funciona
O HMR do Vite usa WebSockets para comunicação instantânea:
HMR FLOW
src/components/Button.sveltepath: '/src/components/Button.svelte',
timestamp: 1699123456789
}] }
- Recebe a mensagem
- Faz
import()dinâmico do módulo atualizado - Substitui o módulo antigo pelo novo
- Estado da aplicação é PRESERVADO!
Exemplo: Observando o HMR
Abra o DevTools → Console e observe as mensagens:
[vite] connecting...
[vite] connected.
# Quando você edita um arquivo:
[vite] hot updated: /src/main.ts API do HMR
Você pode interagir com o HMR programaticamente:
// main.ts
import { contador } from './contador'
// tipagem para o contador
const valor: number = contador
console.log('Contagem:', valor)
// API de HMR do Vite
if (import.meta.hot) {
// Aceita atualizações deste módulo
import.meta.hot.accept()
// Executa quando o módulo é substituído
import.meta.hot.dispose(() => {
console.log('Módulo antigo sendo descartado')
})
} Transformação de Arquivos
Pipeline de Transformação
PIPELINE DE TRANSFORMAÇÃO
Exemplo: TypeScript
// src/utils/math.ts
export function soma(a: number, b: number): number {
return a + b
} O Vite transforma para:
// O que o navegador recebe:
export function soma(a, b) {
return a + b
} A transformação acontece sob demanda, não antecipadamente!
Arquitetura de Produção
No build de produção, o Vite usa Rollup:
BUILD DE PRODUÇÃO
pnpm build
Por que Rollup e não esbuild para produção?
esbuild:
Extremamente rápido
Code splitting ainda não é ideal
Plugins menos flexíveis
Rollup:
Code splitting maduro
Ecossistema de plugins robusto
Otimizações avançadas
Mais lento (mas OK para builds ocasionais) Exemplo Prático: Observando a Arquitetura
1. Veja o pré-bundling acontecendo
# Delete o cache
rm -rf node_modules/.vite
# Rode o dev server e observe o terminal
pnpm dev Você verá:
Optimizing dependencies:
lodash-es, dayjs, svelte
Pre-bundling them to speed up dev server page load... 2. Compare dev vs build
# Em desenvolvimento
pnpm dev
# Abra DevTools → Network
# observe dezenas de arquivos .ts
# Para build
pnpm build
# Observe a pasta dist/
# poucos arquivos otimizados 3. Inspecione o output do build
pnpm build
cat dist/assets/index-*.js | head -20
# Código minificado e otimizado! Mini-Projeto: Continuação
Vamos adicionar monitoramento de HMR ao nosso Dashboard:
Arquivo: src/hmr-monitor.ts
// src/hmr-monitor.ts
// Monitor de atualizações HMR
interface Atualizacao {
path: string
timestamp: string
}
const atualizacoes: Atualizacao[] = []
export function registrarAtualizacao(
path: string
): void {
atualizacoes.push({
path,
timestamp: new Date().toLocaleTimeString('pt-BR')
})
}
export function getAtualizacoes(): Atualizacao[] {
return [...atualizacoes]
}
export function getUltimaAtualizacao(): Atualizacao | null {
return atualizacoes[atualizacoes.length - 1] || null
}
// Configura listener de HMR
if (import.meta.hot) {
// Quando QUALQUER módulo for atualizado
import.meta.hot.on(
'vite:beforeUpdate',
(payload) => {
payload.updates.forEach(
(update: { path: string }) => {
registrarAtualizacao(update.path)
console.log(`HMR: ${update.path}`)
}
)
}
)
} Atualize o main.ts
// main.ts
import './style.css'
import { setupCounter } from './counter'
import {
getAtualizacoes,
getUltimaAtualizacao
} from './hmr-monitor'
const inicioCarregamento: number = performance.now()
function renderApp(): void {
const atualizacoes = getAtualizacoes()
const ultima = getUltimaAtualizacao()
const app = document.querySelector<HTMLDivElement>(
'#app'
)
if (!app) return
app.innerHTML = `
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo"
alt="Vite logo" />
</a>
<h1>Dashboard Vite</h1>
<div class="card">
<button id="counter" type="button"></button>
</div>
<div class="stats">
<p id="tempo-carregamento">
Calculando...
</p>
<p id="hmr-stats">
HMR Updates: ${atualizacoes.length}
${ultima
? `<br>Último: ${ultima.path} às ${ultima.timestamp}`
: ''
}
</p>
</div>
</div>
`
const btn = document.querySelector<HTMLButtonElement>(
'#counter'
)
if (btn) setupCounter(btn)
requestAnimationFrame(() => {
const fimCarregamento: number = performance.now()
const tempoTotal: string = (
fimCarregamento - inicioCarregamento
).toFixed(2)
const el = document.querySelector(
'#tempo-carregamento'
)
if (el) {
el.textContent = `Carregado em ${tempoTotal}ms`
}
})
}
renderApp()
// Aceita HMR e re-renderiza
if (import.meta.hot) {
import.meta.hot.accept(() => {
renderApp()
})
} Teste o HMR
- Rode
pnpm dev - Abra o navegador em
http://localhost:5173 - Edite qualquer arquivo e salve
- Observe o contador de HMR aumentar!
Desafio da Aula
Objetivo
Criar um componente que mostra o tempo de cada atualização HMR.
Instruções
- Modifique
hmr-monitor.tspara também registrar quanto tempo cada HMR levou - Use
performance.now()para medir o tempo entre o início e fim do HMR - Exiba a média de tempo de HMR no dashboard
Dica
// Você pode usar estes eventos do HMR:
import.meta.hot.on(
'vite:beforeUpdate',
() => { /* antes */ }
)
import.meta.hot.on(
'vite:afterUpdate',
() => { /* depois */ }
) Spec de Verificação
- O dashboard mostra quantas atualizações HMR ocorreram
- O dashboard mostra o tempo médio das atualizações
- Ao editar um arquivo, os números atualizam automaticamente
Solução
Clique para ver a solução
// src/hmr-monitor.ts
interface AtualizacaoComTempo {
path: string
timestamp: string
duracao: string
}
const atualizacoes: AtualizacaoComTempo[] = []
let hmrInicio: number | null = null
export function registrarInicio(): void {
hmrInicio = performance.now()
}
export function registrarFim(path: string): void {
if (hmrInicio) {
const duracao: number = performance.now() - hmrInicio
atualizacoes.push({
path,
timestamp: new Date().toLocaleTimeString('pt-BR'),
duracao: duracao.toFixed(2)
})
hmrInicio = null
}
}
export function getAtualizacoes(): AtualizacaoComTempo[] {
return [...atualizacoes]
}
export function getMediaTempo(): string {
if (atualizacoes.length === 0) return '0'
const soma: number = atualizacoes.reduce(
(acc, a) => acc + parseFloat(a.duracao),
0
)
return (soma / atualizacoes.length).toFixed(2)
}
if (import.meta.hot) {
import.meta.hot.on(
'vite:beforeUpdate',
() => {
registrarInicio()
}
)
import.meta.hot.on(
'vite:afterUpdate',
(payload) => {
payload.updates.forEach(
(update: { path: string }) => {
registrarFim(update.path)
console.log(`HMR: ${update.path}`)
}
)
}
)
}Recursos Adicionais
Próxima aula: 2.3 — Criando e Explorando um Projeto Vite