Skip to content

Vite 深度解析

什么是 Vite?

官方定义

Vite 是新一代前端构建工具,利用浏览器原生 ES Modules 特性,实现了极速的开发服务器启动lightning-fast的热更新(HMR)

通俗理解

传统构建工具(Webpack):

  • 像是批发市场,要把所有商品打包好才能开门营业
  • 启动慢,改一行代码要重新打包

Vite:

  • 像是便利店,商品直接上架,顾客要什么拿什么
  • 秒启动,改代码瞬间生效

Vite vs Webpack

特性WebpackVite
启动速度慢(需要打包)快(无需打包)
热更新秒级毫秒级
生产构建自己打包Rollup打包
配置复杂度复杂简单
生态成熟快速发展
学习成本

核心原理

1. 开发阶段 - ESM

javascript
// 传统方式(Webpack)
// 1. 分析所有依赖
// 2. 打包成bundle.js
// 3. 启动服务器
// 4. 浏览器加载bundle.js

// Vite方式
// 1. 直接启动服务器(秒启动)
// 2. 浏览器请求哪个模块,服务器就编译哪个
// 3. 利用浏览器原生ESM

// index.html
<script type="module" src="/src/main.js"></script>

// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

// 浏览器直接请求:
// /src/main.js
// /src/App.vue
// /node_modules/vue/dist/vue.runtime.esm-bundler.js

// Vite拦截这些请求,实时编译返回

2. 依赖预构建

javascript
// 为什么需要预构建?
/*
1. CommonJS转ESM
   很多npm包是CommonJS格式,需要转换

2. 减少HTTP请求
   lodash-es有600+个模块,预构建打包成一个文件

3. 缓存优化
   node_modules很少变化,强缓存提升性能
*/

// 配置预构建
export default {
  optimizeDeps: {
    // 包含的依赖
    include: ['vue', 'vue-router', 'axios'],

    // 排除的依赖
    exclude: ['@vueuse/core'],

    // 强制预构建
    force: true,

    // esbuild选项
    esbuildOptions: {
      define: {
        global: 'globalThis'
      }
    }
  }
}

3. 热更新(HMR)原理

javascript
// Vite HMR原理
/*
1. 文件变化时,只重新请求该模块
2. 通过WebSocket通知浏览器
3. 浏览器接收更新,局部替换
4. 整个过程毫秒级完成
*/

// HMR API使用
if (import.meta.hot) {
  // 接受自身更新
  import.meta.hot.accept((newModule) => {
    console.log('模块更新:', newModule)
  })

  // 接受依赖更新
  import.meta.hot.accept('./dep.js', (newDep) => {
    // 处理依赖更新
  })

  // 销毁回调
  import.meta.hot.dispose((data) => {
    // 清理副作用
    data.cleanup = () => {
      // 清理逻辑
    }
  })

  // 自定义事件
  import.meta.hot.on('custom-event', (data) => {
    console.log('自定义事件:', data)
  })

  // 完全重载
  import.meta.hot.invalidate()
}

// Vue HMR实现
// vite-plugin-vue 自动处理
<template>
  <div>{{ count }}</div>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

// 修改模板或脚本,页面瞬间更新,状态保持

完整配置详解

基础配置

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

export default defineConfig({
  // 1. 插件
  plugins: [vue()],

  // 2. 路径别名
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      'components': resolve(__dirname, 'src/components'),
      'utils': resolve(__dirname, 'src/utils')
    },
    extensions: ['.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  },

  // 3. 服务器配置
  server: {
    port: 3000,
    open: true,
    cors: true,

    // 代理
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },

      '/ws': {
        target: 'ws://localhost:8080',
        ws: true
      }
    },

    // HMR配置
    hmr: {
      overlay: true,
      port: 3000
    }
  },

  // 4. 构建配置
  build: {
    // 输出目录
    outDir: 'dist',

    // 静态资源目录
    assetsDir: 'assets',

    // 小于此阈值的资源转base64
    assetsInlineLimit: 4096,

    // CSS代码分割
    cssCodeSplit: true,

    // Source Map
    sourcemap: false,

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

      output: {
        // chunk文件名
        chunkFileNames: 'js/[name]-[hash].js',

        // 入口文件名
        entryFileNames: 'js/[name]-[hash].js',

        // 静态资源文件名
        assetFileNames: '[ext]/[name]-[hash].[ext]',

        // 代码分割
        manualChunks: {
          vue: ['vue', 'vue-router', 'pinia'],
          ui: ['element-plus'],
          utils: ['axios', 'dayjs']
        }
      }
    },

    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },

    // chunk大小警告限制
    chunkSizeWarningLimit: 1000
  },

  // 5. CSS配置
  css: {
    // CSS预处理器
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      },
      less: {
        modifyVars: {
          'primary-color': '#1890ff'
        },
        javascriptEnabled: true
      }
    },

    // PostCSS配置
    postcss: {
      plugins: [
        require('autoprefixer'),
        require('postcss-px-to-viewport')({
          viewportWidth: 375
        })
      ]
    }
  },

  // 6. 环境变量
  define: {
    __APP_VERSION__: JSON.stringify('1.0.0')
  },

  // 7. 预构建优化
  optimizeDeps: {
    include: ['vue', 'vue-router', 'pinia'],
    exclude: ['some-large-package']
  }
})

