1.8 — Vite para Diferentes Frameworks

Entenda como o Vite se integra com React, Vue e Svelte, preparando-se para o restante do curso.

Objetivos da Aula

  • Entender como plugins de framework funcionam
  • Comparar a integração do Vite com React, Vue e Svelte
  • Configurar um projeto Svelte com Vite (preparação para o próximo módulo)
  • Conhecer o vite-plugin-svelte em detalhes

Como Plugins de Framework Funcionam

Cada framework precisa de transformações específicas:

PLUGINS DE FRAMEWORK
React (.jsx/.tsx)
JSX
<App />
Babel ou esbuild
JavaScript
React.createElement()
Vue (.vue)
SFC Vue
<template>
@vue/compiler-sfc
JavaScript + CSS
Svelte (.svelte)
Componente Svelte
Svelte Compiler
JavaScript Vanilla

Vite + React

Setup

npm create vite@latest meu-app-react -- --template react
# ou com TypeScript
npm create vite@latest meu-app-react -- --template react-ts

Configuração

// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      // Opções do Babel
      babel: {
        plugins: ['@emotion/babel-plugin']
      },
      // Fast Refresh
      fastRefresh: true
    })
  ]
})

Alternativa: React + SWC (mais rápido)

npm create vite@latest meu-app -- --template react-swc
import react from '@vitejs/plugin-react-swc'

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

Estrutura Típica

src/
├── App.jsx
├── main.jsx
├── index.css
└── components/
    └── Button.jsx
// main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

Vite + Vue

Setup

npm create vite@latest meu-app-vue -- --template vue
# ou com TypeScript
npm create vite@latest meu-app-vue -- --template vue-ts

Configuração

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      // Opções do compilador Vue
      template: {
        compilerOptions: {
          // Tratar tags com hífen como custom elements
          isCustomElement: (tag) => tag.includes('-')
        }
      }
    })
  ]
})

Estrutura Típica

src/
├── App.vue
├── main.js
├── style.css
└── components/
    └── Button.vue
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'

createApp(App).mount('#app')
<!-- App.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <button @click="count++">Count: {{ count }}</button>
</template>

<style scoped>
button { color: blue; }
</style>

Vite + Svelte ⭐

Este é o foco do nosso curso!

Setup

npm create vite@latest meu-app-svelte -- --template svelte
# ou com TypeScript
npm create vite@latest meu-app-svelte -- --template svelte-ts

Configuração Básica

// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

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

Configuração Avançada

// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [
    svelte({
      // Opções do compilador Svelte
      compilerOptions: {
        // Modo de desenvolvimento
        dev: true,

        // CSS em arquivo separado ou injetado
        css: 'injected', // 'external' | 'injected' | 'none'

        // Habilitar runes (Svelte 5)
        runes: true,

        // Gerar código acessível
        accessors: false,

        // Preservar espaços em branco
        preserveWhitespace: false
      },

      // Pré-processadores
      preprocess: [
        // vitePreprocess() // Para TypeScript, SCSS, etc.
      ],

      // Hot Module Replacement
      hot: {
        // Preservar estado local durante HMR
        preserveLocalState: true
      },

      // Extensões de arquivo
      extensions: ['.svelte'],

      // Modo de emissão de CSS
      emitCss: true
    })
  ]
})

Estrutura de um Projeto Svelte

meu-app-svelte/
├── index.html
├── package.json
├── svelte.config.js          # Configuração do Svelte
├── vite.config.js            # Configuração do Vite
├── src/
│   ├── App.svelte            # Componente raiz
│   ├── main.js               # Ponto de entrada
│   ├── app.css               # Estilos globais
│   ├── lib/                  # Componentes reutilizáveis
│   │   └── Counter.svelte
│   └── vite-env.d.ts         # Tipos do Vite (TS)
└── public/
    └── vite.svg

Arquivos Principais

<!-- index.html -->
<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Meu App Svelte</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
// src/main.js
import './app.css'
import App from './App.svelte'

const app = new App({
  target: document.getElementById('app')
})

export default app
<!-- src/App.svelte -->
<script>
  import Counter from './lib/Counter.svelte'

  let name = 'mundo'
</script>

<main>
  <h1>Olá, {name}!</h1>
  <Counter />
</main>

<style>
  main {
    text-align: center;
    padding: 2rem;
  }

  h1 {
    color: #ff3e00;
  }
</style>
<!-- src/lib/Counter.svelte -->
<script>
  let count = 0

  function increment() {
    count += 1
  }
</script>

<button on:click={increment}>
  Cliques: {count}
</button>

<style>
  button {
    font-size: 1.5rem;
    padding: 0.5rem 1rem;
    cursor: pointer;
  }
</style>

Comparativo: O Mesmo Componente

React

