Ejemplos de Vite

Ejemplos de la herramienta de compilación Vite - Servidor de desarrollo moderno, HMR, empaquetado y sistema de plugins

💻 Fundamentos y Configuración de Vite javascript

🟢 simple ⭐⭐

Configuración básica de Vite, configuración del proyecto, servidor de desarrollo y características principales

⏱️ 20 min 🏷️ vite, build, configuration, development
Prerequisites: Node.js, JavaScript/TypeScript, Basic understanding of build tools
// Vite Fundamentals and Setup Examples

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

export default defineConfig({
  // Development server configuration
  server: {
    port: 3000,
    host: true, // Expose to network
    open: true, // Open browser automatically
    cors: true, // Enable CORS
    proxy: {
      // API proxy
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, '')
      }
    }
  },

  // Build configuration
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: true,
    minify: 'esbuild', // 'terser' or false
    target: 'es2015',

    // Rollup configuration
    rollupOptions: {
      input: 'src/main.js',
      output: {
        manualChunks: {
          vendor: ['vue', 'react'],
          utils: ['lodash', 'date-fns']
        }
      }
    },

    // Report compressed sizes
    reportCompressedSize: true,

    // CSS code splitting
    cssCodeSplit: true,

    // Chunk size warning limit
    chunkSizeWarningLimit: 1000
  },

  // Preview server configuration
  preview: {
    port: 4173,
    host: true
  },

  // Dependency optimization
  optimizeDeps: {
    include: ['vue', 'react', 'axios'],
    exclude: ['@types/lodash']
  },

  // Plugins
  plugins: [
    vue(),
    react({
      // React fast refresh
      fastRefresh: true
    })
  ],

  // Environment variables
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
  },

  // CSS configuration
  css: {
    // CSS modules configuration
    modules: {
      localsConvention: 'camelCase',
      generateScopedName: '[name]__[hash:base64:5]'
    },

    // PostCSS configuration
    postcss: {
      plugins: [
        require('autoprefixer'),
        require('tailwindcss')
      ]
    },

    // Preprocessor options
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/styles/variables.scss";`
      }
    }
  },

  // Assets configuration
  assetsInclude: ['**/*.gltf'],

  // Experimental features
  experimental: {
    renderBuiltUrl(filename, { hostType }) {
      if (hostType === 'js') {
        return { js: `window.__toPath(${JSON.stringify(filename)})` }
      } else {
        return { relative: true }
      }
    }
  },

  // Global style preprocessing
  cssPreprocessOptions: {
    less: {
      modifyVars: {
        'primary-color': '#1890ff'
      }
    }
  }
})

// 2. package.json - Project Setup
{
  "name": "vite-project",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "serve": "vite preview",
    "build:analyze": "vite build --mode analyze && npx vite-bundle-analyzer dist/stats.html",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage",
    "lint": "eslint . --ext vue,js,jsx,cjs,mjs,ts,tsx,cts,mts --fix --ignore-path .gitignore",
    "format": "prettier --write src/",
    "type-check": "vue-tsc --noEmit"
  },
  "dependencies": {
    "vue": "^3.4.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "axios": "^1.6.0",
    "lodash": "^4.17.21",
    "date-fns": "^2.30.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.0",
    "@vitejs/plugin-react": "^4.2.0",
    "vite": "^5.0.0",
    "vite-plugin-windicss": "^1.9.0",
    "windicss": "^3.5.6",
    "autoprefixer": "^10.4.0",
    "tailwindcss": "^3.4.0",
    "sass": "^1.69.0",
    "typescript": "^5.3.0",
    "vue-tsc": "^1.8.0",
    "vitest": "^1.0.0",
    "@vitest/ui": "^1.0.0",
    "@vitest/coverage-v8": "^1.0.0",
    "jsdom": "^23.0.0",
    "eslint": "^8.56.0",
    "eslint-plugin-vue": "^9.19.0",
    "eslint-plugin-react": "^7.33.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "prettier": "^3.1.0",
    "prettier-plugin-tailwindcss": "^0.5.0"
  }
}

// 3. Environment Variables (.env)
# Development environment variables
VITE_APP_TITLE=My Vite App
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_VERSION=1.0.0
VITE_DEBUG_MODE=true

// .env.production
VITE_APP_TITLE=Production App
VITE_API_BASE_URL=https://api.example.com
VITE_DEBUG_MODE=false

// 4. Using Environment Variables in JavaScript
// src/config.js
export const config = {
  appName: import.meta.env.VITE_APP_TITLE || 'Default App',
  apiUrl: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000',
  version: import.meta.env.VITE_APP_VERSION || '1.0.0',
  isDebug: import.meta.env.VITE_DEBUG_MODE === 'true',
  isDevelopment: import.meta.env.DEV,
  isProduction: import.meta.env.PROD
}

// 5. Basic React Example with Vite (src/App.jsx)
import React, { useState, useEffect } from 'react'
import './App.css'

function App() {
  const [count, setCount] = useState(0)
  const [users, setUsers] = useState([])

  useEffect(() => {
    fetchUsers()
  }, [])

  const fetchUsers = async () => {
    try {
      const response = await fetch(`${config.apiUrl}/users`)
      const data = await response.json()
      setUsers(data)
    } catch (error) {
      console.error('Error fetching users:', error)
    }
  }

  return (
    <div className="app">
      <header>
        <h1>{config.appName}</h1>
        <p>Version: {config.version}</p>
        <p>Environment: {config.isDevelopment ? 'Development' : 'Production'}</p>
      </header>

      <main>
        <div className="counter-section">
          <h2>Counter Example</h2>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>
            Increment
          </button>
          <button onClick={() => setCount(count - 1)}>
            Decrement
          </button>
        </div>

        <div className="users-section">
          <h2>Users List</h2>
          {users.length > 0 ? (
            <ul>
              {users.map(user => (
                <li key={user.id}>{user.name}</li>
              ))}
            </ul>
          ) : (
            <p>Loading users...</p>
          )}
        </div>
      </main>
    </div>
  )
}