多环境配置

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: Number(env.VITE_PORT) || 3000
    },

    build: {
      sourcemap: mode === 'development'
    },

    define: {
      __API_URL__: JSON.stringify(env.VITE_API_URL)
    }
  }
})

// .env.development
VITE_PORT=3000
VITE_API_URL=http://localhost:8080/api

// .env.production
VITE_PORT=8080
VITE_API_URL=https://api.example.com

// 使用环境变量
console.log(import.meta.env.VITE_API_URL)
console.log(import.meta.env.MODE)  // 'development' | 'production'
console.log(import.meta.env.DEV)   // boolean
console.log(import.meta.env.PROD)  // boolean

常用插件

1. Vue 相关

javascript
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
  plugins: [
    vue(),

    // JSX支持
    vueJsx(),

    // 自动导入API
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia'],
      resolvers: [ElementPlusResolver()]
    }),

    // 自动导入组件
    Components({
      resolvers: [ElementPlusResolver()],
      dirs: ['src/components'],
      extensions: ['vue'],
      deep: true
    })
  ]
}

2. 性能优化插件

javascript
import viteCompression from 'vite-plugin-compression'
import { visualizer } from 'rollup-plugin-visualizer'
import viteImagemin from 'vite-plugin-imagemin'

export default {
  plugins: [
    // Gzip压缩
    viteCompression({
      algorithm: 'gzip',
      ext: '.gz',
      threshold: 10240,
      deleteOriginFile: false
    }),

    // Brotli压缩
    viteCompression({
      algorithm: 'brotliCompress',
      ext: '.br'
    }),

    // 打包分析
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true
    }),

    // 图片压缩
    viteImagemin({
      gifsicle: { optimizationLevel: 7 },
      optipng: { optimizationLevel: 7 },
      mozjpeg: { quality: 80 },
      pngquant: { quality: [0.8, 0.9] },
      svgo: {
        plugins: [
          { name: 'removeViewBox' },
          { name: 'removeEmptyAttrs', active: false }
        ]
      }
    })
  ]
}

3. 其他实用插件

javascript
import legacy from '@vitejs/plugin-legacy'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import mockDevServerPlugin from 'vite-plugin-mock-dev-server'

export default {
  plugins: [
    // 浏览器兼容
    legacy({
      targets: ['defaults', 'not IE 11']
    }),

    // SVG图标
    createSvgIconsPlugin({
      iconDirs: [resolve(process.cwd(), 'src/icons')],
      symbolId: 'icon-[dir]-[name]'
    }),

    // Mock服务
    mockDevServerPlugin()
  ]
}

性能优化实战

1. 首屏优化

javascript
export default {
  build: {
    rollupOptions: {
      output: {
        // 分包策略
        manualChunks(id) {
          // node_modules分包
          if (id.includes('node_modules')) {
            // 将大型库单独打包
            if (id.includes('element-plus')) {
              return 'element-plus'
            }
            if (id.includes('echarts')) {
              return 'echarts'
            }
            // 其他第三方库
            return 'vendor'
          }

          // 业务代码分包
          if (id.includes('src/views')) {
            const path = id.split('src/views/')[1]
            const moduleName = path.split('/')[0]
            return `views-${moduleName}`
          }
        }
      }
    }
  }
}

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

// 组件懒加载
const HeavyComponent = defineAsyncComponent(() =>
  import('./components/HeavyComponent.vue')
)

2. 构建优化

javascript
export default {
  build: {
    // 目标浏览器
    target: 'es2015',

    // CSS代码分割
    cssCodeSplit: true,

    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log']
      },
      format: {
        comments: false
      }
    },

    // 关闭brotli可节省构建时间
    reportCompressedSize: false,

    // chunk大小警告
    chunkSizeWarningLimit: 1000
  },

  // esbuild优化
  esbuild: {
    pure: ['console.log'],
    drop: ['debugger'],
    legalComments: 'none'
  }
}

