Skip to content

Vite 面试题

基础概念

1. 什么是 Vite?它解决了什么问题?

核心概念

  • Vite 是新一代前端构建工具,利用浏览器原生 ESM 能力
  • 开发环境使用 esbuild 预构建依赖
  • 生产环境使用 Rollup 打包

解决的问题

typescript
// 传统打包工具(Webpack)
启动服务器 → 打包所有模块 → 启动开发服务器
// 时间:可能需要几十秒甚至几分钟

// Vite
启动服务器 → 预构建依赖 → 按需编译
// 时间:通常在 1 秒内

核心优势

  1. 极速的服务器启动:无需打包,直接启动
  2. 轻量快速的 HMR:基于 ESM 的 HMR,更新速度与模块数量无关
  3. 真正的按需编译:只编译当前页面需要的模块
  4. 开箱即用:内置 TypeScript、JSX、CSS 等支持

2. Vite 的核心原理是什么?

开发环境原理

typescript
// 1. 依赖预构建(使用 esbuild)
// node_modules 中的依赖转换为 ESM
import { createApp } from 'vue'
// ↓ 预构建后
import { createApp } from '/@modules/vue.js'

// 2. 源码按需编译
// 浏览器请求时才编译
GET /src/App.vue
// ↓ Vite 实时编译
// 返回编译后的 JavaScript

// 3. HMR 热更新
// 通过 WebSocket 推送更新
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // 更新模块
  })
}

生产环境原理

typescript
// 使用 Rollup 打包
// 1. Tree-shaking
// 2. 代码分割
// 3. 资源优化
// 4. 生成优化的静态资源

3. Vite 为什么这么快?

性能优势来源

  1. esbuild 预构建
typescript
// esbuild 使用 Go 编写,比 JavaScript 快 10-100 倍
// 预构建依赖:
{
  optimizeDeps: {
    include: ['vue', 'lodash-es'],
    // esbuild 将依赖转换为 ESM
  }
}
  1. 原生 ESM
html
<!-- 浏览器原生支持 -->
<script type="module">
  import { createApp } from '/node_modules/vue/dist/vue.esm-browser.js'
</script>
  1. 按需编译
typescript
// 只编译浏览器请求的模块
// 不需要的模块不会被编译
  1. 智能缓存
typescript
// HTTP 缓存
// 依赖:max-age=31536000,immutable
// 源码:304 Not Modified

// 文件系统缓存
// node_modules/.vite/

配置与优化

4. Vite 配置文件有哪些常用配置?

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  // 插件
  plugins: [vue()],
  
  // 路径解析
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '~': path.resolve(__dirname, 'src'),
    },
    extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'],
  },
  
  // 服务器配置
  server: {
    port: 3000,
    host: true, // 监听所有地址
    open: true, // 自动打开浏览器
    cors: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
  
  // 构建配置
  build: {
    target: 'es2015',
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false,
    minify: 'terser',
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router'],
        },
      },
    },
  },
  
  // 依赖优化
  optimizeDeps: {
    include: ['vue', 'axios'],
    exclude: ['your-local-package'],
  },
  
  // CSS 配置
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
      },
    },
    modules: {
      localsConvention: 'camelCase',
    },
  },
  
  // 环境变量
  envPrefix: 'VITE_',
})

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

typescript
// .env
VITE_APP_TITLE=My App
VITE_API_URL=https://api.example.com

// .env.development
VITE_API_URL=http://localhost:8080

// .env.production
VITE_API_URL=https://api.production.com

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

// TypeScript 类型定义
// env.d.ts
interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_API_URL: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

6. Vite 如何处理静态资源?

typescript
// 1. 导入资源
import imgUrl from './img.png'
// imgUrl 是处理后的 URL

// 2. public 目录
// public/logo.png
// 访问:/logo.png

// 3. URL 导入
import imgUrl from './img.png?url'
// 显式获取 URL

// 4. 内联资源
import imgBase64 from './img.png?inline'
// 转为 base64

// 5. 原始内容
import imgRaw from './shader.glsl?raw'
// 获取原始字符串

// 6. Worker
import Worker from './worker?worker'
const worker = new Worker()

// 配置资源处理
export default defineConfig({
  build: {
    assetsInlineLimit: 4096, // 小于 4kb 内联为 base64
  },
})