export default App

// 6. Basic Vue Example with Vite (src/App.vue)
<template>
  <div id="app">
    <header>
      <h1>{{ config.appName }}</h1>
      <p>Version: {{ config.version }}</p>
      <p>Environment: {{ config.isDevelopment ? 'Development' : 'Production' }}</p>
    </header>

    <main>
      <section class="counter-section">
        <h2>Counter Example</h2>
        <p>Count: {{ count }}</p>
        <button @click="increment">Increment</button>
        <button @click="decrement">Decrement</button>
      </section>

      <section class="users-section">
        <h2>Users List</h2>
        <ul v-if="users.length">
          <li v-for="user in users" :key="user.id">
            {{ user.name }}
          </li>
        </ul>
        <p v-else>Loading users...</p>
      </section>
    </main>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { config } from './config'

const count = ref(0)
const users = ref([])

const increment = () => {
  count.value++
}

const decrement = () => {
  count.value--
}

const fetchUsers = async () => {
  try {
    const response = await fetch(`${config.apiUrl}/users`)
    const data = await response.json()
    users.value = data
  } catch (error) {
    console.error('Error fetching users:', error)
  }
}

onMounted(() => {
  fetchUsers()
})
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

header {
  margin-bottom: 2rem;
}

.counter-section, .users-section {
  margin-bottom: 2rem;
}

button {
  margin: 0 0.5rem;
  padding: 0.5rem 1rem;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #369870;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  margin: 0.5rem 0;
  padding: 0.5rem;
  background-color: #f9f9f9;
  border-radius: 4px;
}
</style>

// 7. JavaScript Module Example (src/modules/api.js)
import axios from 'axios'