// Counter.jsx
import { useState } from 'react'
import './Counter.css'

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Cliques: {count}
    </button>
  )
}

Vue

<!-- Counter.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <button @click="count++">
    Cliques: {{ count }}
  </button>
</template>

<style scoped>
button { /* estilos */ }
</style>

Svelte

<!-- Counter.svelte -->
<script>
  let count = 0
</script>

<button on:click={() => count++}>
  Cliques: {count}
</button>

<style>
  button { /* estilos - escopo automático! */ }
</style>

Comparação

AspectoReactVueSvelte
EstadouseState hookref()let simples
EventosonClick@clickon:click
Interpolação{valor}{{ valor }}{valor}
CSSExterno/CSS-in-JS<style scoped><style> (escopo auto)
BundleRuntime ReactRuntime VueZero runtime

Pré-processadores com Svelte

Instalação do vitePreprocess

npm install -D @sveltejs/vite-plugin-svelte
npm install -D sass  # Para SCSS
npm install -D typescript  # Para TS
// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

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

Usando TypeScript

<!-- Componente.svelte -->
<script lang="ts">
  interface User {
    name: string
    age: number
  }

  export let user: User

  let count: number = 0
</script>

<p>{user.name} tem {user.age} anos</p>

Usando SCSS

<!-- Componente.svelte -->
<style lang="scss">
  $primary: #ff3e00;

  button {
    background: $primary;

    &:hover {
      background: darken($primary, 10%);
    }
  }
</style>

svelte.config.js

Além do vite.config.js, projetos Svelte têm um arquivo de configuração específico:

// svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'

export default {
  // Pré-processadores (TS, SCSS, etc)
  preprocess: vitePreprocess(),

  // Opções do compilador
  compilerOptions: {
    // Svelte 5 runes
    runes: true
  },

  // Avisos a ignorar
  onwarn: (warning, handler) => {
    // Ignora avisos de acessibilidade específicos
    if (warning.code === 'a11y-click-events-have-key-events') return
    handler(warning)
  },

  // Extensões de arquivo
  extensions: ['.svelte']
}

🎯 Mini-Projeto: Migrando para Svelte

Vamos criar a versão Svelte do nosso Dashboard!

Passo 1: Criar Projeto Svelte

# Na pasta do curso
npm create vite@latest dashboard-svelte -- --template svelte

cd dashboard-svelte
npm install

Passo 2: Configurar Aliases

// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import path from 'path'

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

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

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

Passo 3: Componente PerformanceCard

<!-- src/components/PerformanceCard.svelte -->
<script>
  export let titulo = ''
  export let valor = 0
  export let unidade = 'ms'
</script>

<div class="performance-card">
  <h3 class="card-title">{titulo}</h3>
  <p class="card-value">
    {valor}<span class="card-unit">{unidade}</span>
  </p>
</div>

<style>
  .performance-card {
    background: #242424;
    border-radius: 12px;
    padding: 1.5rem;
    transition: transform 0.2s, box-shadow 0.2s;
  }

  .performance-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
  }

  .card-title {
    font-size: 0.875rem;
    color: #a0a0a0;
    margin-bottom: 0.5rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }

  .card-value {
    font-size: 2rem;
    font-weight: 700;
    color: #4ade80;
    margin: 0;
  }

  .card-unit {
    font-size: 0.875rem;
    color: #a0a0a0;
    margin-left: 0.25rem;
  }
</style>

Passo 4: Componente App Principal

<!-- src/App.svelte -->
<script>
  import { onMount } from 'svelte'
  import PerformanceCard from '@components/PerformanceCard.svelte'

  let metricas = {
    domReady: 0,
    pageLoad: 0,
    fcp: 'N/A',
    renderTime: 0
  }

  const inicioRender = performance.now()

  onMount(() => {
    // Calcula tempo de render
    metricas.renderTime = (performance.now() - inicioRender).toFixed(2)

    // Espera página carregar para métricas completas
    window.addEventListener('load', () => {
      const timing = performance.timing
      metricas.domReady = timing.domContentLoadedEventEnd - timing.navigationStart
      metricas.pageLoad = timing.loadEventEnd - timing.navigationStart

      // FCP
      const paintEntries = performance.getEntriesByType('paint')
      const fcp = paintEntries.find(e => e.name === 'first-contentful-paint')
      metricas.fcp = fcp ? fcp.startTime.toFixed(2) : 'N/A'
    })
  })
</script>