插件系统

7. Vite 插件系统是如何工作的?

typescript
// Vite 插件基于 Rollup 插件接口
interface Plugin {
  name: string
  // Vite 独有钩子
  config?: (config: UserConfig) => UserConfig | null
  configResolved?: (config: ResolvedConfig) => void
  configureServer?: (server: ViteDevServer) => void
  transformIndexHtml?: (html: string) => string
  handleHotUpdate?: (ctx: HmrContext) => void
  
  // Rollup 钩子
  resolveId?: (id: string) => string | null
  load?: (id: string) => string | null
  transform?: (code: string, id: string) => string | null
}

// 自定义插件示例
function myPlugin(): Plugin {
  return {
    name: 'my-plugin',
    
    // 修改配置
    config(config) {
      return {
        ...config,
        define: {
          __BUILD_TIME__: JSON.stringify(new Date()),
        },
      }
    },
    
    // 转换代码
    transform(code, id) {
      if (id.endsWith('.custom')) {
        return {
          code: transformCustomFile(code),
          map: null,
        }
      }
    },
    
    // 自定义 HMR
    handleHotUpdate({ file, server }) {
      if (file.endsWith('.custom')) {
        server.ws.send({
          type: 'custom',
          event: 'custom-update',
        })
        return []
      }
    },
  }
}

8. 常用的 Vite 插件有哪些?

typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import legacy from '@vitejs/plugin-legacy'
import { visualizer } from 'rollup-plugin-visualizer'
import compression from 'vite-plugin-compression'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'

export default defineConfig({
  plugins: [
    // Vue 支持
    vue(),
    vueJsx(),
    
    // 传统浏览器支持
    legacy({
      targets: ['defaults', 'not IE 11'],
    }),
    
    // 打包分析
    visualizer({
      open: true,
      gzipSize: true,
    }),
    
    // Gzip 压缩
    compression({
      algorithm: 'gzip',
      ext: '.gz',
    }),
    
    // SVG 图标
    createSvgIconsPlugin({
      iconDirs: [path.resolve(process.cwd(), 'src/icons')],
      symbolId: 'icon-[dir]-[name]',
    }),
  ],
})

HMR 热更新

9. Vite 的 HMR 是如何实现的?

typescript
// 1. HMR API
if (import.meta.hot) {
  // 接受自身更新
  import.meta.hot.accept((newModule) => {
    console.log('模块已更新:', newModule)
  })
  
  // 接受依赖更新
  import.meta.hot.accept('./dep.js', (newDep) => {
    console.log('依赖已更新:', newDep)
  })
  
  // 销毁时清理
  import.meta.hot.dispose((data) => {
    // 保存状态
    data.state = currentState
  })
  
  // 自定义事件
  import.meta.hot.on('custom-event', (data) => {
    console.log('收到自定义事件:', data)
  })
}

// 2. Vue 组件 HMR
// @vitejs/plugin-vue 自动处理
<script>
export default {
  data() {
    return { count: 0 }
  }
}
</script>
// 修改后自动热更新,保持组件状态

// 3. React Fast Refresh
// @vitejs/plugin-react 使用 React Fast Refresh
function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
// 修改后保持 state

10. 如何调试 HMR 问题?

typescript
// 1. 启用 HMR 日志
export default defineConfig({
  server: {
    hmr: {
      overlay: true, // 显示错误覆盖层
    },
  },
  logLevel: 'info', // 查看详细日志
})

// 2. 检查 HMR 边界
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    console.log('HMR 更新:', newModule)
  })
}

// 3. 完整重载
if (import.meta.hot) {
  import.meta.hot.decline() // 拒绝 HMR,触发完整重载
}

// 4. 监听 HMR 事件
if (import.meta.hot) {
  import.meta.hot.on('vite:beforeUpdate', () => {
    console.log('即将更新')
  })
  
  import.meta.hot.on('vite:afterUpdate', () => {
    console.log('更新完成')
  })
}

性能优化

11. Vite 项目如何进行性能优化?