// Create axios instance with base configuration
const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// Request interceptor
api.interceptors.request.use(
  (config) => {
    // Add authentication token if available
    const token = localStorage.getItem('authToken')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }

    // Add request timestamp
    config.metadata = { startTime: new Date() }

    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// Response interceptor
api.interceptors.response.use(
  (response) => {
    // Calculate request duration
    const endTime = new Date()
    const duration = endTime - response.config.metadata.startTime
    console.log(`Request took ${duration}ms`)

    return response
  },
  (error) => {
    // Handle common errors
    if (error.response?.status === 401) {
      // Redirect to login
      window.location.href = '/login'
    }

    return Promise.reject(error)
  }
)

// API methods
export const userApi = {
  getUsers: () => api.get('/users'),
  getUser: (id) => api.get(`/users/${id}`),
  createUser: (userData) => api.post('/users', userData),
  updateUser: (id, userData) => api.put(`/users/${id}`, userData),
  deleteUser: (id) => api.delete(`/users/${id}`)
}

export const productApi = {
  getProducts: (params = {}) => api.get('/products', { params }),
  getProduct: (id) => api.get(`/products/${id}`),
  createProduct: (productData) => api.post('/products', productData),
  updateProduct: (id, productData) => api.put(`/products/${id}`, productData),
  deleteProduct: (id) => api.delete(`/products/${id}`)
}

export default api

// 8. CSS Example with CSS Modules (src/components/Button.module.css)
.button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 0.5rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.primary {
  background-color: #3b82f6;
  color: white;
}

.primary:hover {
  background-color: #2563eb;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}

.secondary {
  background-color: #6b7280;
  color: white;
}

.secondary:hover {
  background-color: #4b5563;
}

.danger {
  background-color: #ef4444;
  color: white;
}

.danger:hover {
  background-color: #dc2626;
}

.large {
  padding: 1rem 2rem;
  font-size: 1.125rem;
}

.small {
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
}

// Usage in React component
import styles from './Button.module.css'

function Button({ variant = 'primary', size = 'medium', children, ...props }) {
  return (
    <button
      className={`${styles.button} ${styles[variant]} ${styles[size]}`}
      {...props}
    >
      {children}
    </button>
  )
}

// 9. Testing with Vitest (src/components/Button.test.js)
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import Button from './Button'

describe('Button Component', () => {
  it('renders with default props', () => {
    render(<Button>Click me</Button>)
    const button = screen.getByRole('button', { name: /click me/i })

    expect(button).toBeInTheDocument()
    expect(button).toHaveClass('button')
    expect(button).toHaveClass('primary')
  })

  it('applies variant classes correctly', () => {
    render(<Button variant="danger">Delete</Button>)
    const button = screen.getByRole('button', { name: /delete/i })

    expect(button).toHaveClass('danger')
  })

  it('handles click events', () => {
    const handleClick = vi.fn()
    render(<Button onClick={handleClick}>Click me</Button>)

    fireEvent.click(screen.getByRole('button'))
    expect(handleClick).toHaveBeenCalledTimes(1)
  })

  it('is disabled when disabled prop is true', () => {
    render(<Button disabled>Disabled</Button>)
    const button = screen.getByRole('button')

    expect(button).toBeDisabled()
  })
})

// 10. PostCSS Configuration (postcss.config.js)
export default {
  plugins: {
    'tailwindcss': {},
    'autoprefixer': {},
    'postcss-px-to-viewport': {
      viewportWidth: 375, // 设计稿的视口宽度
      unitPrecision: 5,    // 单位转换后保留的精度
      viewportUnit: 'vw',  // 希望使用的视口单位
      selectorBlackList: ['ignore'], // 需要忽略的CSS选择器
      minPixelValue: 1,    // 设置最小的转换数值
      mediaQuery: false    // 媒体查询里的单位是否需要转换单位
    }
  }
}

💻 Desarrollo de Plugins de Vite javascript

🟡 intermediate ⭐⭐⭐⭐

Creación de plugins personalizados de Vite, uso de plugins oficiales y ecosistema de plugins

⏱️ 35 min 🏷️ vite, plugins, development, api, hooks
Prerequisites: Vite basics, JavaScript/TypeScript, Node.js API, Build tool concepts
// Vite Plugin Development Examples

// 1. Custom Vite Plugin Structure
// vite-plugin-custom.js
export function customPlugin(options = {}) {
  const {
    transform = true,
    serve = true,
    build = true
  } = options

  return {
    // Plugin name
    name: 'vite-plugin-custom',

    // enforce plugin execution order
    enforce: 'pre', // 'post' or undefined

    // Apply plugin to specific files
    apply: 'serve', // 'build', 'both', or config

    // Config hook - Modify Vite config
    config(config, { command, mode }) {
      console.log('Config hook:', { command, mode })

      // Modify config
      if (command === 'serve') {
        config.server.hmr = {
          overlay: false
        }
      }

      return {
        define: {
          ...config.define,
          __CUSTOM_PLUGIN__: JSON.stringify(true)
        }
      }
    },

    // Config resolved hook
    configResolved(config) {
      console.log('Config resolved hook')
    },

    // Configure server
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        if (req.url === '/custom-api') {
          res.setHeader('Content-Type', 'application/json')
          res.end(JSON.stringify({ message: 'Custom API response' }))
          return
        }
        next()
      })

      // Return cleanup function
      return () => {
        console.log('Server cleanup')
      }
    },

    // Transform modules
    transform(code, id, options) {
      if (!transform) return null

      // Only transform .js files
      if (!id.endsWith('.js')) return null

      console.log('Transforming:', id)

      // Add custom code injection
      if (id.includes('main.js')) {
        return {
          code: code + '\nconsole.log("Custom plugin injected!");',
          map: null // Generate source map if needed
        }
      }

      return null
    },

    // Load id
    load(id) {
      if (id === 'virtual:custom-module') {
        return `export default 'Custom virtual module content'`
      }
    },

    // Resolve id
    resolveId(id, importer) {
      if (id === 'virtual:custom-module') {
        return id
      }
    },

    // Build hooks
    buildStart() {
      console.log('Build started')
    },

    buildEnd() {
      console.log('Build ended')
    },

    generateBundle(options, bundle) {
      console.log('Generate bundle:', Object.keys(bundle))
    },

    writeBundle(options, bundle) {
      console.log('Write bundle completed')
    },

    // Handle hot module replacement
    handleHotUpdate({ file, modules, timestamp }) {
      console.log('HMR update:', file)

      // Custom HMR logic
      if (file.endsWith('.custom')) {
        // Invalidate all modules
        return modules
      }
    }
  }
}

// 2. Vue Plugin Development
// vite-plugin-vue-enhanced.js
import vue from '@vitejs/plugin-vue'
import { compileTemplate } from '@vue/compiler-sfc'

export function vueEnhancedPlugin(options = {}) {
  return [
    vue(),
    {
      name: 'vite-plugin-vue-enhanced',

      transform(code, id) {
        if (!id.endsWith('.vue')) return null

        // Custom Vue template transformations
        if (options.enableAutoTranslate) {
          // Auto-translate text content
          code = code.replace(
            />([^<]+)</g,
            (match, text) => {
              const trimmed = text.trim()
              if (trimmed && !trimmed.includes('{') && !trimmed.includes('v-')) {
                return `>{{ t('${trimmed}') }}<`
              }
              return match
            }
          )
        }

        return { code, map: null }
      },

      // Vue-specific hooks
      vueTransform(code, filename) {
        console.log('Vue transform:', filename)
        return { code }
      }
    }
  ]
}

// 3. CSS Plugin Development
// vite-plugin-css-enhanced.js
export function cssEnhancedPlugin(options = {}) {
  const {
    enableVariables = false,
    enableAutoPrefix = false,
    enableMinification = false
  } = options

  return {
    name: 'vite-plugin-css-enhanced',

    transform(code, id) {
      if (!id.endsWith('.css')) return null

      let transformedCode = code

      // Auto prefix CSS properties
      if (enableAutoPrefix) {
        transformedCode = transformedCode.replace(
          /([^:]+):([^;]+)/g,
          (match, property, value) => {
            const prefixes = {
              'transform': '-webkit-transform',
              'transition': '-webkit-transition',
              'animation': '-webkit-animation',
              'user-select': '-webkit-user-select',
              'box-sizing': '-webkit-box-sizing'
            }

            const prefixed = prefixes[property.trim()]
            return prefixed ? `${prefixed}: ${value}; ${match}` : match
          }
        )
      }

      // CSS variables transformation
      if (enableVariables) {
        transformedCode = transformedCode.replace(
          /\$([a-zA-Z0-9-_]+):/g,
          ':root { --$1:'
        )
      }

      if (transformedCode !== code) {
        return { code: transformedCode, map: null }
      }
    },

    // CSS processing hooks
    cssTransform(code, filename) {
      console.log('CSS transform:', filename)
      return { code }
    }
  }
}

