Skip to content

Vite 构建工具

Vite 是一个现代化的前端构建工具,为 Vue.js 项目提供了极快的开发体验和优化的生产构建。

什么是 Vite?

Vite(法语意为"快速")是一个构建工具,具有以下特点:

  • ⚡️ 极速的服务启动
  • 🔥 闪电般的热模块替换 (HMR)
  • 🛠️ 丰富的功能
  • 📦 优化的构建
  • 🔩 通用的插件接口
  • 🔑 完全类型化的 API

快速开始

创建新项目

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

# yarn
yarn create vue my-vue-app

# pnpm
pnpm create vue my-vue-app

# 使用 Vite 模板
npm create vite@latest my-vue-app -- --template vue

项目结构

my-vue-app/
├── public/
│   └── favicon.ico
├── src/
│   ├── assets/
│   ├── components/
│   ├── App.vue
│   └── main.js
├── index.html
├── package.json
├── vite.config.js
└── README.md

基本配置

javascript
// vite.config.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: false,
    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'),
      '@assets': resolve(__dirname, 'src/assets'),
      '@utils': resolve(__dirname, 'src/utils')
    }
  },
  
  // CSS 配置
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  },
  
  // 环境变量
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
  }
})

开发功能

热模块替换 (HMR)

Vite 提供了开箱即用的 HMR 支持:

vue
<!-- 组件会自动支持 HMR -->
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <button @click="count++">Count: {{ count }}</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

defineProps({
  msg: String
})

const count = ref(0)

// HMR API(通常不需要手动使用)
if (import.meta.hot) {
  import.meta.hot.accept()
}
</script>

<style scoped>
.hello {
  color: #42b883;
}
</style>

环境变量

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

// .env.development
VITE_API_BASE_URL=http://localhost:3000/api

// .env.production
VITE_API_BASE_URL=https://prod-api.example.com

在代码中使用:

javascript
// main.js
console.log(import.meta.env.VITE_APP_TITLE)
console.log(import.meta.env.VITE_API_BASE_URL)
console.log(import.meta.env.MODE) // 'development' 或 'production'
console.log(import.meta.env.DEV)  // boolean
console.log(import.meta.env.PROD) // boolean

// 在组件中
export default {
  setup() {
    const apiUrl = import.meta.env.VITE_API_BASE_URL
    
    return {
      apiUrl
    }
  }
}

静态资源处理

vue
<template>
  <div>
    <!-- public 目录下的文件 -->
    <img src="/logo.png" alt="Logo">
    
    <!-- src/assets 目录下的文件 -->
    <img :src="logoUrl" alt="Logo">
    
    <!-- 内联导入 -->
    <img :src="inlineLogo" alt="Logo">
    
    <!-- 显式 URL 导入 -->
    <img :src="explicitUrl" alt="Logo">
  </div>
</template>

<script setup>
// 默认导入(会被处理和优化)
import logoUrl from '@/assets/logo.png'

// 内联导入(转为 base64)
import inlineLogo from '@/assets/logo.png?inline'

// 显式 URL 导入
import explicitUrl from '@/assets/logo.png?url'

// 动态导入
import { ref, onMounted } from 'vue'

const dynamicImage = ref('')

onMounted(async () => {
  const module = await import('@/assets/dynamic.png')
  dynamicImage.value = module.default
})
</script>

插件系统

常用插件

javascript
// vite.config.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'

// ESLint
import eslint from 'vite-plugin-eslint'

export default defineConfig({
  plugins: [
    vue(),
    
    // ESLint 集成
    eslint({
      include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.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'
          }
        ]
      }
    })
  ]
})

自定义插件

javascript
// plugins/virtual-module.js
export function virtualModulePlugin() {
  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!"`
      }
    }
  }
}

// 使用插件
import { virtualModulePlugin } from './plugins/virtual-module'

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

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

构建优化

代码分割

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

// 或者使用函数形式
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // 将 node_modules 中的包分组
          if (id.includes('node_modules')) {
            if (id.includes('vue')) {
              return 'vue'
            }
            if (id.includes('element-plus')) {
              return 'ui'
            }
            return 'vendor'
          }
        }
      }
    }
  }
})

动态导入

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

// 组件懒加载
export default {
  components: {
    AsyncComponent: () => import('@/components/AsyncComponent.vue')
  }
}

// 条件导入
async function loadFeature() {
  if (someCondition) {
    const { feature } = await import('@/features/advanced')
    return feature
  }
}

预加载和预获取

javascript
// 预加载关键资源
import { preloadModule } from 'vite/dynamic-import-polyfill'

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

// 在路由守卫中预加载
router.beforeEach((to, from, next) => {
  // 预加载目标路由组件
  if (to.matched.some(record => record.meta.preload)) {
    import(to.matched[0].component)
  }
  next()
})

多环境配置

环境特定配置