3. 预加载和预连接

html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <!-- DNS预解析 -->
  <link rel="dns-prefetch" href="https://api.example.com">

  <!-- 预连接 -->
  <link rel="preconnect" href="https://cdn.example.com">

  <!-- 预加载关键资源 -->
  <link rel="preload" href="/logo.png" as="image">
  <link rel="preload" href="/font.woff2" as="font" crossorigin>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>
</html>

从 Webpack 迁移到 Vite

迁移步骤

javascript
// 1. 安装Vite
npm install -D vite @vitejs/plugin-vue

// 2. 修改package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

// 3. 创建vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

// 4. 修改index.html
// Webpack:
<script src="/src/main.js"></script>

// Vite:
<script type="module" src="/src/main.js"></script>

// 5. 环境变量改名
// Webpack: process.env.VUE_APP_API_URL
// Vite: import.meta.env.VITE_API_URL

// 6. 静态资源处理
// Webpack: require('@/assets/logo.png')
// Vite: new URL('@/assets/logo.png', import.meta.url).href
// 或者: import logo from '@/assets/logo.png'

// 7. CommonJS转ESM
// Webpack: module.exports = {}
// Vite: export default {}

常见问题

javascript
// 问题1: require is not defined
// 解决: 改用import
// ❌
const logo = require('@/assets/logo.png')

// ✅
import logo from '@/assets/logo.png'

// 问题2: process is not defined
// 解决: 使用import.meta.env
// ❌
console.log(process.env.NODE_ENV)

// ✅
console.log(import.meta.env.MODE)

// 问题3: __dirname is not defined
// 解决: 使用import.meta.url
// ❌
const dir = __dirname

// ✅
import { fileURLToPath } from 'url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))

// 问题4: 动态import不work
// 解决: 使用Glob导入
// ❌
const modules = require.context('./modules', true, /\.js$/)

// ✅
const modules = import.meta.glob('./modules/**/*.js')
// 或立即导入
const modules = import.meta.glob('./modules/**/*.js', { eager: true })

手写简易Vite

javascript
// mini-vite.js
const fs = require('fs')
const path = require('path')
const Koa = require('koa')
const { transformAsync } = require('@babel/core')

class MiniVite {
  constructor() {
    this.app = new Koa()
    this.setupMiddleware()
  }

  setupMiddleware() {
    // 处理ESM模块
    this.app.use(async (ctx) => {
      const { url } = ctx.request

      // 1. 处理根路径
      if (url === '/') {
        ctx.type = 'text/html'
        ctx.body = fs.readFileSync(path.join(process.cwd(), 'index.html'), 'utf-8')
        return
      }

      // 2. 处理JS/Vue文件
      if (url.endsWith('.js') || url.endsWith('.vue')) {
        const filepath = path.join(process.cwd(), url)
        let content = fs.readFileSync(filepath, 'utf-8')

        // 3. 处理node_modules导入
        // import vue from 'vue' => import vue from '/@modules/vue'
        content = content.replace(
          /from ['"]([^'"]+)['"]/g,
          (match, p1) => {
            if (!p1.startsWith('./') && !p1.startsWith('/')) {
              return `from '/@modules/${p1}'`
            }
            return match
          }
        )

        // 4. 编译Vue单文件组件
        if (url.endsWith('.vue')) {
          content = this.compileVue(content)
        }

        // 5. 转换ES6
        const result = await transformAsync(content, {
          presets: ['@babel/preset-env'],
          filename: filepath
        })

        ctx.type = 'application/javascript'
        ctx.body = result.code
        return
      }

      // 6. 处理node_modules
      if (url.startsWith('/@modules/')) {
        const moduleName = url.replace('/@modules/', '')
        const modulePath = path.join(
          process.cwd(),
          'node_modules',
          moduleName,
          'package.json'
        )
        const pkg = require(modulePath)
        const entryFile = pkg.module || pkg.main
        const content = fs.readFileSync(
          path.join(path.dirname(modulePath), entryFile),
          'utf-8'
        )

        ctx.type = 'application/javascript'
        ctx.body = content
      }
    })
  }

  compileVue(content) {
    // 简化的Vue SFC编译
    const template = content.match(/<template>([\s\S]*?)<\/template>/)[1]
    const script = content.match(/<script>([\s\S]*?)<\/script>/)[1]

    return `
      ${script.replace('export default', 'const __script =')}
      __script.template = \`${template}\`
      export default __script
    `
  }

  listen(port) {
    this.app.listen(port, () => {
      console.log(`Server running at http://localhost:${port}`)
    })
  }
}