// 4. Image Optimization Plugin
// vite-plugin-image-optimize.js
import sharp from 'sharp'
import fs from 'fs/promises'
import path from 'path'

export function imageOptimizePlugin(options = {}) {
  const {
    formats = ['webp', 'avif'],
    quality = 80,
    sizes = [480, 768, 1024, 1920]
  } = options

  return {
    name: 'vite-plugin-image-optimize',

    async load(id) {
      if (!id.match(/\.(png|jpg|jpeg)$/i)) return null

      const originalPath = id
      const publicDir = path.dirname(id).includes('/public')
        ? path.dirname(id).split('/public/')[1]
        : ''

      // Generate responsive images
      const optimizedImages = []

      for (const format of formats) {
        for (const size of sizes) {
          const filename = `optimized-${size}w.${format}`
          const outputPath = `public/optimized/${filename}`

          try {
            await fs.mkdir(path.dirname(outputPath), { recursive: true })

            await sharp(originalPath)
              .resize(size, null, { withoutEnlargement: true })
              .toFormat(format, { quality })
              .toFile(outputPath)

            optimizedImages.push({
              src: `/optimized/${filename}`,
              width: size,
              format,
              type: format.toUpperCase()
            })
          } catch (error) {
            console.warn('Image optimization failed:', error)
          }
        }
      }

      // Return JavaScript module with image data
      return `
export default {
  src: `${publicDir}/${path.basename(originalPath)}`,
  optimized: ${JSON.stringify(optimizedImages)}
}
`
    }
  }
}

// 5. Markdown Enhancement Plugin
// vite-plugin-markdown-enhanced.js
import { marked } from 'marked'
import Prism from 'prismjs'

