kb.erickguedes.com
Vite: Build Tool do Futuro

Build e Otimização

Aula 3 de 4

Build (Rollup)

Vite usa Rollup para produção, com configuração exposta via build.rollupOptions.

Configuração Básica

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

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: true,
    minify: 'esbuild',   // 'esbuild' (rápido) | 'terser' (menor) | false
    target: 'es2020',     // Alvo de transpilação
    cssCodeSplit: true,   // Separar CSS por chunk
    cssMinify: 'lightningcss', // 'esbuild' | 'lightningcss'
    reportCompressedSize: true,
    chunkSizeWarningLimit: 500 // KB
  }
});

Code Splitting

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // Separar vendor chunks
        manualChunks(id) {
          if (id.includes('node_modules')) {
            // Agrupar por biblioteca
            if (id.includes('react')) return 'vendor-react';
            if (id.includes('lodash')) return 'vendor-lodash';
            if (id.includes('d3')) return 'vendor-d3';
            return 'vendor'; // vendor genérico
          }
        },

        // Nomes customizados
        entryFileNames: 'assets/[name]-[hash].js',
        chunkFileNames: 'assets/[name]-[hash].js',
        assetFileNames: 'assets/[name]-[hash][extname]'
      }
    }
  }
});

manualChunks Avançado

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Agrupar bibliotecas específicas
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown'],
          'chart-vendor': ['recharts', 'd3']
        }
      }
    }
  }
});

Treeshaking

// vite.config.ts
export default defineConfig({
  build: {
    // Treeshaking é automático no Rollup
    // Configurar opções adicionais
    rollupOptions: {
      treeshake: {
        moduleSideEffects: [
          '*.css',
          '*.scss',
          'regenerator-runtime/runtime'
        ],
        propertyReadSideEffects: false,
        tryCatchDeoptimization: false,
        unknownGlobalSideEffects: false
      }
    }
  }
});

ChunkSizeWarningLimit

export default defineConfig({
  build: {
    chunkSizeWarningLimit: 300, // alerta se chunk > 300KB

    rollupOptions: {
      output: {
        manualChunks(id) {
          // Garantir que chunks grandes sejam divididos
          if (id.includes('node_modules')) {
            if (id.includes('react')) return 'react';
            if (id.includes('lodash')) {
              // Dividir lodash em chunks menores
              if (id.includes('lodash-es')) {
                const parts = id.split('/');
                const module = parts[parts.length - 2] || 'lodash';
                return `lodash.${module}`;
              }
            }
            return 'vendor';
          }
        }
      }
    }
  }
});

Variáveis de Ambiente

// .env
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App

// .env.development
VITE_API_URL=http://localhost:8080

// .env.production
VITE_API_URL=https://api.example.com
// Acesso no código
const apiUrl = import.meta.env.VITE_API_URL;
const mode = import.meta.env.MODE; // 'development' | 'production'
const isDev = import.meta.env.DEV;
const isProd = import.meta.env.PROD;

console.log(import.meta.env); // Tipo: ImportMetaEnv

Env por Modo

.env                # Carregado em todos os ambientes
.env.local          # Sobrescreve .env (não commitado)
.env.development    # Modo development
.env.production     # Modo production
.env.staging        # Custom
# Usar modo customizado
vite build --mode staging

Multi-Page App

// vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin/index.html'),
        login: resolve(__dirname, 'login/index.html')
      }
    }
  }
});
<!-- admin/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Admin</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/admin/src/main.tsx"></script>
  </body>
</html>

Lib Mode

Para publicar bibliotecas (não apps).

// vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';
import dts from 'vite-plugin-dts';

export default defineConfig({
  plugins: [
    dts({ // Gera .d.ts
      insertTypesEntry: true
    })
  ],
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'MyLibrary',
      formats: ['es', 'cjs', 'umd'],
      fileName: (format) => `my-lib.${format}.js`
    },
    rollupOptions: {
      external: ['react', 'react-dom'], // Peer dependencies
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM'
        }
      }
    }
  }
});
# Build da lib
npm run build

# Output
dist/
├── my-lib.es.js
├── my-lib.cjs.js
├── my-lib.umd.js
└── index.d.ts

Package.json para Lib

{
  "name": "my-library",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/my-lib.cjs.js",
  "module": "./dist/my-lib.es.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/my-lib.es.js",
      "require": "./dist/my-lib.cjs.js",
      "types": "./dist/index.d.ts"
    }
  }
}

Visualizer

npm install -D rollup-plugin-visualizer
// vite.config.ts
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    visualizer({
      filename: 'stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ]
});
npm run build
# Abre stats.html com treemap interativo do bundle

Lab: Exercício - Build Otimizado

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    react(),
    visualizer({ open: true })
  ],
  build: {
    sourcemap: false,
    minify: 'terser',
    chunkSizeWarningLimit: 300,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom', 'react-router-dom'],
          ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu']
        }
      }
    }
  }
});
npm run build

# Analisar resultado
ls -lh dist/assets/
# Ver stats.html no navegador

Code splitting com manualChunks separa vendor do código da app. Variáveis de ambiente com prefixo VITE_ são incluídas no bundle. Lib mode gera bibliotecas em múltiplos formatos. Visualizer ajuda a identificar chunks grandes.