// 使用
const vite = new MiniVite()
vite.listen(3000)

常见面试题

1. Vite为什么这么快?

点击查看答案

核心原因:

  1. 开发环境不打包

    • Webpack: 启动前打包所有模块
    • Vite: 直接启动服务器,按需编译
  2. 利用浏览器ESM

    • 现代浏览器原生支持ESM
    • 不需要打包就能运行
  3. esbuild预构建

    • 使用Go编写的esbuild
    • 比JavaScript快10-100倍
  4. 智能缓存

    • 强缓存node_modules
    • 协商缓存业务代码
  5. HMR更快

    • 只更新变化的模块
    • 不需要重新打包

性能对比:

  • Webpack: 启动10-30秒
  • Vite: 启动1秒内
  • HMR: Vite是Webpack的10倍快

2. Vite的缺点是什么?

点击查看答案

主要缺点:

  1. 生态不如Webpack成熟

    • 插件数量较少
    • 部分Webpack插件无法直接使用
  2. 生产构建用Rollup

    • 开发和生产环境不一致
    • 可能出现开发正常生产报错
  3. 首次启动较慢(相对)

    • 需要预构建依赖
    • 首次访问需要编译
  4. 浏览器兼容性

    • 开发环境需要支持ESM的浏览器
    • 不支持IE(可以用@vitejs/plugin-legacy)
  5. 大型项目问题

    • 模块过多时,开发环境可能卡顿
    • HTTP请求数量多

解决方案:

  • 使用依赖预构建缓存
  • 合理配置optimizeDeps
  • 对于IE用户使用legacy插件

3. 如何在Vite中处理环境变量?

点击查看答案

完整方案:

javascript
// 1. 创建环境变量文件
// .env.development
VITE_APP_TITLE=My App Dev
VITE_API_URL=http://localhost:8080
VITE_ENABLE_MOCK=true

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

// 2. 使用环境变量
console.log(import.meta.env.VITE_APP_TITLE)
console.log(import.meta.env.VITE_API_URL)

// 3. 内置变量
import.meta.env.MODE        // 'development' | 'production'
import.meta.env.BASE_URL    // 公共基础路径
import.meta.env.PROD        // boolean
import.meta.env.DEV         // boolean
import.meta.env.SSR         // boolean

// 4. TypeScript类型支持
// env.d.ts
interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_API_URL: string
  readonly VITE_ENABLE_MOCK: 'true' | 'false'
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

// 5. 配置文件中使用
// vite.config.js
import { loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())

  return {
    define: {
      __APP_VERSION__: JSON.stringify('1.0.0'),
      __API_URL__: JSON.stringify(env.VITE_API_URL)
    }
  }
})

注意事项:

  • 必须以VITE_开头
  • 不要存储敏感信息
  • 重启开发服务器生效

总结

核心要点

  1. 极速启动: 利用ESM,无需打包
  2. 智能预构建: esbuild处理依赖
  3. HMR超快: 只更新变化模块
  4. 简单配置: 开箱即用
  5. Rollup构建: 生产环境优化

面试加分项

  • 能解释Vite快的原理
  • 了解ESM和预构建机制
  • 掌握常见配置和插件
  • 有实际项目迁移经验
  • 知道如何优化Vite项目

最佳实践

  • 合理配置依赖预构建
  • 使用环境变量管理配置
  • 善用插件生态
  • 注意开发生产环境一致性
  • 大型项目注意性能优化

高频面试题

1. Vite 为什么比 Webpack 快?

一句话答案

Vite 利用浏览器原生 ESM 和 esbuild 预构建,开发时无需打包,按需编译,而 Webpack 需要打包所有模块才能启动。

详细解答

核心原因有 5 点:

1. 开发环境无需打包

javascript
// Webpack 工作流程
启动 → 分析所有依赖 → 打包所有模块 → 生成 bundle → 启动服务器 (10-30秒)

// Vite 工作流程
启动 → 直接启动服务器 → 按需编译请求的模块 (秒启动)

// 示例对比
// Webpack: 有1000个模块,启动时需要打包所有1000个
// Vite: 有1000个模块,只编译浏览器请求的几个

2. 利用浏览器原生 ESM

html
<!-- Vite 直接利用浏览器的 ESM 能力 -->
<script type="module">
  // 浏览器原生支持,直接请求
  import { createApp } from 'vue'
  import App from './App.vue'
</script>

<!-- 浏览器会发起 HTTP 请求 -->
GET /node_modules/vue/dist/vue.runtime.esm-bundler.js
GET /src/App.vue

<!-- Vite 拦截请求,实时编译返回 -->