export function markdownEnhancedPlugin(options = {}) {
  const {
    enableCodeHighlight = true,
    enableTOC = true,
    enableFrontMatter = true
  } = options

  return {
    name: 'vite-plugin-markdown-enhanced',

    async load(id) {
      if (!id.endsWith('.md')) return null

      let content = await fs.readFile(id, 'utf-8')
      let frontMatter = {}

      // Parse front matter
      if (enableFrontMatter && content.startsWith('---')) {
        const frontMatterEnd = content.indexOf('---', 3)
        const frontMatterStr = content.slice(3, frontMatterEnd).trim()
        content = content.slice(frontMatterEnd + 3).trim()

        // Simple YAML parser (in production, use js-yaml)
        frontMatterStr.split('\n').forEach(line => {
          const [key, ...valueParts] = line.split(':')
          if (key && valueParts.length) {
            frontMatter[key.trim()] = valueParts.join(':').trim()
          }
        })
      }

      // Generate table of contents
      let toc = ''
      if (enableTOC) {
        const headings = content.match(/^#{1,6}\s.+$/gm) || []
        toc = '## Table of Contents\n\n'

        headings.forEach(heading => {
          const level = heading.match(/^#/)?.length || 1
          const text = heading.replace(/^#+\s/, '')
          const anchor = text.toLowerCase().replace(/[^a-z0-9]+/g, '-')

          toc += `${'  '.repeat(level - 1)}* [${text}](#${anchor})\n`
        })
      }

      // Convert markdown to HTML
      let html = marked(content)

      // Add code highlighting
      if (enableCodeHighlight) {
        html = html.replace(
          /<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g,
          (match, lang, code) => {
            try {
              const highlighted = Prism.highlight(
                code.replace(/&lt;/g, '<').replace(/&gt;/g, '>'),
                Prism.languages[lang],
                lang
              )
              return `<pre><code class="language-${lang}">${highlighted}</code></pre>`
            } catch (e) {
              return match
            }
          }
        )
      }

      // Return JavaScript module
      return `
export default {
  frontMatter: ${JSON.stringify(frontMatter)},
  toc: `${toc}`,
  html: `${html}`,
  content: `${content}`
}
`
    }
  }
}

// 6. Bundle Analysis Plugin
// vite-plugin-bundle-analyzer.js
export function bundleAnalyzerPlugin(options = {}) {
  const {
    outputFile = 'bundle-analysis.html',
    open = true
  } = options

  return {
    name: 'vite-plugin-bundle-analyzer',

    generateBundle(options, bundle) {
      const analysis = {}

      for (const [fileName, chunk] of Object.entries(bundle)) {
        if (chunk.type === 'chunk') {
          analysis[fileName] = {
            size: chunk.code.length,
            modules: Object.keys(chunk.modules).length,
            imports: chunk.imports || [],
            dynamicImports: chunk.dynamicImports || []
          }
        }
      }

      // Generate HTML report
      const html = `
<!DOCTYPE html>
<html>
<head>
  <title>Bundle Analysis</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
  <h1>Bundle Analysis</h1>
  <canvas id="bundleChart"></canvas>
  <script>
    const data = ${JSON.stringify(analysis)};
    const ctx = document.getElementById('bundleChart').getContext('2d');

    new Chart(ctx, {
      type: 'bar',
      data: {
        labels: Object.keys(data),
        datasets: [{
          label: 'Bundle Size (bytes)',
          data: Object.values(data).map(d => d.size),
          backgroundColor: 'rgba(54, 162, 235, 0.2)',
          borderColor: 'rgba(54, 162, 235, 1)',
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          y: {
            beginAtZero: true
          }
        }
      }
    });
  </script>
  <pre>${JSON.stringify(analysis, null, 2)}</pre>
</body>
</html>
`

      // Write analysis file
      this.emitFile({
        type: 'asset',
        fileName: outputFile,
        source: html
      })

      if (open) {
        console.log(`Bundle analysis saved to: ${outputFile}`)
      }
    }
  }
}

// 7. Advanced Plugin Example - PWA Manifest Generator
// vite-plugin-pwa-manifest.js
export function pwaManifestPlugin(options = {}) {
  const {
    name = 'My PWA App',
    shortName = 'PWA',
    themeColor = '#000000',
    backgroundColor = '#ffffff',
    display = 'standalone',
    startUrl = '/',
    icons = []
  } = options

  const manifest = {
    name,
    shortName,
    themeColor,
    backgroundColor,
    display,
    startUrl,
    icons: icons.map(size => ({
      src: `/icon-${size}.png`,
      sizes: `${size}x${size}`,
      type: 'image/png'
    }))
  }

  return {
    name: 'vite-plugin-pwa-manifest',

    configResolved(config) {
      // Ensure public directory exists
      const publicDir = path.resolve(config.root, 'public')
      fs.mkdir(publicDir, { recursive: true })
    },

    generateBundle(options, bundle) {
      // Generate manifest.json
      this.emitFile({
        type: 'asset',
        fileName: 'manifest.json',
        source: JSON.stringify(manifest, null, 2)
      })

      // Generate service worker template
      const serviceWorker = `
const CACHE_NAME = '${name.toLowerCase().replace(/\s+/g, '-')}-v1'
const urlsToCache = ${JSON.stringify(Object.keys(bundle))}

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  )
})

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        return response || fetch(event.request)
      })
  )
})
`

      this.emitFile({
        type: 'asset',
        fileName: 'sw.js',
        source: serviceWorker
      })
    }
  }
}

// 8. Using Multiple Plugins in vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { customPlugin } from './vite-plugin-custom.js'
import { vueEnhancedPlugin } from './vite-plugin-vue-enhanced.js'
import { cssEnhancedPlugin } from './css-enhanced.js'
import { imageOptimizePlugin } from './image-optimize.js'
import { pwaManifestPlugin } from './pwa-manifest.js'

export default defineConfig({
  plugins: [
    // Core plugins
    react(),

    // Custom plugins
    customPlugin({
      transform: true,
      serve: true,
      build: false // Disable in build for performance
    }),

    cssEnhancedPlugin({
      enableAutoPrefix: true,
      enableVariables: true
    }),

    // Production-only plugins
    process.env.NODE_ENV === 'production' && imageOptimizePlugin({
      formats: ['webp', 'avif'],
      quality: 85
    }),

    // PWA plugin
    pwaManifestPlugin({
      name: 'My Awesome App',
      icons: [192, 512]
    })
  ].filter(Boolean) // Remove falsy plugins
})

// 9. Plugin Testing with Vitest
// vite-plugin-custom.test.js
import { describe, it, expect, vi } from 'vitest'
import { customPlugin } from './vite-plugin-custom.js'

describe('Custom Vite Plugin', () => {
  it('should have correct plugin name', () => {
    const plugin = customPlugin()
    expect(plugin.name).toBe('vite-plugin-custom')
  })

  it('should apply custom config in development', () => {
    const plugin = customPlugin()
    const mockConfig = { define: {} }

    const result = plugin.config(mockConfig, { command: 'serve', mode: 'development' })

    expect(result.define.__CUSTOM_PLUGIN__).toBe(true)
    expect(result.server.hmr.overlay).toBe(false)
  })

  it('should transform main.js file', () => {
    const plugin = customPlugin()
    const code = 'console.log("original");'
    const id = '/path/to/main.js'

    const result = plugin.transform(code, id)

    expect(result.code).toContain('Custom plugin injected!')
  })

  it('should handle virtual module', () => {
    const plugin = customPlugin()
    const result = plugin.load('virtual:custom-module')

    expect(result).toBe('export default \'Custom virtual module content\'')
  })

  it('should resolve virtual module id', () => {
    const plugin = customPlugin()
    const result = plugin.resolveId('virtual:custom-module')

    expect(result).toBe('virtual:custom-module')
  })
})

// 10. Plugin Development Best Practices
// Documentation: Always include README.md with examples
/*
# Vite Custom Plugin

## Usage

```js
// vite.config.js
import { customPlugin } from 'vite-plugin-custom'

export default {
  plugins: [
    customPlugin({
      // Plugin options
    })
  ]
}
```

## API

### Options

- `transform` (boolean): Enable code transformation (default: true)
- `serve` (boolean): Apply to dev server (default: true)
- `build` (boolean): Apply to build (default: true)

### Hooks

- `config`: Modify Vite configuration
- `transform`: Transform module code
- `load`: Load virtual modules
- `configureServer`: Configure development server

## Examples

```js
// Basic usage
export default {
  plugins: [customPlugin()]
}

// With options
export default {
  plugins: [
    customPlugin({
      transform: true,
      serve: true,
      build: false
    })
  ]
}
```
*/

💻 Características Avanzadas y Optimización de Vite javascript

🔴 complex ⭐⭐⭐⭐⭐

Optimización de rendimiento, SSR, empaquetado avanzado, despliegue y consideraciones de producción

⏱️ 45 min 🏷️ vite, optimization, production, deployment, ssr
Prerequisites: Advanced Vite knowledge, Production deployment, Performance optimization, Build tools expertise
// Vite Advanced Features and Optimization Examples

// 1. Advanced Vite Configuration for Production
// vite.config.prod.js
import { defineConfig } from 'vite'
import { createRequire } from 'module'
import { fileURLToPath } from 'url'
import { dirname, resolve } from 'path'
import { visualizer } from 'rollup-plugin-visualizer'
import { VitePWA } from 'vite-plugin-pwa'

const __dirname = dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)

export default defineConfig({
  // Advanced build configuration
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false, // Disabled for production
    minify: 'terser',
    target: 'es2020',

    // Rollup optimization
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin.html'),
      },

      output: {
        // Chunk splitting strategy
        manualChunks: {
          // Vendor libraries
          vendor: ['react', 'react-dom', 'vue', 'vue-router'],

          // Utility libraries
          utils: ['lodash-es', 'date-fns', 'axios'],

          // UI libraries
          ui: ['antd', '@headlessui/vue', 'element-plus'],

          // Charts
          charts: ['chart.js', 'echarts'],

          // Icons
          icons: ['@ant-design/icons', '@fortawesome/fontawesome-free'],
        },

        // Asset naming
        chunkFileNames: (chunkInfo) => {
          const facadeModuleId = chunkInfo.facadeModuleId ?
            chunkInfo.facadeModuleId.split('/').pop() : 'chunk'
          return `js/${facadeModuleId}-[hash].js`
        },

        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name.split('.')
          const ext = info[info.length - 1]
          if (/.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) {
            return `media/${info[info.length - 2]}-[hash][extname]`
          }
          if (/\.(png|jpe?g|gif|svg)(\?.*)?$/i.test(assetInfo.name)) {
            return `img/${info[info.length - 2]}-[hash][extname]`
          }
          if (/\.css$/i.test(assetInfo.name)) {
            return `css/[name]-[hash][extname]`
          }
          return `assets/${info[info.length - 2]}-[hash][extname]`
        }
      },

      // External dependencies
      external: ['react', 'react-dom', 'vue'],

      // Plugin configuration
      plugins: [
        // Bundle analyzer
        process.env.ANALYZE && visualizer({
          filename: 'dist/stats.html',
          open: true,
          gzipSize: true,
          brotliSize: true
        })
      ].filter(Boolean)
    },

    // Build report
    reportCompressedSize: true,

    // Chunk size warning
    chunkSizeWarningLimit: 1000,

    // CSS configuration
    cssCodeSplit: true,

    // Manifest generation
    manifest: true
  },

  // Development server optimizations
  server: {
    port: 3000,
    host: true,
    cors: true,

    // HMR configuration
    hmr: {
      overlay: true,
      port: 3001
    },

    // Proxy configuration
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        secure: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
        configure: (proxy, _options) => {
          proxy.on('error', (err, _req, _res) => {
            console.log('proxy error', err)
          })
          proxy.on('proxyReq', (proxyReq, req, _res) => {
            console.log('Sending Request to the Target:', req.method, req.url)
          })
          proxy.on('proxyRes', (proxyRes, req, _res) => {
            console.log('Received Response from the Target:', proxyRes.statusCode, req.url)
          })
        }
      },

      // WebSocket proxy
      '/socket.io': {
        target: 'ws://localhost:3001',
        ws: true,
      }
    },

    // Watch configuration
    watch: {
      usePolling: false,
      interval: 100,
      ignored: ['**/node_modules/**', '**/.git/**']
    }
  },

  // Preview server
  preview: {
    port: 4173,
    host: true,
    cors: true
  },

  // Dependency optimization
  optimizeDeps: {
    include: [
      'react',
      'react-dom',
      'vue',
      'vue-router',
      'pinia',
      'axios',
      'lodash-es'
    ],
    exclude: [
      '@types/lodash-es',
      'fsevents',
      'electron'
    ],
    entries: [
      'src/main.js',
      'src/admin.js'
    ]
  },

  // CSS configuration
  css: {
    // PostCSS configuration
    postcss: {
      plugins: [
        require('autoprefixer'),
        require('postcss-flexbugs-fixes'),
        require('postcss-preset-env')({
          stage: 3,
          features: {
            'nesting-rules': true,
            'custom-properties': true
          },
          browsers: ['> 1%', 'last 2 versions', 'not ie <= 8']
        })
      ]
    },

    // CSS modules
    modules: {
      localsConvention: 'camelCaseOnly',
      generateScopedName: (name, filename, css) => {
        const hash = require('hash-sum')(css)
        return `${name}__${hash}`
      }
    },

    // Preprocessor configuration
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss"; @import "@/styles/mixins.scss";`,
        sassOptions: {
          includePaths: ['./src/styles'],
          outputStyle: 'compressed'
        }
      },
      less: {
        javascriptEnabled: true,
        modifyVars: {
          'primary-color': '#1890ff',
          'border-radius-base': '6px'
        }
      },
      styl: {
        imports: [
          './src/styles/variables.styl'
        ]
      }
    },

    // Dev sourcemap
    devSourcemap: true
  },

  // Advanced plugins
  plugins: [
    // PWA Plugin
    VitePWA({
      registerType: 'autoUpdate',
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg,jpg,jpeg}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/api\.example\.com\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 100,
                maxAgeSeconds: 60 * 60 * 24 // 24 hours
              }
            }
          }
        ]
      },
      includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
      manifest: {
        name: 'My Awesome App',
        short_name: 'MyApp',
        description: 'A modern web application built with Vite',
        theme_color: '#1890ff',
        background_color: '#ffffff',
        display: 'standalone',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ]
      }
    }),

    // Legacy browser support
    require('@vitejs/plugin-legacy')({
      targets: ['defaults', 'not IE 11'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime']
    }),

    // Compression
    require('vite-plugin-compression')({
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz'
    }),

    // Bundle analyzer (conditional)
    process.env.ANALYZE && visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ].filter(Boolean),

  // Environment configuration
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
    __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
    __GIT_COMMIT__: JSON.stringify(process.env.GIT_COMMIT || 'unknown')
  },

  // Experimental features
  experimental: {
    // Build target support
    buildTarget: undefined, // 'node', 'webworker', etc.

    // Advanced asset handling
    renderBuiltUrl(filename, { hostType, type }) {
      if (type === 'asset') {
        // CDN URLs for assets
        if (process.env.NODE_ENV === 'production') {
          return `https://cdn.example.com/assets/${filename}`
        }
      }
      return { relative: true }
    }
  },

  // Preview configuration
  preview: {
    port: 4173,
    host: true,
    cors: true
  },

  // Clear screen
  clearScreen: false,

  // Environment files
  envPrefix: 'VITE_',

  // Base path
  base: process.env.VITE_BASE_PATH || '/',

  // Public directory
  publicDir: 'public',

  // Cache directory
  cacheDir: 'node_modules/.vite'
})