<div class="dashboard">
  <header class="header">
    <img src="/vite.svg" class="logo" alt="Vite logo" />
    <h1>Dashboard Svelte</h1>
    <p class="subtitle">Agora com o poder do Svelte! ⚡</p>
  </header>

  <main class="main">
    <section class="section">
      <h2>📊 Métricas de Performance</h2>
      <div class="cards-grid">
        <PerformanceCard titulo="DOM Ready" valor={metricas.domReady} />
        <PerformanceCard titulo="Page Load" valor={metricas.pageLoad} />
        <PerformanceCard titulo="FCP" valor={metricas.fcp} />
        <PerformanceCard titulo="Render" valor={metricas.renderTime} />
      </div>
    </section>
  </main>

  <footer class="footer">
    <p>Feito com Svelte + Vite</p>
  </footer>
</div>

<style>
  :global(body) {
    margin: 0;
    font-family: Inter, system-ui, -apple-system, sans-serif;
    background-color: #0f0f0f;
    color: #ffffff;
  }

  .dashboard {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem;
  }

  .header {
    text-align: center;
    margin-bottom: 3rem;
  }

  .logo {
    width: 80px;
    height: 80px;
    animation: pulse 2s ease-in-out infinite;
  }

  @keyframes pulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.05); }
  }

  .header h1 {
    font-size: 2.5rem;
    margin: 1rem 0 0.5rem;
    background: linear-gradient(135deg, #ff3e00, #ff8a00);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
  }

  .subtitle {
    color: #a0a0a0;
    margin: 0;
  }

  .section {
    margin-bottom: 2rem;
  }

  .section h2 {
    margin-bottom: 1rem;
    font-size: 1.25rem;
    color: #a0a0a0;
  }

  .cards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1rem;
  }

  .footer {
    margin-top: 3rem;
    text-align: center;
    color: #666;
  }
</style>

Passo 5: Testar

npm run dev
# Acesse http://localhost:3000

npm run build
# Observe: bundle MUITO menor que React/Vue!

✅ Desafio da Aula

Objetivo

Adicionar um componente Counter interativo ao Dashboard Svelte.

Instruções

  1. Crie src/components/Counter.svelte
  2. O contador deve ter botões de + e -
  3. Adicione o componente ao App.svelte
  4. Bônus: Adicione uma animação quando o valor muda

Spec de Verificação

  • O contador aparece no dashboard
  • Clicar em + aumenta o valor
  • Clicar em - diminui o valor
  • Bônus: Há animação visual na mudança

Solução

🔍 Clique para ver a solução
<!-- src/components/Counter.svelte -->
<script>
  let count = 0

  function increment() {
    count += 1
  }

  function decrement() {
    count -= 1
  }
</script>

<div class="counter-card">
  <h3>Contador Interativo</h3>
  <div class="counter-display">
    <button on:click={decrement} class="btn-minus"></button>
    <span class="count" class:bump={count !== 0}>{count}</span>
    <button on:click={increment} class="btn-plus">+</button>
  </div>
</div>

<style>
  .counter-card {
    background: #242424;
    border-radius: 12px;
    padding: 1.5rem;
    text-align: center;
  }

  h3 {
    margin: 0 0 1rem;
    color: #a0a0a0;
    font-size: 0.875rem;
    text-transform: uppercase;
  }

  .counter-display {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
  }

  button {
    width: 48px;
    height: 48px;
    border: none;
    border-radius: 50%;
    font-size: 1.5rem;
    cursor: pointer;
    transition: transform 0.1s, background 0.2s;
  }

  button:hover {
    transform: scale(1.1);
  }

  button:active {
    transform: scale(0.95);
  }

  .btn-plus {
    background: #4ade80;
    color: #000;
  }

  .btn-minus {
    background: #ef4444;
    color: #fff;
  }

  .count {
    font-size: 3rem;
    font-weight: 700;
    min-width: 80px;
    color: #fff;
    transition: transform 0.1s;
  }

  .count.bump {
    animation: bump 0.1s ease-out;
  }

  @keyframes bump {
    50% { transform: scale(1.2); }
  }
</style>

No App.svelte, adicione:

<script>
  import Counter from '@components/Counter.svelte'
</script>

<!-- Na section -->
<Counter />

🎉 Conclusão do Módulo 1

Parabéns! Você completou o Módulo 1 sobre Fundamentos do Vite.

O que você aprendeu

  • ✅ Por que o Vite é mais rápido que bundlers tradicionais
  • ✅ Arquitetura de ESModules nativos e HMR
  • ✅ Criação e estrutura de projetos Vite
  • ✅ Configuração avançada com vite.config.js
  • ✅ Sistema de plugins e criação de plugins customizados
  • ✅ Variáveis de ambiente e múltiplos modos
  • ✅ Build de produção e otimização
  • ✅ Integração com diferentes frameworks (React, Vue, Svelte)

Próximos passos

No Módulo 2, vamos fazer uma comparação profunda entre Svelte e React, entendendo as diferenças filosóficas e práticas entre os dois frameworks.


Próximo módulo: Módulo 2 — Svelte vs React: Entendendo as Diferenças