3. esbuild 预构建依赖

javascript
// esbuild 是用 Go 编写的,比 JavaScript 快 10-100 倍
// vite.config.js
export default {
  optimizeDeps: {
    // esbuild 将 CommonJS 依赖转为 ESM
    include: ['vue', 'vue-router', 'axios']
  }
}

// 性能对比
// Babel/Webpack: 10秒
// esbuild: 0.1秒

4. 智能缓存策略

javascript
// 依赖预构建缓存 (强缓存)
node_modules/.vite/
  deps/
    vue.js          // Cache-Control: max-age=31536000
    vue-router.js   // 一年不过期

// 源码模块缓存 (协商缓存)
src/
  App.vue          // 304 Not Modified
  main.js          // 304 Not Modified

5. HMR 更新更快

javascript
// Webpack HMR
文件改变 → 重新打包相关模块 → 推送更新 (秒级)

// Vite HMR
文件改变 → 只编译该模块 → WebSocket 推送 → 浏览器精确替换 (毫秒级)

// 示例
// 修改 Button.vue
// Webpack: 可能重新打包整个 chunk (500ms-2s)
// Vite: 只编译 Button.vue (10-50ms)

性能数据对比:

javascript
// 项目规模: 500个模块
                 冷启动      热启动      HMR
Webpack          25s        15s        800ms
Vite             1.2s       0.5s       50ms

// 项目规模: 5000个模块
Webpack          120s       60s        2s
Vite             3s         1s         80ms

底层原理:

javascript
// Vite 开发服务器核心逻辑
class ViteDevServer {
  async handleRequest(url) {
    // 1. 拦截浏览器请求
    if (url.endsWith('.vue')) {
      // 2. 读取文件
      const content = await fs.readFile(url)

      // 3. 实时编译 (esbuild)
      const compiled = await esbuild.transform(content)

      // 4. 返回编译结果
      return compiled
    }
  }
}

// 整个过程不需要打包,所以极快

面试口语化回答

面试官你好,Vite 比 Webpack 快主要是因为它改变了开发模式。

首先,Vite 在开发环境不需要打包。Webpack 启动时要分析所有依赖,打包所有模块,可能需要十几秒甚至几十秒。而 Vite 直接启动服务器,浏览器请求哪个模块就编译哪个,秒级启动。

第二,Vite 利用了浏览器原生的 ESM 能力。现代浏览器已经支持 import 语法,可以直接加载模块,不需要打包成一个大文件。Vite 就是利用这个特性,让浏览器自己去加载模块。

第三,Vite 用 esbuild 做依赖预构建。esbuild 是用 Go 写的,比 JavaScript 快几十倍,可以在 100 毫秒内处理完依赖。

第四是缓存策略,node_modules 很少变化,Vite 会强缓存一年,源码用协商缓存,避免重复编译。

最后是 HMR 热更新,Webpack 改一个文件可能要重新打包整个 chunk,而 Vite 只需要重新编译这一个模块,通过 WebSocket 推送给浏览器,所以更新特别快,基本是毫秒级的。

我在实际项目中,把一个大型 Webpack 项目迁移到 Vite,启动时间从 30 秒降到了 2 秒,热更新从 1 秒多降到了几十毫秒,开发体验提升非常明显。


2. Vite 的原理是什么?

一句话答案

Vite 通过拦截浏览器 ESM 请求,使用 esbuild 实时编译并返回模块,无需预先打包。

详细解答

核心原理分为 4 个部分:

1. 基于 ESM 的开发服务器

javascript
// 启动流程
const vite = await createServer({
  root: process.cwd()
})

await vite.listen(3000)

// 核心: Vite 是一个 Connect/Koa 中间件服务器
class ViteDevServer {
  middlewares = []

  constructor() {
    this.setupMiddlewares()
  }

  setupMiddlewares() {
    // 1. 处理 HTML
    this.middlewares.push(htmlMiddleware)

    // 2. 处理 JS/TS/Vue 等
    this.middlewares.push(transformMiddleware)

    // 3. 处理静态资源
    this.middlewares.push(staticMiddleware)
  }
}

2. 模块请求处理流程

javascript
// 浏览器请求流程
// index.html
<!DOCTYPE html>
<html>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

// 1. 浏览器请求 /src/main.js
GET /src/main.js

// 2. Vite 拦截请求,读取文件
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

// 3. Vite 重写裸模块导入
import { createApp } from '/@fs/D:/project/node_modules/vue/dist/vue.runtime.esm-bundler.js'
import App from '/src/App.vue'
createApp(App).mount('#app')