typescript
// 1. 依赖预构建优化
export default defineConfig({
  optimizeDeps: {
    // 强制预构建
    include: [
      'vue',
      'vue-router',
      'pinia',
      'axios',
      'lodash-es',
    ],
    // 排除预构建
    exclude: ['your-local-package'],
    // esbuild 选项
    esbuildOptions: {
      target: 'es2020',
    },
  },
})

// 2. 代码分割
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 第三方库分离
          'vendor-vue': ['vue', 'vue-router', 'pinia'],
          'vendor-ui': ['element-plus'],
          'vendor-utils': ['axios', 'lodash-es'],
        },
        // 动态生成 chunk 名称
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]',
      },
    },
    // chunk 大小警告
    chunkSizeWarningLimit: 1000,
  },
})

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

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

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

// 5. 图片优化
import { defineConfig } from 'vite'
import viteImagemin from 'vite-plugin-imagemin'

export default defineConfig({
  plugins: [
    viteImagemin({
      gifsicle: { optimizationLevel: 7 },
      optipng: { optimizationLevel: 7 },
      mozjpeg: { quality: 80 },
      pngquant: { quality: [0.8, 0.9] },
      svgo: {
        plugins: [{ name: 'removeViewBox' }],
      },
    }),
  ],
})

// 6. 开启 Gzip
import compression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    compression({
      algorithm: 'gzip',
      ext: '.gz',
      threshold: 10240, // 大于 10kb 才压缩
    }),
  ],
})

12. 如何优化 Vite 的构建速度?

typescript
// 1. 使用 SWC/esbuild 替代 Babel
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc' // 使用 SWC

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

// 2. 减少 resolve 查找
export default defineConfig({
  resolve: {
    extensions: ['.js', '.ts', '.jsx', '.tsx'], // 只保留必要的
  },
})

// 3. 使用 esbuild 压缩
export default defineConfig({
  build: {
    minify: 'esbuild', // 比 terser 快很多
  },
})

// 4. 关闭 sourcemap
export default defineConfig({
  build: {
    sourcemap: false,
  },
})

// 5. 并行构建
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        experimentalMinChunkSize: 1000,
      },
    },
  },
})

迁移与对比

13. 如何从 Webpack 迁移到 Vite?

typescript
// 1. 安装依赖
// npm install vite @vitejs/plugin-vue -D

// 2. 创建 vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  server: {
    port: 8080,
    proxy: {
      '/api': 'http://localhost:3000',
    },
  },
})

// 3. 修改 index.html
// Webpack: public/index.html
// Vite: 项目根目录 index.html
<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- Vite 入口 -->
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

// 4. 更新环境变量
// Webpack: process.env.VUE_APP_XXX
// Vite: import.meta.env.VITE_XXX

// 5. 更新静态资源引用
// Webpack: require('@/assets/logo.png')
// Vite: new URL('@/assets/logo.png', import.meta.url).href

// 6. 更新 package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

14. Vite vs Webpack 的区别?

特性ViteWebpack
启动速度极快(秒级)慢(可能需要分钟)
HMR快速且稳定较慢,项目大时明显
配置简单,开箱即用复杂,需要大量配置
生态快速增长成熟完善
浏览器支持现代浏览器可支持旧浏览器
构建工具RollupWebpack
开发原理ESM + esbuildBundle
typescript
// Vite 优势
✅ 开发体验极佳
✅ 配置简单
✅ 启动快速
HMR 快速

// Webpack 优势
✅ 生态成熟
✅ 功能强大
✅ 社区庞大
✅ 兼容性好

高级特性

15. Vite 的 SSR 支持如何使用?

typescript
// server.js
import express from 'express'
import { createServer as createViteServer } from 'vite'

async function createServer() {
  const app = express()
  
  // 创建 Vite 服务器
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom',
  })
  
  app.use(vite.middlewares)
  
  app.use('*', async (req, res) => {
    const url = req.originalUrl
    
    try {
      // 1. 读取 index.html
      let template = await vite.transformIndexHtml(
        url,
        fs.readFileSync('index.html', 'utf-8')
      )
      
      // 2. 加载服务器入口
      const { render } = await vite.ssrLoadModule('/src/entry-server.js')
      
      // 3. 渲染应用
      const appHtml = await render(url)
      
      // 4. 注入渲染的应用
      const html = template.replace('<!--ssr-outlet-->', appHtml)
      
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      vite.ssrFixStacktrace(e)
      console.error(e)
      res.status(500).end(e.message)
    }
  })
  
  app.listen(3000)
}

