Skip to content

Vite

Vite 是一个现代化的前端构建工具,为 Vue.js 项目提供了极快的开发体验。它利用了 ES 模块的原生支持和现代浏览器的能力,在开发环境中提供了近乎即时的热更新。

为什么选择 Vite?

  • 极快的冷启动:无需打包,直接启动开发服务器
  • 即时热更新:基于 ES 模块的 HMR,更新速度不受应用大小影响
  • 丰富的功能:开箱即用的 TypeScript、JSX、CSS 预处理器支持
  • 优化的构建:基于 Rollup 的生产构建,支持多种优化策略
  • 插件生态:丰富的插件系统,易于扩展
  • 框架无关:虽然为 Vue 优化,但也支持 React、Svelte 等

快速开始

创建新项目

bash
# 使用 npm
npm create vue@latest my-vue-app

# 使用 yarn
yarn create vue my-vue-app

# 使用 pnpm
pnpm create vue my-vue-app

手动安装

bash
npm install -D vite @vitejs/plugin-vue

基本配置

vite.config.js

js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  
  // 开发服务器配置
  server: {
    port: 3000,
    open: true,
    cors: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  
  // 构建配置
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: true,
    minify: 'terser',
    rollupOptions: {
      output: {
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]'
      }
    }
  },
  
  // 路径别名
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@components': resolve(__dirname, 'src/components'),
      '@utils': resolve(__dirname, 'src/utils'),
      '@assets': resolve(__dirname, 'src/assets')
    }
  },
  
  // CSS 配置
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    },
    modules: {
      localsConvention: 'camelCase'
    }
  },
  
  // 环境变量
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
  }
})

环境变量

.env 文件

bash
# .env
VITE_APP_TITLE=My Vue App
VITE_API_BASE_URL=https://api.example.com

# .env.development
VITE_API_BASE_URL=http://localhost:8080
VITE_DEBUG=true

# .env.production
VITE_API_BASE_URL=https://api.production.com
VITE_DEBUG=false

在代码中使用

js
// 在 Vue 组件或 JS 文件中
console.log(import.meta.env.VITE_APP_TITLE)
console.log(import.meta.env.VITE_API_BASE_URL)

// 类型定义 (TypeScript)
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_API_BASE_URL: string
  readonly VITE_DEBUG: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

插件系统

常用插件

js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// 自动导入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// PWA
import { VitePWA } from 'vite-plugin-pwa'

// 图标
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'

// 压缩
import { compression } from 'vite-plugin-compression2'

// 分析
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    vue(),
    
    // 自动导入 Vue API
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia'],
      resolvers: [
        ElementPlusResolver(),
        IconsResolver({
          prefix: 'Icon',
        }),
      ],
      dts: true, // 生成类型定义文件
    }),
    
    // 自动导入组件
    Components({
      resolvers: [
        ElementPlusResolver(),
        IconsResolver({
          enabledCollections: ['ep'],
        }),
      ],
      dts: true,
    }),
    
    // 图标
    Icons({
      autoInstall: true,
    }),
    
    // PWA
    VitePWA({
      registerType: 'autoUpdate',
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg}']
      },
      manifest: {
        name: 'My Vue App',
        short_name: 'VueApp',
        description: 'My Awesome Vue App',
        theme_color: '#ffffff',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          }
        ]
      }
    }),
    
    // Gzip 压缩
    compression({
      algorithm: 'gzip'
    }),
    
    // 构建分析
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true
    })
  ]
})

自定义插件

js
// plugins/virtual-module.js
export function virtualModule() {
  const virtualModuleId = 'virtual:my-module'
  const resolvedVirtualModuleId = '\0' + virtualModuleId

  return {
    name: 'virtual-module',
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return `export const msg = "Hello from virtual module!"`
      }
    }
  }
}

// vite.config.js
import { virtualModule } from './plugins/virtual-module'

export default defineConfig({
  plugins: [
    vue(),
    virtualModule()
  ]
})

// 在代码中使用
import { msg } from 'virtual:my-module'
console.log(msg) // "Hello from virtual module!"

开发优化

热更新配置

js
// vite.config.js
export default defineConfig({
  server: {
    hmr: {
      overlay: true, // 显示错误覆盖层
    }
  },
  
  plugins: [
    vue({
      // 启用响应式语法糖
      reactivityTransform: true,
      
      // 自定义块支持
      include: [/\.vue$/, /\.md$/]
    })
  ]
})

预构建配置

js
export default defineConfig({
  optimizeDeps: {
    // 强制预构建
    include: ['lodash-es', 'axios'],
    
    // 排除预构建
    exclude: ['your-local-package'],
    
    // 自定义 esbuild 选项
    esbuildOptions: {
      target: 'es2020'
    }
  }
})

构建优化

代码分割

js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 将 Vue 相关库打包到一个 chunk
          vue: ['vue', 'vue-router', 'pinia'],
          
          // 将 UI 库单独打包
          ui: ['element-plus'],
          
          // 将工具库单独打包
          utils: ['lodash-es', 'axios', 'dayjs']
        }
      }
    }
  }
})