// 4. 浏览器继续请求依赖
GET /@fs/D:/project/node_modules/vue/dist/vue.runtime.esm-bundler.js
GET /src/App.vue

// 5. Vite 编译 Vue 文件
// App.vue
<template>
  <div>{{ msg }}</div>
</template>

<script setup>
const msg = 'Hello'
</script>

// 编译后返回
import { defineComponent as _defineComponent } from 'vue'
export default _defineComponent({
  setup() {
    const msg = 'Hello'
    return { msg }
  },
  render(_ctx, _cache) {
    return _openBlock(), _createElementBlock("div", null, _toDisplayString(_ctx.msg), 1)
  }
})

3. 依赖预构建 (Pre-Bundling)

javascript
// 为什么需要预构建?
/*
1. CommonJS 转 ESM
   很多 npm 包是 CommonJS 格式,浏览器不支持

2. 性能优化
   有些包有几百个模块(如 lodash-es),预构建打包成一个文件

3. 缓存优化
   依赖不常变化,预构建后强缓存
*/

// 预构建流程
async function optimizeDeps() {
  // 1. 扫描依赖
  const deps = await scanImports(entry)
  // deps = ['vue', 'vue-router', 'axios']

  // 2. 使用 esbuild 打包
  await esbuild.build({
    entryPoints: deps,
    bundle: true,
    format: 'esm',
    outdir: 'node_modules/.vite/deps'
  })

  // 3. 生成缓存
  // node_modules/.vite/deps/
  //   vue.js
  //   vue-router.js
  //   axios.js
  //   _metadata.json
}

// 缓存策略
{
  "hash": "abc123",
  "browserHash": "def456",
  "optimized": {
    "vue": {
      "src": "../../vue/dist/vue.runtime.esm-bundler.js",
      "file": "vue.js",
      "needsInterop": false
    }
  }
}

4. HMR 热更新机制

javascript
// HMR 完整流程

// 1. 建立 WebSocket 连接
// 客户端 (vite/client)
const socket = new WebSocket('ws://localhost:3000')

socket.addEventListener('message', ({ data }) => {
  const payload = JSON.parse(data)
  handleMessage(payload)
})

// 2. 文件监听
// 服务端
import chokidar from 'chokidar'

const watcher = chokidar.watch(root, {
  ignored: ['**/node_modules/**', '**/.git/**']
})

watcher.on('change', async (file) => {
  // 3. 计算影响的模块
  const mods = await moduleGraph.getModulesByFile(file)

  // 4. 推送更新消息
  ws.send({
    type: 'update',
    updates: [
      {
        type: 'js-update',
        path: '/src/App.vue',
        acceptedPath: '/src/App.vue',
        timestamp: Date.now()
      }
    ]
  })
})

// 5. 客户端接收并更新
async function handleMessage(payload) {
  if (payload.type === 'update') {
    for (const update of payload.updates) {
      // 重新请求模块
      const newMod = await import(
        `${update.path}?t=${update.timestamp}`
      )

      // 执行 HMR accept 回调
      if (update.acceptedPath) {
        const mod = hotModulesMap.get(update.acceptedPath)
        mod?.callbacks.forEach(cb => cb(newMod))
      }
    }
  }
}

// 6. Vue HMR API
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // Vue 组件自动重新渲染
    __VUE_HMR_RUNTIME__.reload(newModule.default)
  })
}

完整请求链路图:

浏览器                    Vite Dev Server              文件系统
  |                             |                         |
  |--GET /index.html---------->|                         |
  |<--返回 HTML----------------|                         |
  |                             |                         |
  |--GET /src/main.js--------->|                         |
  |                             |--读取 main.js---------->|
  |                             |<--文件内容--------------|
  |                             |--esbuild 编译           |
  |<--返回编译后的 JS----------|                         |
  |                             |                         |
  |--GET /src/App.vue--------->|                         |
  |                             |--读取 App.vue---------->|
  |                             |<--文件内容--------------|
  |                             |--Vue 编译器处理         |
  |<--返回编译后的 JS----------|                         |
  |                             |                         |
  |--WebSocket 连接----------->|                         |
  |                             |                         |
  |                             |<--文件变化监听----------|
  |<--HMR 更新通知-------------|                         |
  |--重新请求变化的模块------->|                         |

面试口语化回答

Vite 的原理可以用"拦截-编译-返回"来概括。

首先,Vite 启动一个开发服务器,这个服务器本质上是一个中间件系统。当浏览器请求模块时,Vite 会拦截这些请求。