javascript
// vite.config.js
import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ command, mode }) => {
  // 加载环境变量
  const env = loadEnv(mode, process.cwd(), '')
  
  return {
    plugins: [vue()],
    
    // 根据环境调整配置
    server: {
      port: env.VITE_PORT || 3000
    },
    
    build: {
      // 开发环境生成 sourcemap
      sourcemap: command === 'serve',
      
      // 生产环境压缩
      minify: mode === 'production' ? 'terser' : false
    },
    
    define: {
      __APP_ENV__: JSON.stringify(mode),
      __API_URL__: JSON.stringify(env.VITE_API_URL)
    }
  }
})

多个配置文件

javascript
// vite.config.base.js
export const baseConfig = {
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
}

// vite.config.dev.js
import { defineConfig } from 'vite'
import { baseConfig } from './vite.config.base'

export default defineConfig({
  ...baseConfig,
  server: {
    port: 3000,
    open: true
  }
})

// vite.config.prod.js
import { defineConfig } from 'vite'
import { baseConfig } from './vite.config.base'

export default defineConfig({
  ...baseConfig,
  build: {
    minify: 'terser',
    rollupOptions: {
      // 生产环境特定配置
    }
  }
})

性能优化

依赖预构建

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

构建分析

javascript
// 安装分析插件
npm install --save-dev rollup-plugin-visualizer

// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    vue(),
    
    // 构建分析
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ]
})

缓存策略

javascript
// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 文件名包含 hash,利用浏览器缓存
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name.split('.')
          const ext = info[info.length - 1]
          
          if (/\.(png|jpe?g|gif|svg|webp)$/i.test(assetInfo.name)) {
            return `images/[name]-[hash].${ext}`
          }
          
          if (/\.(css)$/i.test(assetInfo.name)) {
            return `css/[name]-[hash].${ext}`
          }
          
          return `assets/[name]-[hash].${ext}`
        }
      }
    }
  }
})

调试和开发工具

Source Maps

javascript
// vite.config.js
export default defineConfig({
  build: {
    sourcemap: true, // 或 'inline', 'hidden'
  },
  
  css: {
    devSourcemap: true // CSS source maps
  }
})

开发服务器配置

javascript
// vite.config.js
export default defineConfig({
  server: {
    // 自定义端口
    port: 3000,
    
    // 自动打开浏览器
    open: true,
    
    // 启用 HTTPS
    https: true,
    
    // 代理配置
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
      
      // WebSocket 代理
      '/ws': {
        target: 'ws://localhost:8080',
        ws: true
      }
    },
    
    // 自定义中间件
    middlewareMode: false,
    
    // 文件监听配置
    watch: {
      usePolling: true,
      interval: 1000
    }
  }
})

部署

静态部署

bash
# 构建
npm run build

# 预览构建结果
npm run preview

Nginx 配置

nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/dist;
    index index.html;
    
    # 处理 SPA 路由
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Gzip 压缩
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

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;"]

故障排除

常见问题

javascript
// 1. 依赖预构建问题
// 删除 node_modules/.vite 目录
rm -rf node_modules/.vite

// 2. 端口冲突
// vite.config.js
export default defineConfig({
  server: {
    port: 3001 // 更改端口
  }
})

// 3. 路径别名不生效
// 确保 jsconfig.json 或 tsconfig.json 配置正确
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

// 4. 环境变量不生效
// 确保变量名以 VITE_ 开头
VITE_API_URL=http://localhost:3000

// 5. 热更新不工作
// 检查文件监听配置
export default defineConfig({
  server: {
    watch: {
      usePolling: true
    }
  }
})

调试配置

javascript
// vite.config.js
export default defineConfig({
  // 启用详细日志
  logLevel: 'info',
  
  // 清除控制台
  clearScreen: false,
  
  // 自定义日志
  customLogger: {
    info(msg) {
      console.log(`[INFO] ${msg}`)
    },
    warn(msg) {
      console.warn(`[WARN] ${msg}`)
    },
    error(msg) {
      console.error(`[ERROR] ${msg}`)
    }
  }
})

最佳实践

1. 项目结构

src/
├── assets/          # 静态资源
├── components/      # 公共组件
├── composables/     # 组合式函数
├── layouts/         # 布局组件
├── pages/           # 页面组件
├── router/          # 路由配置
├── stores/          # 状态管理
├── styles/          # 全局样式
├── utils/           # 工具函数
├── App.vue
└── main.js

2. 配置管理

javascript
// 使用配置工厂函数
export function createViteConfig(options = {}) {
  const { mode = 'development', plugins = [] } = options
  
  return defineConfig({
    plugins: [vue(), ...plugins],
    
    server: {
      port: mode === 'development' ? 3000 : 4000
    },
    
    build: {
      sourcemap: mode === 'development'
    }
  })
}

3. 性能监控

javascript
// 添加构建时间监控
const startTime = Date.now()

export default defineConfig({
  plugins: [
    vue(),
    {
      name: 'build-time',
      buildEnd() {
        console.log(`Build completed in ${Date.now() - startTime}ms`)
      }
    }
  ]
})

下一步

Vite 为 Vue 开发提供了现代化的工具链,掌握它将大大提升你的开发效率!

基于 Vue.js 官方文档构建的学习宝典