Elysia Tools
Navigation mobile
Outils de build
Exemples Vite
Exemples de l'outil de build Vite - Serveur de développement moderne, HMR, bundling et système de plugins
Exemples
Entrées de cette collection
Fondamentaux et Configuration de Vite
Configuration basique de Vite, mise en place du projet, serveur de développement et fonctionnalités principales
Difficulté
3/10
Temps estimé
20 min
Étiquettes
vite, build, configuration, development
Prérequis
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 { config } from './config'
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, vi } 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 // 媒体查询里的单位是否需要转换单位
}
}
}Développement de Plugins Vite
Création de plugins Vite personnalisés, utilisation des plugins officiels et écosystème de plugins
Difficulté
7/10
Temps estimé
35 min
Étiquettes
vite, plugins, development, api, hooks
Prérequis
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(/</g, '<').replace(/>/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
})
]
}
```
*/Fonctionnalités Avancées et Optimisation de Vite
Optimisation des performances, SSR, bundling avancé, déploiement et considérations de production
Difficulté
9/10
Temps estimé
45 min
Étiquettes
vite, optimization, production, deployment, ssr
Prérequis
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)
}
}
}Outils
Outils souvent utilisés avec cet exemple
Associé