createServer()

// entry-server.js
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'
import App from './App.vue'

export async function render(url) {
  const app = createSSRApp(App)
  const html = await renderToString(app)
  return html
}

16. Vite 如何处理 CSS?

typescript
// 1. CSS Modules
// Button.module.css
.button {
  color: red;
}

// Button.tsx
import styles from './Button.module.css'
<button className={styles.button}>Click</button>

// 2. CSS 预处理器
// 自动支持 .scss, .sass, .less, .styl
import './styles.scss'

// 配置
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
      },
    },
  },
})

// 3. PostCSS
// postcss.config.js
export default {
  plugins: {
    autoprefixer: {},
    'postcss-nested': {},
  },
}

// 4. CSS-in-JS
import styled from 'styled-components'

const Button = styled.button`
  color: red;
  &:hover {
    color: blue;
  }
`

// 5. Tailwind CSS
// vite.config.ts
import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer'

export default defineConfig({
  css: {
    postcss: {
      plugins: [tailwindcss, autoprefixer],
    },
  },
})

17. Vite 的依赖预构建是什么?

typescript
// 依赖预构建的作用:
// 1. 将 CommonJS/UMD 转换为 ESM
// 2. 将多个内部模块合并为单个模块

// 示例:lodash-es 有很多内部模块
import { debounce } from 'lodash-es'
// 没有预构建:会发起数百个请求
// 预构建后:合并为单个文件

// 配置预构建
export default defineConfig({
  optimizeDeps: {
    // 强制包含
    include: ['vue', 'vue-router'],
    
    // 排除
    exclude: ['your-local-package'],
    
    // 自定义 esbuild 选项
    esbuildOptions: {
      target: 'es2020',
      supported: {
        'top-level-await': true,
      },
    },
    
    // 强制重新预构建
    force: true,
  },
})

// 预构建缓存位置
// node_modules/.vite/deps/

// 清除缓存
// rm -rf node_modules/.vite
// 或在代码中
import { build } from 'vite'
await build({ force: true })

实战问题

18. Vite 项目中如何处理跨域?

typescript
// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      // 字符串简写
      '/api': 'http://localhost:3000',
      
      // 完整配置
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
        configure: (proxy, options) => {
          // 配置代理
        },
      },
      
      // 正则表达式
      '^/fallback/.*': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, ''),
      },
      
      // WebSocket
      '/socket.io': {
        target: 'ws://localhost:3000',
        ws: true,
      },
    },
  },
})

19. Vite 如何支持多页面应用?

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin/index.html'),
        mobile: resolve(__dirname, 'mobile/index.html'),
      },
    },
  },
})

// 目录结构
// ├── index.html          (主页)
// ├── admin/
// │   └── index.html      (管理后台)
// └── mobile/
//     └── index.html      (移动端)

20. Vite 在生产环境的最佳实践?

typescript
export default defineConfig({
  build: {
    // 1. 目标环境
    target: 'es2015',
    
    // 2. 输出目录
    outDir: 'dist',
    assetsDir: 'assets',
    
    // 3. 压缩
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true, // 移除 console
        drop_debugger: true,
      },
    },
    
    // 4. Sourcemap
    sourcemap: false, // 生产环境关闭
    
    // 5. 代码分割
    rollupOptions: {
      output: {
        manualChunks: {
          'vendor-vue': ['vue', 'vue-router', 'pinia'],
          'vendor-ui': ['element-plus'],
        },
      },
    },
    
    // 6. 资源内联限制
    assetsInlineLimit: 4096,
    
    // 7. CSS 代码分割
    cssCodeSplit: true,
    
    // 8. 报告压缩后大小
    reportCompressedSize: false, // 大项目关闭以提升构建速度
  },
  
  // 9. 使用插件优化
  plugins: [
    // 传统浏览器支持
    legacy({
      targets: ['defaults', 'not IE 11'],
    }),
    
    // 压缩
    compression({
      algorithm: 'gzip',
      ext: '.gz',
    }),
    
    // 分析
    visualizer({
      open: true,
      gzipSize: true,
    }),
  ],
})