Vite 面试题
基础概念
1. 什么是 Vite?它解决了什么问题?
核心概念:
- Vite 是新一代前端构建工具,利用浏览器原生 ESM 能力
- 开发环境使用 esbuild 预构建依赖
- 生产环境使用 Rollup 打包
解决的问题:
typescript
// 传统打包工具(Webpack)
启动服务器 → 打包所有模块 → 启动开发服务器
// 时间:可能需要几十秒甚至几分钟
// Vite
启动服务器 → 预构建依赖 → 按需编译
// 时间:通常在 1 秒内核心优势:
- 极速的服务器启动:无需打包,直接启动
- 轻量快速的 HMR:基于 ESM 的 HMR,更新速度与模块数量无关
- 真正的按需编译:只编译当前页面需要的模块
- 开箱即用:内置 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 为什么这么快?
性能优势来源:
- esbuild 预构建:
typescript
// esbuild 使用 Go 编写,比 JavaScript 快 10-100 倍
// 预构建依赖:
{
optimizeDeps: {
include: ['vue', 'lodash-es'],
// esbuild 将依赖转换为 ESM
}
}- 原生 ESM:
html
<!-- 浏览器原生支持 -->
<script type="module">
import { createApp } from '/node_modules/vue/dist/vue.esm-browser.js'
</script>- 按需编译:
typescript
// 只编译浏览器请求的模块
// 不需要的模块不会被编译- 智能缓存:
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>
}
// 修改后保持 state10. 如何调试 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 的区别?
| 特性 | Vite | Webpack |
|---|---|---|
| 启动速度 | 极快(秒级) | 慢(可能需要分钟) |
| HMR | 快速且稳定 | 较慢,项目大时明显 |
| 配置 | 简单,开箱即用 | 复杂,需要大量配置 |
| 生态 | 快速增长 | 成熟完善 |
| 浏览器支持 | 现代浏览器 | 可支持旧浏览器 |
| 构建工具 | Rollup | Webpack |
| 开发原理 | ESM + esbuild | Bundle |
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,
}),
],
})