TypeScript no frontend: Vite e configuração moderna
1. Por que Vite com TypeScript?
O ecossistema frontend enfrentou por anos um problema crônico: a lentidão dos bundlers tradicionais. Webpack, embora extremamente flexível, exigia segundos (ou minutos) para reiniciar o servidor de desenvolvimento após qualquer alteração. Parcel melhorava a experiência, mas ainda sofria com projetos grandes.
Vite surgiu como resposta a esse gargalo. Em vez de empacotar todo o código antes de servir, ele usa ESBuild para transformação rápida de TypeScript durante o desenvolvimento — uma ferramenta escrita em Go que compila arquivos dezenas de vezes mais rápido que o tsc tradicional. Para produção, o Vite delega o empacotamento ao Rollup, garantindo bundles otimizados.
A diferença prática é imediata: enquanto o Webpack recompila o projeto inteiro a cada salvamento, o Vite utiliza Hot Module Replacement (HMR) baseado em módulos ES nativos, atualizando apenas o arquivo modificado. O resultado? Um servidor de desenvolvimento que inicia em milissegundos e responde instantaneamente.
// Exemplo: diferença na velocidade de compilação
// Com tsc tradicional (lento para projetos grandes)
// tsc --noEmit --watch
// Com Vite + ESBuild (rápido, nativo)
// vite dev
2. Configuração inicial de um projeto Vite + TypeScript
Criar um projeto novo é simples:
npm create vite@latest meu-projeto -- --template react-ts
cd meu-projeto
npm install
A estrutura gerada já vem otimizada:
meu-projeto/
├── src/
│ ├── App.tsx
│ ├── main.tsx
│ └── vite-env.d.ts
├── public/
├── types/
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
O diretório types/ não é criado por padrão, mas é uma boa prática adicioná-lo para declarações de tipos globais e interfaces compartilhadas.
3. Tipagem avançada no tsconfig.json para frontend moderno
O tsconfig.json é o coração da configuração TypeScript. Para projetos Vite, recomenda-se:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"lib": ["ES2020", "DOM", "DOM.Iterable"]
}
}
Explicação das opções:
"moduleResolution": "bundler": essencial para Vite, pois permite que o TypeScript entenda imports de arquivos não-JS (CSS, imagens) sem declarar explicitamente cada um."noUncheckedIndexedAccess": true: previne erros ao acessar arrays ou objetos com índices dinâmicos sem verificação."exactOptionalPropertyTypes": true: garante que propriedades opcionais sejam exatamenteundefinedquando ausentes, nãoundefined | T.
Os paths permitem imports absolutos:
// Em vez de import { Button } from '../../components/Button'
import { Button } from '@/components/Button'
4. Integração de TypeScript com Vite: plugins e configurações
Para sincronizar TypeScript e Vite, três plugins são indispensáveis:
a) vite-plugin-checker — Validação de tipos em tempo real:
// vite.config.ts
import { defineConfig } from 'vite'
import checker from 'vite-plugin-checker'
export default defineConfig({
plugins: [
checker({ typescript: true })
]
})
Isso substitui o antigo fork-ts-checker-webpack-plugin, exibindo erros de tipo diretamente no terminal e no navegador.
b) vite-plugin-dts — Geração de arquivos .d.ts para bibliotecas:
import dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
dts({ rollupTypes: true })
]
})
c) Sincronizando resolve.alias com paths do TypeScript:
import { resolve } from 'path'
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})
5. Tipando recursos estáticos e assets no Vite
O arquivo vite-env.d.ts gerado automaticamente contém:
/// <reference types="vite/client" />
Isso adiciona tipos para imports de assets. Mas você pode estender para recursos específicos:
// src/types/assets.d.ts
declare module '*.svg' {
import React from 'react'
const SVGComponent: React.FC<React.SVGProps<SVGSVGElement>>
export default SVGComponent
}
declare module '*.module.css' {
const classes: { readonly [key: string]: string }
export default classes
}
declare module '*.json' {
const value: Record<string, unknown>
export default value
}
Variáveis de ambiente tipadas:
// src/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
// Adicione outras variáveis aqui
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
Com Zod, você pode validar essas variáveis em tempo de execução:
import { z } from 'zod'
const envSchema = z.object({
VITE_API_URL: z.string().url(),
VITE_APP_TITLE: z.string().min(1)
})
const env = envSchema.parse(import.meta.env)
6. Linting e formatação com TypeScript no ecossistema Vite
ESLint moderno:
// eslint.config.js (flat config)
import tsParser from '@typescript-eslint/parser'
import tsPlugin from '@typescript-eslint/eslint-plugin'
export default [
{
files: ['src/**/*.ts', 'src/**/*.tsx'],
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json'
}
},
plugins: {
'@typescript-eslint': tsPlugin
},
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn'
}
}
]
Prettier integrado:
// .prettierrc
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
}
Husky + lint-staged:
npx husky init
echo "npx lint-staged" > .husky/pre-commit
// package.json
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,css,md}": ["prettier --write"]
}
}
7. Deploy e build otimizado com TypeScript
É crucial entender a diferença entre tsc e vite build:
tsc --noEmit: realiza type-checking completo, mas não gera arquivos JS.vite build: transpila TypeScript via ESBuild (ignorando tipos) e empacota com Rollup.
No package.json, configure scripts separados:
{
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview"
}
}
Tree-shaking com TypeScript: O Rollup elimina automaticamente código morto. Para maximizar isso:
// ❌ Evite imports de efeito colateral
import './polyfills'
// ✅ Prefira imports nomeados
import { debounce } from 'lodash-es'
Configuração de lib para navegadores alvo:
// tsconfig.json
{
"compilerOptions": {
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"target": "ES2020"
}
}
E no vite.config.ts:
export default defineConfig({
build: {
target: 'es2020',
polyfillDynamicImport: false
}
})
Isso garante que o código gerado seja compatível com navegadores modernos (Chrome 80+, Firefox 75+, Safari 13.1+), sem polifills desnecessários.
Referências
- Documentação oficial do Vite + TypeScript — Guia completo de integração TypeScript no Vite
- ESBuild: Why it's so fast — Explicação técnica sobre a velocidade do ESBuild em comparação com bundlers tradicionais
- TypeScript Strict Mode Explained — Documentação oficial sobre todas as flags do strict mode
- vite-plugin-checker no GitHub — Plugin para validação de tipos em tempo real no Vite
- Zod + Vite: Validando variáveis de ambiente — Tutorial oficial de validação de env vars com Zod
- Husky + lint-staged: Automação de qualidade de código — Guia prático para configurar hooks de pré-commit com TypeScript
- Rollup Tree-shaking Guide — Documentação oficial sobre eliminação de código morto no Rollup (usado pelo Vite em produção)