// 2. Server-Side Rendering (SSR) Configuration
// vite.config.ssr.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  build: {
    target: 'node',
    rollupOptions: {
      input: 'src/entry-server.js',
      output: {
        format: 'es',
        entryFileNames: '[name].js'
      }
    },
    ssr: true
  },
  ssr: {
    // Server-side rendering options
    noExternal: ['@vitejs/plugin-react']
  },
  define: {
    'process.env': process.env
  }
})

// 3. Multi-Application Configuration
// vite.config.multi-app.js
import { defineConfig } from 'vite'
import { resolve } from 'path'
import reactRefresh from '@vitejs/plugin-react-refresh'

const sharedConfig = {
  plugins: [reactRefresh()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@shared': resolve(__dirname, 'shared')
    }
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
}

export default defineConfig([
  // Main app
  {
    ...sharedConfig,
    build: {
      ...sharedConfig.build,
      outDir: 'dist/main',
      rollupOptions: {
        input: resolve(__dirname, 'index.html')
      }
    }
  },

  // Admin app
  {
    ...sharedConfig,
    build: {
      ...sharedConfig.build,
      outDir: 'dist/admin',
      rollupOptions: {
        input: resolve(__dirname, 'admin.html')
      }
    }
  }
])

// 4. Library Mode Configuration
// vite.config.lib.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.js'),
      name: 'MyLibrary',
      fileName: (format) => `my-library.${format}.js`
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    },
    sourcemap: true,
    minify: 'terser'
  }
})