比如浏览器请求 /src/main.js,Vite 会读取这个文件,发现里面有 import vue from 'vue',它会把这个裸模块导入改写成具体路径,像 /@fs/node_modules/vue/... 这样。浏览器拿到这个文件后,会继续请求依赖,Vite 再继续拦截处理。

对于 Vue、JSX、TypeScript 这些文件,Vite 会调用对应的编译器,像 Vue 用 @vue/compiler-sfc,TS 用 esbuild,实时编译成浏览器能理解的 JavaScript,然后返回。

另外,Vite 有一个依赖预构建机制。因为 node_modules 里很多包是 CommonJS 格式,或者有上百个小文件,Vite 会用 esbuild 提前把这些打包成 ESM 格式,放到 .vite 目录下,并且强缓存起来。

热更新方面,Vite 用 chokidar 监听文件变化,一旦文件改了,就计算出影响了哪些模块,通过 WebSocket 推送给浏览器。浏览器收到消息后,重新请求这个模块,并执行 HMR API 的回调,实现局部更新。

整个流程没有打包这一步,所以特别快。就像是一个实时翻译,浏览器需要什么,Vite 就翻译什么,不需要提前把所有东西都翻译好。


3. Vite 和 Webpack 的区别?

一句话答案

Vite 开发时基于 ESM 按需编译,生产用 Rollup 打包;Webpack 开发生产都打包,配置复杂但生态成熟。

详细解答

核心区别对比表:

维度WebpackVite
开发模式Bundle-based (打包)ESM-based (原生模块)
启动速度慢 (10-60s)快 (1-3s)
热更新秒级 (0.5-3s)毫秒级 (10-100ms)
编译工具Babel/TS Loaderesbuild
生产构建WebpackRollup
配置复杂度
生态成熟度非常成熟快速发展
浏览器支持全部现代浏览器

1. 开发模式的本质区别

javascript
// ============ Webpack 开发模式 ============
// 1. 启动流程
npm run dev

读取 webpack.config.js

分析入口文件依赖

递归分析所有依赖 (1000+ 模块)

Babel 编译所有模块

打包成 bundle.js (几MB)

启动 webpack-dev-server (10-30秒后)

浏览器加载 bundle.js

// 2. 热更新流程
修改 Button.vue

重新编译 Button.vue

重新打包相关 chunk

webpack-dev-server 推送更新

浏览器接收并刷新 (0.5-2秒)


// ============ Vite 开发模式 ============
// 1. 启动流程
npm run dev

读取 vite.config.js

预构建依赖 (esbuild, 0.1秒)

启动开发服务器 (1秒内)

浏览器访问 index.html

浏览器请求 /src/main.js

Vite 实时编译返回

浏览器继续请求其他模块

Vite 按需编译

// 2. 热更新流程
修改 Button.vue

chokidar 监听到变化

esbuild 编译 Button.vue (10ms)

WebSocket 推送更新

浏览器精确替换模块 (50ms)

2. 配置复杂度对比

javascript
// ============ Webpack 配置 ============
// webpack.config.js (简化版已经很复杂)
module.exports = {
  mode: 'development',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js'
  },

  // 需要配置各种 loader
  module: {
    rules: [
      // JavaScript
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },

      // Vue
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },

      // CSS
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'postcss-loader']
      },

      // SCSS
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      },

      // 图片
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024
          }
        }
      }
    ]
  },

  // 插件配置
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './index.html'
    }),
    new MiniCssExtractPlugin(),
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true
    })
  ],

  // 优化配置
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        }
      }
    }
  },

  // 开发服务器
  devServer: {
    port: 3000,
    hot: true,
    proxy: {
      '/api': 'http://localhost:8080'
    }
  },

  // 路径别名
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    },
    extensions: ['.js', '.vue', '.json']
  }
}

// 还需要配置 babel.config.js, postcss.config.js 等


// ============ Vite 配置 ============
// vite.config.js (功能相同,简洁很多)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

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

  resolve: {
    alias: {
      '@': '/src'
    }
  },

  server: {
    port: 3000,
    proxy: {
      '/api': 'http://localhost:8080'
    }
  },

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

  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router']
        }
      }
    }
  }
})

// 不需要配置 loader,不需要配置各种复杂的规则
// Vite 内置了大部分功能

3. 生产构建差异

javascript
// ============ Webpack 生产构建 ============
npm run build

Webpack 打包

Babel 编译 (慢)

Terser 压缩 (慢)

生成 dist/ (120秒)

// 优点: 开发生产一致
// 缺点: 慢


// ============ Vite 生产构建 ============
npm run build

Rollup 打包

esbuild 编译 (快)

esbuild/Terser 压缩 (快)

生成 dist/ (20秒)