// 或者使用函数形式
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // 将 node_modules 中的包单独打包
          if (id.includes('node_modules')) {
            return 'vendor'
          }
          
          // 将组件库单独打包
          if (id.includes('/src/components/')) {
            return 'components'
          }
        }
      }
    }
  }
})

资源优化

js
export default defineConfig({
  build: {
    // 资源内联阈值
    assetsInlineLimit: 4096,
    
    // CSS 代码分割
    cssCodeSplit: true,
    
    // 生成 manifest
    manifest: true,
    
    // Terser 压缩选项
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  }
})

TypeScript 支持

tsconfig.json

json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
  ],
  "references": [
    { "path": "./tsconfig.node.json" }
  ]
}

Vue 组件类型支持

vue
<script setup lang="ts">
import { ref, computed, type Ref } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

const users: Ref<User[]> = ref([])
const selectedUserId = ref<number | null>(null)

const selectedUser = computed(() => 
  users.value.find(user => user.id === selectedUserId.value)
)

// 定义组件 props
interface Props {
  title: string
  count?: number
  users: User[]
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
})

// 定义组件 emits
interface Emits {
  (e: 'update:count', value: number): void
  (e: 'select-user', user: User): void
}

const emit = defineEmits<Emits>()

function handleUserSelect(user: User) {
  emit('select-user', user)
}
</script>

测试集成

Vitest 配置

js
// vitest.config.js
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts']
  },
  
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
})

// src/test/setup.ts
import { config } from '@vue/test-utils'
import { createPinia } from 'pinia'

// 全局测试配置
config.global.plugins = [createPinia()]

测试示例

js
// src/components/__tests__/Counter.test.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from '../Counter.vue'

describe('Counter', () => {
  it('renders properly', () => {
    const wrapper = mount(Counter, {
      props: { initialCount: 4 }
    })
    
    expect(wrapper.text()).toContain('4')
  })
  
  it('increments count when button is clicked', async () => {
    const wrapper = mount(Counter)
    
    await wrapper.find('button').trigger('click')
    
    expect(wrapper.text()).toContain('1')
  })
})

部署配置

静态部署

js
// vite.config.js
export default defineConfig({
  base: '/my-app/', // 如果部署在子路径
  
  build: {
    outDir: 'dist',
    
    // 生成相对路径
    assetsDir: 'assets',
    
    rollupOptions: {
      output: {
        // 自定义文件名
        entryFileNames: 'js/[name].[hash].js',
        chunkFileNames: 'js/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]'
      }
    }
  }
})

Docker 部署

dockerfile
# Dockerfile
FROM node:18-alpine as build-stage

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx
# nginx.conf
server {
    listen 80;
    server_name localhost;
    
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    
    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # API 代理
    location /api/ {
        proxy_pass http://backend:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

性能优化

懒加载

js
// 路由懒加载
const routes = [
  {
    path: '/home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    component: () => import('@/views/About.vue')
  }
]

// 组件懒加载
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent(() => 
  import('@/components/HeavyComponent.vue')
)

预加载

js
// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'critical': ['./src/components/Critical.vue'],
          'non-critical': ['./src/components/NonCritical.vue']
        }
      }
    }
  }
})

// 在组件中预加载
import { onMounted } from 'vue'

onMounted(() => {
  // 预加载下一个可能访问的页面
  import('@/views/NextPage.vue')
})

构建分析

bash
# 安装分析工具
npm install -D rollup-plugin-visualizer

# 构建并生成分析报告
npm run build

# 查看分析报告
open dist/stats.html

常见问题

1. 导入路径问题

js
// ❌ 错误:相对路径过长
import Component from '../../../components/Component.vue'

// ✅ 正确:使用别名
import Component from '@/components/Component.vue'

2. 环境变量不生效

js
// ❌ 错误:没有 VITE_ 前缀
VUE_APP_API_URL=http://localhost:8080

// ✅ 正确:使用 VITE_ 前缀
VITE_API_URL=http://localhost:8080

3. 动态导入问题

js
// ❌ 错误:完全动态的导入
const module = await import(moduleName)

// ✅ 正确:部分静态的导入
const module = await import(`./modules/${moduleName}.js`)

最佳实践

1. 项目结构

src/
├── assets/          # 静态资源
├── components/      # 公共组件
├── composables/     # 组合式函数
├── layouts/         # 布局组件
├── pages/           # 页面组件
├── plugins/         # 插件
├── router/          # 路由配置
├── stores/          # 状态管理
├── styles/          # 样式文件
├── utils/           # 工具函数
└── types/           # 类型定义

2. 配置管理

js
// config/index.js
const config = {
  development: {
    apiUrl: 'http://localhost:8080',
    debug: true
  },
  production: {
    apiUrl: 'https://api.production.com',
    debug: false
  }
}

export default config[import.meta.env.MODE]

3. 性能监控

js
// utils/performance.js
export function measurePerformance(name, fn) {
  return async (...args) => {
    const start = performance.now()
    const result = await fn(...args)
    const end = performance.now()
    
    console.log(`${name} took ${end - start} milliseconds`)
    return result
  }
}

// 使用
const fetchData = measurePerformance('fetchData', async () => {
  const response = await fetch('/api/data')
  return response.json()
})

下一步

vue study guide - 专业的 Vue.js 学习平台