// 5. Advanced Plugin Development
// vite-plugin-performance.js
import { performance } from 'perf_hooks'

export function performancePlugin(options = {}) {
  const {
    enableMetrics = process.env.NODE_ENV === 'development',
    enableMonitoring = false,
    reportInterval = 5000
  } = options

  let startTime
  let metrics = {
    transforms: 0,
    loads: 0,
    resolves: 0,
    totalTransformTime: 0,
    totalLoadTime: 0
  }

  return {
    name: 'vite-plugin-performance',

    buildStart() {
      if (enableMetrics) {
        startTime = performance.now()
        console.log('🚀 Build started')
      }
    },

    transform(code, id) {
      if (!enableMetrics) return null

      const start = performance.now()
      metrics.transforms++

      // Continue with original transform
      return null
    },

    transformEnd() {
      if (!enableMetrics) return

      const end = performance.now()
      const buildTime = end - startTime

      console.log('📊 Build Performance Metrics:')
      console.log(`  Total time: ${buildTime.toFixed(2)}ms`)
      console.log(`  Transforms: ${metrics.transforms}`)
      console.log(`  Loads: ${metrics.loads}`)
      console.log(`  Resolves: ${metrics.resolves}`)
    },

    generateBundle(options, bundle) {
      if (enableMonitoring) {
        const bundleSize = Object.values(bundle)
          .filter(chunk => chunk.type === 'chunk')
          .reduce((size, chunk) => size + chunk.code.length, 0)

        console.log(`📦 Bundle size: ${(bundleSize / 1024).toFixed(2)} KB`)
      }
    }
  }
}

// 6. Custom Asset Handling
// vite-plugin-custom-assets.js
import { createHash } from 'crypto'
import { readFile } from 'fs/promises'

export function customAssetsPlugin(options = {}) {
  const {
    extensions = ['.glb', '.gltf', '.fbx'],
    outputPath = 'assets/3d'
  } = options

  return {
    name: 'vite-plugin-custom-assets',

    async load(id) {
      const ext = id.split('.').pop()

      if (!extensions.includes(`.${ext}`)) {
        return null
      }

      try {
        const buffer = await readFile(id)
        const hash = createHash('md5').update(buffer).digest('hex').slice(0, 8)
        const filename = `${id.split('/').pop().split('.')[0]}-${hash}.${ext}`

        // Emit the asset
        this.emitFile({
          type: 'asset',
          fileName: `${outputPath}/${filename}`,
          source: buffer
        })

        // Return JavaScript module
        return `
export default `${outputPath}/${filename}`
export const url = `${outputPath}/${filename}`
export const hash = '${hash}'
`
      } catch (error) {
        console.error(`Failed to load asset ${id}:`, error)
        return null
      }
    }
  }
}