// 优点: 快,产物更优化
// 缺点: 开发用 esbuild,生产用 Rollup,可能有差异

4. 处理模块方式的差异

javascript
// ============ Webpack ============
// 任何导入都会被打包

// src/main.js
import { add } from './utils.js'
import { Button } from './components/Button.vue'
import axios from 'axios'

// Webpack 处理后 (伪代码)
const bundle = {
  './src/main.js': function(require, exports) {
    const utils = require('./utils.js')
    const Button = require('./components/Button.vue')
    const axios = require('axios')
    // ...
  },
  './utils.js': function(require, exports) {
    exports.add = function(a, b) { return a + b }
  },
  // ... 所有模块都在 bundle 里
}


// ============ Vite ============
// 开发时保持模块化,按需加载

// src/main.js (几乎不变)
import { add } from './utils.js'
import { Button } from './components/Button.vue'
import axios from 'axios'

// 浏览器真实请求
GET /src/main.js
GET /src/utils.js
GET /src/components/Button.vue
GET /node_modules/.vite/deps/axios.js

// 每个文件独立编译返回

5. 适用场景对比

javascript
// ============ 选择 Webpack ============
需要兼容旧浏览器 (IE)
✅ 需要特定的 Webpack 插件
✅ 团队已有成熟的 Webpack 配置
✅ 对构建一致性要求极高
✅ 项目已经在用 Webpack,迁移成本高

// 实际案例
const webpackProjects = [
  '大型企业级应用 (要兼容 IE)',
  '有复杂自定义构建需求的项目',
  'Webpack 生态的特定插件依赖'
]


// ============ 选择 Vite ============
✅ 新项目
✅ 现代浏览器项目
✅ 重视开发体验
✅ 快速原型开发
✅ 中小型项目

// 实际案例
const viteProjects = [
  'Vue 3 / React 新项目',
  '管理后台系统',
  'H5 移动端项目',
  '组件库开发',
  '个人项目/开源项目'
]

6. 生态和插件对比

javascript
// ============ Webpack 生态 ============
// 优点: 成熟,插件多
const webpackPlugins = {
  css: ['mini-css-extract-plugin', 'css-minimizer-webpack-plugin'],
  compression: ['compression-webpack-plugin'],
  analysis: ['webpack-bundle-analyzer'],
  optimization: ['speed-measure-webpack-plugin'],
  pwa: ['workbox-webpack-plugin'],
  // ... 几千个插件
}

// 缺点: 配置复杂,学习成本高


// ============ Vite 生态 ============
// 优点: 简单,官方插件质量高
const vitePlugins = {
  vue: ['@vitejs/plugin-vue', '@vitejs/plugin-vue-jsx'],
  react: ['@vitejs/plugin-react'],
  legacy: ['@vitejs/plugin-legacy'],
  compression: ['vite-plugin-compression'],
  autoImport: ['unplugin-auto-import', 'unplugin-vue-components'],
  // ... 快速增长中
}

// 缺点: 部分 Webpack 插件没有对应的 Vite 版本

面试口语化回答

Vite 和 Webpack 最大的区别在于开发模式不同。

Webpack 是"打包模式",启动时要分析所有依赖,打包成一个 bundle 文件,这个过程很慢,可能要十几秒甚至更久。改代码时也要重新打包,热更新比较慢。

Vite 是"ESM 模式",它不打包,直接启动开发服务器,浏览器请求哪个模块就编译哪个,秒级启动。热更新也是毫秒级的,因为只需要重新编译改动的那个模块。

配置复杂度上,Webpack 需要配置各种 loader 和插件,比如处理 Vue 要 vue-loader,处理 CSS 要 style-loader 和 css-loader,处理图片又要另外的配置,非常繁琐。Vite 内置了这些功能,配置文件简洁很多,基本开箱即用。

生产构建也不一样,Webpack 开发和生产都用自己打包,一致性好但慢。Vite 开发用 esbuild,生产用 Rollup,虽然快但理论上可能有差异,不过实践中很少遇到问题。

生态方面,Webpack 更成熟,插件生态非常丰富,但 Vite 发展很快,常用的功能基本都有对应的插件了。

选择上,如果是新项目,特别是 Vue 3 或 React 项目,我会优先选 Vite,开发体验好太多。如果要兼容 IE,或者项目已经用了 Webpack 且运行良好,那继续用 Webpack 也没问题。

我之前把一个 Vue 2 + Webpack 项目升级到 Vue 3 + Vite,启动时间从 30 秒降到 2 秒,热更新从 1 秒多降到几十毫秒,开发效率提升非常明显,团队都很满意。