// 7. Advanced HMR Configuration
// vite-plugin-advanced-hmr.js
export function advancedHmrPlugin() {
  return {
    name: 'vite-plugin-advanced-hmr',

    configureServer(server) {
      // Custom HMR for specific file types
      server.ws.on('connection', (ws) => {
        ws.on('message', (data) => {
          const parsed = JSON.parse(data)

          if (parsed.type === 'custom-reload') {
            // Handle custom reload messages
            server.ws.send({
              type: 'full-reload'
            })
          }
        })
      })

      // Watch additional files
      server.watcher.add(['config/*.json', 'data/*.json'])

      // Custom file watching
      server.watcher.on('change', (file) => {
        if (file.includes('.env')) {
          // Reload environment variables
          server.ws.send({
            type: 'custom',
            event: 'env-updated',
            data: { file }
          })
        }
      })
    }
  }
}

// 8. Progressive Web App (PWA) Configuration
// vite-pwa.config.js
import { VitePWA } from 'vite-plugin-pwa'

export const pwaConfig = VitePWA({
  registerType: 'autoUpdate',
  includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'mask-icon.svg'],
  manifest: {
    name: 'My Progressive Web App',
    short_name: 'MyPWA',
    description: 'A modern PWA built with Vite',
    theme_color: '#0d47a1',
    background_color: '#ffffff',
    display: 'standalone',
    orientation: 'portrait',
    scope: '/',
    start_url: '/',
    icons: [
      {
        src: 'pwa-192x192.png',
        sizes: '192x192',
        type: 'image/png'
      },
      {
        src: 'pwa-512x512.png',
        sizes: '512x512',
        type: 'image/png'
      },
      {
        src: 'pwa-512x512.png',
        sizes: '512x512',
        type: 'image/png',
        purpose: 'any maskable'
      }
    ]
  },
  workbox: {
    globPatterns: ['**/*.{js,css,html,ico,png,svg,jpg,jpeg,json}'],
    runtimeCaching: [
      {
        urlPattern: /^https:\/\/api\.example\.com\/.*/i,
        handler: 'NetworkFirst',
        options: {
          cacheName: 'api-cache',
          expiration: {
            maxEntries: 100,
            maxAgeSeconds: 60 * 60 * 24
          },
          cacheableResponse: {
            statuses: [0, 200]
          }
        }
      },
      {
        urlPattern: /^https:\/\/cdn\.example\.com\/.*/i,
        handler: 'CacheFirst',
        options: {
          cacheName: 'static-cache',
          expiration: {
            maxEntries: 1000,
            maxAgeSeconds: 60 * 60 * 24 * 365
          }
        }
      }
    ]
  },
  devOptions: {
    enabled: true,
    type: 'module'
  }
})

// 9. Deployment Configuration Examples
// deploy.config.js
export const deploymentConfigs = {
  // Vercel deployment
  vercel: {
    server: {
      host: true,
      port: 3000
    },
    build: {
      target: 'es2018',
      outDir: 'dist'
    }
  },

  // Netlify deployment
  netlify: {
    build: {
      outDir: 'dist'
    },
    base: '/',
    plugins: [
      // Redirect rules
      {
        name: 'redirects',
        configureServer(server) {
          server.middlewares.use((req, res, next) => {
            if (req.path.endsWith('/')) {
              const indexPath = path.join(process.cwd(), 'dist', req.path, 'index.html')
              if (fs.existsSync(indexPath)) {
                return res.sendFile(indexPath)
              }
            }
            next()
          })
        }
      }
    ]
  },

  // AWS S3 deployment
  s3: {
    base: '/my-app/',
    build: {
      outDir: 'dist',
      assetsDir: 'assets',
      rollupOptions: {
        output: {
          assetFileNames: 'assets/[name].[hash].[ext]'
        }
      }
    }
  },

  // Docker deployment
  docker: {
    server: {
      host: '0.0.0.0',
      port: 3000
    },
    build: {
      outDir: 'dist'
    }
  }
}

// 10. Performance Monitoring Plugin
// vite-plugin-monitoring.js
export function monitoringPlugin(options = {}) {
  const {
    apiKey = 'your-api-key',
    endpoint = 'https://monitoring.example.com/api/metrics',
    environment = process.env.NODE_ENV || 'development'
  } = options

  return {
    name: 'vite-plugin-monitoring',

    buildStart() {
      const startTime = Date.now()

      return () => {
        const buildTime = Date.now() - startTime
        sendMetrics({
          type: 'build_time',
          value: buildTime,
          environment,
          timestamp: Date.now()
        })
      }
    },

    generateBundle(options, bundle) {
      const metrics = {
        type: 'bundle_analysis',
        environment,
        timestamp: Date.now(),
        data: {
          totalSize: 0,
          chunks: 0,
          assets: 0
        }
      }

      for (const [fileName, chunk] of Object.entries(bundle)) {
        if (chunk.type === 'chunk') {
          metrics.data.totalSize += chunk.code.length
          metrics.data.chunks++
        } else {
          metrics.data.totalSize += chunk.source.length
          metrics.data.assets++
        }
      }

      sendMetrics(metrics)
    }
  }

  async function sendMetrics(metrics) {
    try {
      await fetch(endpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-API-Key': apiKey
        },
        body: JSON.stringify(metrics)
      })
    } catch (error) {
      console.warn('Failed to send metrics:', error)
    }
  }
}