Vite
一、核心原理
1. Vite 是什么?
定义: Vite 是一个现代化的前端构建工具,由 Vue 作者尤雨溪开发,旨在提供更快、更高效的前端开发体验。
原理: Vite 的名字来源于法语"快"的意思。它充分利用了浏览器原生 ES Modules 的能力,在开发环境下跳过打包步骤,实现按需编译和加载。
工作方式:
- 开发环境:基于浏览器原生 ESM,实现按需编译
- 生产环境:使用 Rollup 进行打包,确保生产代码的优化
示例:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 3000
}
})
常见误区:
- 误区:Vite 只适用于 Vue 项目
- 正解:Vite 支持 Vue、React、Svelte、Preact 等多种框架
2. Vite 原理
定义: Vite 的核心原理是基于浏览器原生 ES Modules 实现开发环境下的按需编译,生产环境使用 Rollup 打包。
原理详解:
-
开发环境工作流程:
- 启动时只处理入口文件,不打包整个项目
- 浏览器请求模块时,Vite 拦截请求并实时编译
- 利用浏览器原生 ESM 能力,按模块逐个加载
- 只有被请求的模块才会被编译
-
模块解析流程:
浏览器请求 → Vite Dev Server 拦截 → 模块转换 → 返回编译结果
示例流程:
// 源码 main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
// Vite 转换后的结果(浏览器实际加载的)
import { createApp } from '/@modules/vue.js'
import App from '/src/App.vue?t=1631234567'
createApp(App).mount('#app')
代码说明:
/@modules/vue被转换为/@modules/vue.js(依赖解析)./App.vue被转换为/src/App.vue?t=1631234567(添加时间戳用于缓存控制)
常见误区:
- 误区:Vite 开发环境也需要打包
- 正解:Vite 开发环境不打包,而是按需编译和加载
3. Vite 为什么快?
定义: Vite 的快速来源于其独特的开发环境架构设计,避免了传统打包工具的冷启动和全量编译。
核心原因:
- 无需打包: 开发环境跳过打包环节,直接利用浏览器 ESM
- 按需编译: 只有被请求的模块才会被编译
- 原生 ESM: 浏览器原生支持,无需额外的模块加载器
- 高效缓存: 依赖预构建结果被缓存,减少重复工作
对比说明:
传统打包工具(Webpack)启动流程:
项目启动 → 分析依赖树 → 转换所有模块 → 打包输出 → 启动服务器
↑ 耗时主要在这里
Vite 启动流程:
项目启动 → 预构建依赖 → 启动服务器 → 按需编译模块
↑ 只处理一次,后续按需加载
性能对比示例:
// Webpack 启动时需要编译所有模块
// 1000个模块 → 全部编译 → 打包 → 启动(可能需要10-30秒)
// Vite 启动时只处理入口和预构建依赖
// 1000个模块 → 只预构建依赖 → 启动(通常1-3秒)
// 后续访问模块时才编译(每个模块几十毫秒)
常见误区:
- 误区:Vite 快只是因为缓存
- 正解:Vite 快的核心原因是开发环境不打包+按需编译
4. ES Modules 与原生 ESM
定义: ES Modules 是 JavaScript 官方的模块化规范。原生 ESM 指浏览器原生支持该规范,无需额外工具。
ESM 语法:
// 导出 - export.js
export const name = 'Vite'
export function greet() {
return `Hello ${name}`
}
export default { name, greet }
// 导入 - main.js
import myModule, { name, greet } from './export.js'
console.log(name)
console.log(greet())
浏览器原生 ESM 使用:
<!-- 必须添加 type="module" -->
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
template: '<div>Hello ESM</div>'
}).mount('#app')
</script>
ESM 核心特性:
// 1. 静态分析 - 编译时确定依赖关系
import { foo } from './bar.js' // 可以在编译时确定依赖
// 2. 严格模式 - ESM 默认启用严格模式
'use strict' // 隐式启用
// 3. 模块级作用域 - 变量不会污染全局
const modulePrivate = 'not global'
// 4. 顶层 await - 支持顶层异步操作
const data = await fetch('/api/data').then(r => r.json())
// 5. 单例模式 - 模块只执行一次
let count = 0
export function getCount() {
count++
return count
}
浏览器支持情况:
// 现代浏览器都支持原生 ESM
// Chrome 61+, Firefox 60+, Safari 11+, Edge 16+
// 查看当前环境是否支持 ESM
const supportsESM = (() => {
try {
new Function('import("")')
return true
} catch {
return false
}
})()
常见误区:
- 误区:ESM 只能用于浏览器
- 正解:Node.js 12.17+ 也支持原生 ESM(需要 .mjs 扩展名或 package.json 中设置 type: "module")
- 误区:import 语句可以在条件语句中使用
- 正解:import 是静态的,不能在 if/for 等动态语句中使用(需要使用动态 import())
5. Vite 启动原理
定义: Vite 启动时只处理入口文件和依赖预构建,不会打包整个应用。
启动流程:
/**
* Vite 启动流程:
*
* 1. 读取配置文件 (vite.config.js)
* 2. 创建 Dev Server
* 3. 依赖预构建 (optimizeDeps)
* 4. 启动 HTTP 服务器
* 5. 等待浏览器请求
* 6. 按需编译返回模块
*/
// 伪代码模拟启动流程
async function startViteDevServer() {
// 1. 加载配置
const config = await loadConfig('vite.config.js')
// 2. 扫描入口文件,发现依赖
const deps = scanDependencies(config.root)
// 3. 预构建依赖(esbuild)
await preBundleDeps(deps) // 使用 esbuild,非常快
// 4. 启动开发服务器
const server = createDevServer(config)
// 5. 监听请求,按需转换模块
server.on('request', async (ctx) => {
if (ctx.path.startsWith('/@modules/')) {
// 预构建的依赖
return servePreBundledModule(ctx.path)
} else if (ctx.path.endsWith('.vue')) {
// Vue 单文件组件,需要转换
return await transformVueSFC(ctx.path)
} else if (ctx.path.endsWith('.ts')) {
// TypeScript,需要转换
return await transformTypeScript(ctx.path)
}
// 其他文件直接返回
return serveFile(ctx.path)
})
return server
}
启动时只处理入口文件:
// index.html(入口)
<script type="module" src="/src/main.js"></script>
// Vite 启动时:
// 1. 解析 index.html,发现 /src/main.js
// 2. 预构建 node_modules 中的依赖
// 3. 启动服务器,等待请求
// 4. 当浏览器请求 main.js 时,才编译 main.js
// 5. 当 main.js 中 import 了其他模块,浏览器再次请求时才编译那些模块
常见误区:
- 误区:Vite 启动时会编译所有源码文件
- 正解:Vite 启动时只预构建依赖,源码文件按需编译
6. Vite 模块解析
定义: Vite 模块解析是指 Vite 如何处理和转换各种类型的模块导入。
模块解析规则:
// vite.config.js 中的 resolve 配置
export default defineConfig({
resolve: {
// 别名配置
alias: {
'@': '/src',
'~': path.resolve(__dirname, 'src/assets')
},
// 扩展名解析
extensions: ['.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
// 主字段解析顺序
mainFields: ['module', 'jsnext:main', 'main']
}
})
解析优先级:
// 1. 绝对路径
import utils from '/src/utils/index.js'
// 2. 相对路径
import utils from './utils/index.js'
import utils from '../utils/index.js'
// 3. 别名路径(需要在配置中定义)
import utils from '@/utils/index.js'
// 4. npm 包(从 node_modules 解析)
import vue from 'vue'
import lodash from 'lodash-es'
特殊模块转换:
// 裸模块导入会被转换为 /@modules/ 路径
import { createApp } from 'vue'
// 转换为: import { createApp } from '/@modules/vue.js'
// 带条件路径的导入
import 'my-lib/sub/module.js'
// 根据 package.json 的 exports 字段解析
常见误区:
- 误区:Vite 的别名配置和 Webpack 完全一样
- 正解:Vite 的别名使用
resolve.alias,配置方式与 Webpack 类似,但底层解析逻辑不同
二、热更新/HMR
7. Vite 快速启动
定义: Vite 的快速启动体现在开发服务器的冷启动时间极短,通常 1-3 秒即可启动。
快速启动的原因:
- 不打包源码: 跳过整个应用的打包环节
- esbuild 预构建依赖: 使用 Go 编写的 esbuild,速度极快
- 按需编译: 只编译被请求的模块
启动速度对比:
Webpack(大型项目):
- 冷启动:15-30秒
- 需要分析所有依赖,转换所有模块,打包输出
Vite(同等规模项目):
- 冷启动:1-3秒
- 只预构建依赖,源码按需编译
配置优化启动速度:
// vite.config.js
export default defineConfig({
optimizeDeps: {
// 预构建时包含的依赖
include: ['vue', 'vue-router', 'pinia'],
// 排除不需要预构建的依赖
exclude: ['some-local-package']
},
server: {
// 预构建完成后自动打开浏览器
open: true
}
})
8. Vite 热更新(HMR)
定义: HMR(Hot Module Replacement)热模块替换,指在开发过程中修改代码后,只更新修改的模块,而无需刷新整个页面。
HMR 核心优势:
- 保持应用状态(如表单输入、Vuex/Redux 状态)
- 节省重新加载的时间
- 提升开发体验
HMR 工作流程:
/**
* HMR 工作流程:
*
* 1. 文件发生变化
* 2. Vite 监听到文件变更
* 3. 确定受影响的模块
* 4. 通过 WebSocket 通知浏览器
* 5. 浏览器请求更新后的模块
* 6. 替换旧模块,执行更新回调
* 7. 页面局部更新,保持状态
*/
// Vite 的 HMR 使用 WebSocket 通信
// 服务器 → 浏览器:发送更新通知
// 浏览器 → 服务器:请求更新后的模块内容
示例:
// counter.js
let count = 0
export function increment() {
count++
console.log('Count:', count)
}
// HMR 处理
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 模块更新后的处理
console.log('Counter module updated')
// 保留原有状态
newModule.increment()
})
}
9. HMR 原理
定义: Vite 的 HMR 原理是基于文件监听 + WebSocket + 模块图实现的高效模块替换机制。
原理详解:
/**
* Vite HMR 原理:
*
* 1. 文件监听(基于 chokidar)
* - 监听项目文件变化
* - 记录模块依赖图
*
* 2. WebSocket 通信
* - 服务器与浏览器建立 WebSocket 连接
* - 发送更新事件和模块路径
*
* 3. 模块边界计算
* - 确定受影响的模块链
* - 找到最近的 HMR 边界
*
* 4. 按需更新
* - 只请求和更新变更的模块
* - 执行模块的 accept 回调
*/
// Vite 模块图结构
const moduleGraph = {
'/src/main.js': {
importers: [], // 导入当前模块的模块
importedModules: [ // 当前模块导入的模块
'/src/App.vue',
'/@modules/vue.js'
],
isSelfAccepting: false // 是否接受自身更新
},
'/src/App.vue': {
importers: ['/src/main.js'],
importedModules: ['/src/components/Hello.vue'],
isSelfAccepting: true // Vue SFC 通常支持自更新
}
}
HMR 通信流程代码模拟:
// 服务器端(Vite Dev Server)
const watcher = chokidar.watch(root)
watcher.on('change', async (file) => {
// 1. 找到受影响的模块
const affectedModules = moduleGraph.getModulesByFile(file)
// 2. 通过 WebSocket 发送更新通知
ws.send(JSON.stringify({
type: 'update',
updates: affectedModules.map(mod => ({
type: mod.isSelfAccepting ? 'js-update' : 'full-reload',
path: mod.url,
acceptedPath: mod.url,
timestamp: Date.now()
}))
}))
})
// 客户端(Vite HMR Client)
const socket = new WebSocket('ws://localhost:3000')
socket.onmessage = async ({ data }) => {
const message = JSON.parse(data)
if (message.type === 'update') {
for (const update of message.updates) {
if (update.type === 'js-update') {
// 动态导入更新后的模块
const newModule = await import(update.path + '?t=' + update.timestamp)
// 执行 accept 回调
hotModulesMap.get(update.path).callbacks.forEach(cb => cb(newModule))
} else {
// 全量刷新
location.reload()
}
}
}
}
常见误区:
- 误区:HMR 就是自动刷新页面
- 正解:HMR 是模块级别的热替换,不是页面刷新。只有当无法找到 HMR 边界时才会全量刷新
10. Vite HMR API
定义: Vite 提供了 import.meta.hot API,允许开发者自定义模块更新行为。
HMR API 方法:
// 1. accept - 接受模块更新
import.meta.hot.accept((newModule) => {
// 模块更新后的回调
console.log('Module updated:', newModule)
})
// 2. accept 带路径 - 接受特定依赖的更新
import.meta.hot.accept('./dep.js', (newDep) => {
console.log('Dependency updated:', newDep)
})
// 3. dispose - 清理旧模块的资源
import.meta.hot.dispose((data) => {
// 模块被替换前执行
// data 可以传递给新模块
data.cleanup = 'some state'
})
// 4. invalidate - 标记模块无效,触发全量刷新
import.meta.hot.invalidate('Reason for invalidation')
// 5. prune - 模块不再被导入时的回调
import.meta.hot.prune(() => {
// 清理不再需要的资源
})
// 6. decline - 拒绝更新,触发全量刷新
import.meta.hot.decline()
完整 HMR 处理示例:
// store.js - 一个简单的状态管理
let state = { count: 0 }
const listeners = new Set()
export function getState() {
return state
}
export function setState(newState) {
state = { ...state, ...newState }
listeners.forEach(fn => fn(state))
}
export function subscribe(fn) {
listeners.add(fn)
return () => listeners.delete(fn)
}
// HMR 处理
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 保持原有状态
const currentState = state
// 如果需要,可以迁移状态
console.log('Store module hot updated')
})
import.meta.hot.dispose(() => {
// 清理监听器
listeners.clear()
})
}
11. 热更新边界
定义: 热更新边界是指模块能够接受自身或依赖更新的最小范围。当文件变化时,Vite 会向上查找直到找到能接受更新的模块。
边界查找规则:
/**
* 热更新边界查找:
*
* 1. 检查变更模块是否接受自身更新 (self-accepting)
* 2. 如果不接受,检查导入它的模块是否接受它的更新
* 3. 递归向上查找,直到找到接受更新的模块
* 4. 如果到达根模块仍未找到,则全量刷新
*/
// 示例模块依赖图:
// main.js → App.vue → Header.vue → Logo.vue
// → Nav.vue
// 场景1:Logo.vue 变更
// - Logo.vue 接受自身更新 → 只更新 Logo.vue
// 场景2:Header.vue 变更
// - Header.vue 接受自身更新 → 更新 Header.vue 及其子组件
// 场景3:App.vue 变更
// - App.vue 接受自身更新 → 更新整个应用
// 场景4:main.js 变更
// - 到达根模块,全量刷新
框架的 HMR 边界处理:
// Vue SFC 插件自动处理 HMR
// @vitejs/plugin-vue 会为每个 .vue 文件注入 HMR 代码
// 转换后的 .vue 文件包含:
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// Vue 组件的 HMR 处理
__VUE_HMR_RUNTIME__.rerender(componentId, newModule.render)
__VUE_HMR_RUNTIME__.reload(componentId, newModule.default)
})
}
// React 通过 @vitejs/plugin-react 实现
// 使用 Fast Refresh 技术
常见误区:
- 误区:所有文件变更都会触发热更新
- 正解:只有支持 HMR 的文件类型才会热更新,如 .vue、.jsx、.css 等。配置文件变更需要重启服务器
三、依赖预构建
12. Vite 依赖预构建
定义: Vite 在启动时会对 node_modules 中的依赖进行预构建,将 CommonJS/UMD 格式的依赖转换为 ESM 格式。
预构建原因:
- CommonJS/UMD 兼容: 浏览器原生 ESM 不支持 CommonJS,需要转换
- 性能优化: 将多个内部模块合并为单个文件,减少请求数量
- 统一入口: 确保依赖以标准 ESM 格式提供
预构建流程图:
node_modules 中的依赖
↓
扫描发现依赖
↓
esbuild 预构建(转换 + 合并)
↓
缓存到 node_modules/.vite
↓
开发环境从缓存加载
13. optimizeDeps 配置
定义: optimizeDeps 是 Vite 配置中用于控制依赖预构建行为的选项。
配置项详解:
// vite.config.js
export default defineConfig({
optimizeDeps: {
// 强制预构建的依赖(即使未被直接导入)
include: [
'lodash-es',
'axios'
],
// 排除预构建的依赖
exclude: [
'some-local-package',
'custom-lib'
],
// esbuild 转换选项
esbuildOptions: {
target: 'esnext',
supported: {
'top-level-await': true
}
},
// 强制重新预构建
force: true
}
})
include 配置示例:
// 场景:某些依赖未被直接导入,但需要在预构建中包含
export default defineConfig({
optimizeDeps: {
include: [
// 直接导入的依赖会自动预构建
'vue',
'vue-router',
// 动态导入或间接依赖需要手动指定
'element-plus/es',
'lodash-es/merge.js'
]
}
})
exclude 配置示例:
// 场景:某些本地开发的包不需要预构建
export default defineConfig({
optimizeDeps: {
exclude: [
// 本地 link 的包
'my-local-package',
// 使用 ES Modules 的包
'some-esm-only-package'
]
}
})
14. 预构建的目的
定义: 预构建的主要目的是解决 CommonJS 兼容性和性能优化问题。
具体目的:
- 格式转换:
// 转换前(CommonJS)
// node_modules/lodash/index.js
module.exports = {
merge: function() { /* ... */ },
debounce: function() { /* ... */ }
}
// 转换后(ESM)
// node_modules/.vite/deps/lodash.js
export { merge, debounce }
// 包装为标准 ESM 格式,浏览器可直接使用
- 模块合并:
// 转换前:lodash-es 包含 600+ 个文件
// 用户可能只用了其中几个函数
// 转换后:合并为单个文件
// node_modules/.vite/deps/lodash-es.js
// 减少 HTTP 请求数量
- 统一依赖入口:
// 不同包可能导出方式不同
// 预构建确保统一为标准 ESM
常见误区:
- 误区:预构建会减小依赖体积
- 正解:预构建主要解决格式兼容和减少请求数量,不一定会减小体积
15. 预构建缓存
定义: Vite 将预构建结果缓存到 node_modules/.vite 目录,避免重复构建。
缓存机制:
// 缓存位置:node_modules/.vite/deps/
// 缓存失效条件:
// 1. package.json 变更
// 2. lockfile 变更(package-lock.json, yarn.lock, pnpm-lock.yaml)
// 3. optimizeDeps 配置变更
// 4. 使用 --force 参数启动
// 缓存结构:
// node_modules/.vite/
// ├── deps/ // 预构建的依赖
// │ ├── vue.js
// │ ├── vue-router.js
// │ └── _metadata.json // 缓存元数据
// └── deps_cache/ // esbuild 缓存
// _metadata.json 示例
{
"hash": "abc123",
"browserHash": "def456",
"optimized": {
"vue": {
"file": "vue.js",
"src": "vue/dist/vue.runtime.esm-bundler.js",
"needsInterop": false
}
}
}
强制重新预构建:
# 方式1:使用 --force 参数
vite --force
# 方式2:删除缓存目录
rm -rf node_modules/.vite
# 方式3:配置中设置
export default defineConfig({
optimizeDeps: {
force: true
}
})
四、插件系统
16. Vite 插件
定义: Vite 插件系统基于 Rollup 插件设计,扩展了开发环境特有的钩子。
插件基础结构:
// 一个简单的 Vite 插件
export function myPlugin(options = {}) {
return {
name: 'my-plugin', // 插件名称(必需)
// 配置钩子
config(config, { command, mode }) {
// 修改 Vite 配置
return {
resolve: {
alias: {
'~': options.basePath || '/src'
}
}
}
},
// 配置解析后钩子
configResolved(config) {
// 获取最终配置
console.log('Resolved config:', config)
},
// 转换钩子
transform(code, id) {
// 转换模块内容
if (id.endsWith('.md')) {
return transformMarkdown(code)
}
return null // 不处理则返回 null
}
}
}
常用内置插件:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
// Vue 支持
vue({
template: {
compilerOptions: {
// 自定义元素配置
isCustomElement: (tag) => tag.startsWith('custom-')
}
}
}),
// React 支持
react({
fastRefresh: true,
babel: {
// 自定义 Babel 配置
}
})
]
})
17. Vite 插件钩子
定义: Vite 插件钩子分为两类:Rollup 构建钩子和 Vite 特有钩子。
Rollup 构建钩子(通用):
export default {
name: 'rollup-hooks-demo',
// 构建开始
buildStart(options) {
console.log('Build starting')
},
// 解析导入
resolveId(source, importer, options) {
// 自定义模块解析
if (source.startsWith('virtual:')) {
return '\0' + source // \0 前缀表示虚拟模块
}
return null // 使用默认解析
},
// 加载模块
load(id) {
// 自定义模块加载
if (id.startsWith('\0virtual:')) {
return 'export default "virtual module content"'
}
return null
},
// 转换模块
transform(code, id) {
// 自定义模块转换
return null
},
// 构建结束
buildEnd(error) {
console.log('Build finished')
}
}
Vite 特有钩子:
export default {
name: 'vite-hooks-demo',
// 修改配置
config(config, env) {
return {
define: {
__CUSTOM_DEFINE__: JSON.stringify('custom value')
}
}
},
// 配置解析后
configResolved(config) {
// 可以访问完整解析后的配置
},
// 配置服务器
configureServer(server) {
// 添加自定义中间件
server.middlewares.use((req, res, next) => {
if (req.url.startsWith('/api')) {
// 处理 API 请求
res.end(JSON.stringify({ message: 'Hello' }))
} else {
next()
}
})
},
// 转换 HTML
transformIndexHtml(html) {
return html.replace(
'</head>',
'<script src="/inject.js"></script></head>'
)
},
// 热更新处理
handleHotUpdate({ file, server, modules, read }) {
// 自定义 HMR 处理
return modules
}
}
钩子执行顺序:
/**
* 开发环境钩子执行顺序:
* 1. config
* 2. configResolved
* 3. configureServer
* 4. transformIndexHtml
* 5. resolveId(每次模块请求)
* 6. load(每次模块请求)
* 7. transform(每次模块请求)
* 8. handleHotUpdate(文件变更时)
*
* 生产环境钩子执行顺序:
* 1. config
* 2. configResolved
* 3. buildStart
* 4. resolveId
* 5. load
* 6. transform
* 7. buildEnd
* 8. closeBundle
*/
18. 自定义 Vite 插件
定义: 自定义 Vite 插件可以满足特定的项目需求,如自定义文件处理、代码生成等。
实战案例1:Markdown 转 Vue 组件
// vite-plugin-markdown-vue.js
import { marked } from 'marked'
export function markdownToVue() {
return {
name: 'markdown-to-vue',
transform(code, id) {
if (id.endsWith('.md')) {
// 将 Markdown 转换为 HTML
const html = marked(code)
// 包装为 Vue 组件
const vueComponent = `
<template>
<article class="markdown-body">
${html}
</article>
</template>
<script>
export default {
name: 'MarkdownDoc'
}
</script>
`
return vueComponent
}
return null
}
}
}
// 使用
// vite.config.js
import { markdownToVue } from './vite-plugin-markdown-vue'
export default defineConfig({
plugins: [markdownToVue()]
})
// 在代码中使用
import Doc from './docs/guide.md'
实战案例2:虚拟模块插件
// vite-plugin-virtual-module.js
export function virtualModule() {
const virtualModuleId = 'virtual:app-config'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'virtual-module',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
return null
},
load(id) {
if (id === resolvedVirtualModuleId) {
// 动态生成模块内容
return `
export const APP_NAME = 'My App'
export const VERSION = '${process.env.npm_package_version}'
export const BUILD_TIME = '${new Date().toISOString()}'
`
}
return null
}
}
}
// 使用
import { APP_NAME, VERSION } from 'virtual:app-config'
console.log(`${APP_NAME} v${VERSION}`)
实战案例3:图片压缩插件
// vite-plugin-imagemin 类似功能
import imagemin from 'imagemin'
import imageminPngquant from 'imagemin-pngquant'
export function imageMin(options = {}) {
return {
name: 'image-min',
apply: 'build', // 仅在生产构建时生效
async transform(code, id) {
if (/\.(png|jpg|jpeg|gif|webp)$/.test(id)) {
const result = await imagemin.buffer(code, {
plugins: [
imageminPngquant({
quality: [0.6, 0.8]
})
]
})
return { code: Buffer.from(result).toString('base64') }
}
return null
}
}
}
19. Vite 插件与 Rollup 插件
定义: Vite 插件基于 Rollup 插件设计,但增加了开发环境特有的钩子。
关系说明:
/**
* Vite 插件 vs Rollup 插件:
*
* 相同点:
* - 使用相同的插件接口(name, resolveId, load, transform 等)
* - Rollup 插件可以在 Vite 中使用(大部分)
* - 都支持钩子机制
*
* 不同点:
* - Vite 增加了开发环境特有的钩子(configureServer, handleHotUpdate 等)
* - Vite 插件可以在开发环境和生产环境有不同行为
* - Vite 插件支持 apply 选项控制生效环境
*/
// apply 选项
export default {
name: 'conditional-plugin',
// 仅在开发环境生效
apply: 'serve',
// 或仅在生产环境生效
// apply: 'build',
// 或自定义条件
// apply(config, { command }) {
// return command === 'build' && config.mode === 'production'
// },
transform(code, id) {
// 根据环境不同行为
return code
}
}
// 常用第三方插件
// - vite-plugin-pwa: PWA 支持
// - vite-plugin-imagemin: 图片压缩
// - unplugin-auto-import: 自动导入 API
// - vite-plugin-compression: Gzip/Brotli 压缩
// - vite-plugin-html: HTML 处理
常见误区:
- 误区:所有 Rollup 插件都能直接在 Vite 中使用
- 正解:大部分 Rollup 插件可以直接使用,但涉及文件系统操作的插件可能需要调整
20. 常用 Vite 插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteStaticCopy } from 'vite-plugin-static-copy'
import { compression } from 'vite-plugin-compression2'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
// 1. 框架支持
vue(),
// 2. 自动导入
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts'
}),
// 3. 组件自动注册
Components({
dirs: ['src/components'],
extensions: ['vue'],
dts: 'src/components.d.ts'
}),
// 4. 静态资源复制
viteStaticCopy({
targets: [
{
src: 'public/robots.txt',
dest: ''
}
]
}),
// 5. Gzip 压缩
compression({
algorithm: 'gzip'
})
]
})
五、构建优化
21. Vite 构建优化
定义: Vite 生产构建使用 Rollup 打包,提供多种优化策略来提升生产环境的加载性能。
优化策略总览:
// vite.config.js
export default defineConfig({
build: {
// 1. 代码分割
rollupOptions: {
output: {
manualChunks(id) {
// 将 node_modules 中的包分割为单独的文件
if (id.includes('node_modules')) {
// 第三方库单独打包
if (id.includes('vue')) return 'vue-vendor'
if (id.includes('lodash')) return 'lodash-vendor'
return 'vendor'
}
// 大型组件单独打包
if (id.includes('components/Chart')) return 'chart'
if (id.includes('components/Editor')) return 'editor'
}
}
},
// 2. 压缩
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // 移除 console.log
drop_debugger: true // 移除 debugger
}
},
// 3. CSS 代码分割
cssCodeSplit: true,
// 4. 静态资源处理
assetsInlineLimit: 4096, // 小于 4KB 的资源内联为 base64
// 5. Source Map
sourcemap: false, // 生产环境默认关闭
}
})
22. 代码分割
定义: 代码分割是将打包后的代码拆分为多个较小的文件,实现按需加载。
配置方式:
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
// 方式1:函数方式(灵活控制)
manualChunks(id, { getModuleInfo }) {
if (id.includes('node_modules')) {
// 按库分类
if (id.includes('vue')) return 'vue-vendor'
if (id.includes('react')) return 'react-vendor'
if (id.includes('element-plus')) return 'element-vendor'
return 'vendor'
}
// 按功能模块分割
if (id.includes('/pages/')) {
const match = id.match(/\/pages\/([^/]+)/)
if (match) return `page-${match[1]}`
}
},
// 方式2:对象方式(简单配置)
// manualChunks: {
// vue: ['vue', 'vue-router', 'pinia'],
// ui: ['element-plus'],
// utils: ['lodash-es', 'dayjs']
// }
}
}
}
})
动态导入实现按需加载:
// 路由级别代码分割
const routes = [
{
path: '/dashboard',
// 动态导入,自动创建独立的 chunk
component: () => import('@/pages/Dashboard.vue')
},
{
path: '/settings',
component: () => import('@/pages/Settings.vue')
}
]
// 组件级别代码分割
<template>
<div>
<button @click="loadChart">加载图表</button>
<component v-if="ChartComponent" :is="ChartComponent" />
</div>
</template>
<script setup>
import { ref } from 'vue'
const ChartComponent = ref(null)
const loadChart = async () => {
ChartComponent.value = (await import('@/components/Chart.vue')).default
}
</script>
23. Tree Shaking
定义: Tree Shaking 是指在打包过程中移除未使用的代码,减小最终打包体积。
原理: 基于 ES Modules 的静态分析特性,在编译时确定哪些导出被使用,移除未使用的代码。
示例:
// utils.js
export function used() {
return 'I am used'
}
export function unused() {
return 'I am unused' // 这个函数会被 Tree Shaking 移除
}
export const constant = 'constant value'
// main.js
import { used } from './utils.js'
console.log(used())
// unused 函数不会被打包,因为没有被导入使用
// 使用 Vite 构建后(简化)
function used() {
return 'I am used'
}
console.log(used())
// unused 函数不存在于打包结果中
Tree Shaking 生效条件:
// 1. 使用 ESM 格式
// ✅ 正确 - 支持 Tree Shaking
import { foo } from 'lodash-es'
// ❌ 错误 - CommonJS 不支持 Tree Shaking
const { foo } = require('lodash')
// 2. 避免副作用
// ✅ 正确 - 无副作用
export function pureFunction() {
return 42
}
// ❌ 错误 - 有副作用,可能无法被正确 Tree Shaking
export function sideEffectFunction() {
window.myGlobal = {} // 修改全局状态
return 42
}
// 3. package.json 中标注副作用
// package.json
{
"sideEffects": false // 声明所有文件无副作用
// 或
"sideEffects": ["*.css"] // 只有 CSS 文件有副作用
}
最佳实践:
// 1. 使用 ESM 版本的库
// ✅ 推荐
import merge from 'lodash-es/merge.js'
// ❌ 不推荐
import merge from 'lodash/merge.js'
// 2. 按需导入
// ✅ 推荐
import { debounce } from 'lodash-es'
// ❌ 不推荐(会导入整个库)
import _ from 'lodash-es'
// 3. 确保模块无副作用声明正确
// 在 package.json 中正确配置 sideEffects
24. 压缩代码
定义: 压缩代码是指移除代码中的空格、注释,缩短变量名等操作,减小文件体积。
压缩配置:
// vite.config.js
export default defineConfig({
build: {
// 压缩方式选择
minify: 'esbuild', // 默认,速度最快
// minify: 'terser', // 更细粒度控制
// Terser 配置
terserOptions: {
compress: {
// 生产环境移除 console
drop_console: true,
drop_debugger: true,
// 移除纯函数调用
pure_funcs: ['console.log', 'console.info'],
// 优化
passes: 2
},
format: {
// 移除注释
comments: false
},
mangle: {
// 混淆变量名(默认启用)
safari10: true // 兼容 Safari 10
}
}
}
})
esbuild vs Terser:
// esbuild 压缩(默认)
// - 速度极快(Go 编写)
// - 移除空格、注释
// - 缩短变量名
// - 不支持移除 console
// Terser 压缩
// - 速度较慢(JavaScript 编写)
// - 支持移除 console.log
// - 支持更细粒度的控制
// - 支持压缩 Passes(多次压缩优化)
25. CSS 代码分割
定义: CSS 代码分割将 CSS 提取为独立文件,支持按需加载对应的 CSS。
配置:
// vite.config.js
export default defineConfig({
build: {
// 启用 CSS 代码分割(默认 true)
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
},
// CSS 文件名格式
assetFileNames: (assetInfo) => {
if (assetInfo.name.endsWith('.css')) {
return 'css/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
}
}
}
}
})
效果说明:
/* cssCodeSplit: true 时 */
/* 动态导入的组件会提取对应的 CSS */
/* 按需加载 */
<!-- 页面加载 -->
<link rel="stylesheet" href="/css/index-abc123.css">
<!-- 异步组件加载时 -->
<link rel="stylesheet" href="/css/AsyncComponent-def456.css">
/* cssCodeSplit: false 时 */
/* 所有 CSS 合并为单个文件 */
<link rel="stylesheet" href="/css/style-xyz789.css">
六、生产环境构建
26. Vite 生产构建(vite build)
定义: vite build 命令用于将项目构建为生产环境可用的静态文件。
构建流程:
/**
* vite build 流程:
*
* 1. 加载配置(生产环境配置)
* 2. 使用 Rollup 打包
* 3. 执行 Tree Shaking
* 4. 代码分割
* 5. 压缩代码
* 6. 处理静态资源
* 7. 输出到 dist 目录
*/
构建命令:
# 基础构建
vite build
# 指定模式
vite build --mode staging
# 详细日志
vite build --debug
# 指定输出目录
vite build --outDir custom-dist
# 空输出目录前是否清空
vite build --emptyOutDir
构建配置:
// vite.config.js
export default defineConfig({
build: {
// 输出目录
outDir: 'dist',
// 静态资源目录名
assetsDir: 'assets',
// 静态资源内联阈值(字节)
assetsInlineLimit: 4096,
// 生成 source map
sourcemap: false,
// 自定义 Rollup 配置
rollupOptions: {
input: {
main: 'index.html',
admin: 'admin.html'
},
output: {
// 文件命名规则
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]'
}
},
// chunk 大小警告阈值(kb)
chunkSizeWarningLimit: 500,
// 构建前是否清空 outDir
emptyOutDir: true,
// 启用 CSS source map
cssCodeSplit: true,
// 报告压缩后文件大小
reportCompressedSize: true,
}
})
多页面应用构建:
// vite.config.js
import { resolve } from 'path'
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html'),
mobile: resolve(__dirname, 'mobile.html')
}
}
}
})
27. Vite preview
定义: vite preview 命令用于本地预览生产构建结果。
使用方式:
# 构建后预览
vite build
vite preview
# 指定端口
vite preview --port 4173
# 指定 host
vite preview --host 0.0.0.0
# 打开浏览器
vite preview --open
配置预览服务器:
// vite.config.js
export default defineConfig({
preview: {
port: 4173,
strictPort: true, // 端口被占用时报错,不自动切换
host: '0.0.0.0',
open: true,
cors: true,
headers: {
'Cache-Control': 'no-cache'
}
}
})
预览与开发服务器的区别:
/**
* vite preview vs vite dev:
*
* vite dev(开发服务器):
* - 按需编译,不打包
* - 支持 HMR
* - 使用原生 ESM
*
* vite preview(预览服务器):
* - 提供静态文件服务
* - 不支持 HMR
* - 模拟生产环境
* - 用于验证构建结果
*/
28. Rollup 打包
定义: Vite 生产环境使用 Rollup 进行打包,利用其优秀的 Tree Shaking 和代码分割能力。
Rollup 在 Vite 中的作用:
/**
* Vite 为什么生产环境使用 Rollup:
*
* 1. Rollup 专注于库/应用打包
* 2. 优秀的 Tree Shaking 能力
* 3. 成熟的代码分割策略
* 4. 丰富的插件生态
* 5. 输出格式多样(ESM, CommonJS, IIFE, UMD)
*/
// Vite 内部使用 Rollup 配置示例
// 可以通过 build.rollupOptions 自定义
export default defineConfig({
build: {
rollupOptions: {
// 外部依赖(不从 node_modules 打包)
external: ['vue', 'vue-router'],
// 插件
plugins: [
// 自定义 Rollup 插件
],
// 输出配置
output: {
format: 'es', // 输出格式
dir: 'dist',
manualChunks(id) {
// 手动分割 chunk
}
}
}
}
})
七、多框架支持
29. Vite 多框架支持
定义: Vite 通过官方插件支持多种前端框架,核心能力与框架无关。
支持的框架:
// Vue 3
import vue from '@vitejs/plugin-vue'
// React
import react from '@vitejs/plugin-react'
// Svelte
import { svelte } from '@sveltejs/vite-plugin-svelte'
// Preact
import preact from '@preact/preset-vite'
// Vue 2
import { createVuePlugin } from 'vite-plugin-vue2'
原理说明:
/**
* Vite 多框架支持原理:
*
* 1. 核心能力与框架无关
* - 模块解析、依赖预构建、HMR 等是通用的
*
* 2. 框架特定处理通过插件实现
* - .vue 文件转换 → @vitejs/plugin-vue
* - JSX/TSX 转换 → @vitejs/plugin-react
* - .svelte 文件转换 → @sveltejs/vite-plugin-svelte
*
* 3. 框架的 HMR 通过插件注入
* - 每个框架插件处理各自的 HMR 逻辑
*/
30. Vite 与 Vue
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
// Vue 编译器选项
template: {
compilerOptions: {
// 自定义元素
isCustomElement: (tag) => tag.startsWith('custom-')
}
},
// Vue SFC 选项
script: {
// 定义宏处理
defineModel: true
},
// 响应性转换
reactivityTransform: true
})
]
})
31. Vite 与 React
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
// Fast Refresh(热更新)
fastRefresh: true,
// 使用 Babel
babel: {
plugins: [
// 自定义 Babel 插件
['babel-plugin-macros', { /* options */ }]
]
},
// 或使用 SWC(更快)
// 使用 @vitejs/plugin-react-swc
})
]
})
React SWC 插件(更快):
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
export default defineConfig({
plugins: [react()],
// SWC 比 Babel 快 20x
})
32. Vite 与 Svelte
// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [
svelte({
// Svelte 编译器选项
compilerOptions: {
dev: process.env.NODE_ENV !== 'production'
}
})
]
})
33. Vite 与 Preact
// vite.config.js
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'
export default defineConfig({
plugins: [preact()]
})
八、与 Webpack 对比
34. Vite 与 Webpack 的区别
定义: Vite 和 Webpack 都是前端构建工具,但设计理念和工作方式有本质区别。
对比表格:
| 对比维度 | Vite | Webpack |
|---|---|---|
| 核心理念 | 开发环境不打包,按需编译 | 开发环境打包整个应用 |
| 开发环境 | 原生 ESM + 按需编译 | Bundle + Dev Server |
| 生产环境 | Rollup 打包 | Webpack 打包 |
| 启动速度 | 极快(1-3秒) | 较慢(10-30秒) |
| HMR | 快速、稳定 | 较慢,项目大时可能失效 |
| 打包工具 | esbuild(开发)+ Rollup(生产) | Webpack(全部) |
| 配置复杂度 | 简单 | 较复杂 |
| 插件生态 | 成长中 | 非常成熟 |
| 兼容性 | 现代浏览器 | 支持所有浏览器 |
| 代码分割 | 优秀 | 优秀 |
| Tree Shaking | 优秀 | 优秀 |
| 适合场景 | 现代项目、快速开发 | 大型复杂项目、需要高度定制 |
选择策略:
/**
* 选择 Vite 的场景:
* - 新项目,没有历史包袱
* - 追求开发体验和速度
* - 使用现代框架(Vue3, React18+)
* - 团队规模小,需要快速迭代
* - 不需要兼容老旧浏览器
*
* 选择 Webpack 的场景:
* - 已有大型 Webpack 项目
* - 需要深度定制构建流程
* - 需要支持老旧浏览器(IE11)
* - 需要成熟的插件和 loader
* - 企业级复杂构建需求
*/
35. Vite 的优势
/**
* Vite 的优势:
*
* 1. 极速启动:1-3秒启动开发服务器
* 2. 即时 HMR:模块级别热更新,保持状态
* 3. 开箱即用:TypeScript、CSS 预处理器等内置支持
* 4. 现代配置:ESM 配置文件,类型提示
* 5. 多框架支持:Vue、React、Svelte、Preact
* 6. 生产优化:Rollup 打包,Tree Shaking
* 7. 简单配置:比 Webpack 配置更简洁
* 8. 活跃社区:快速发展,文档完善
*/
36. Vite 的劣势
/**
* Vite 的劣势:
*
* 1. 生态不如 Webpack 成熟:部分 Webpack 插件没有 Vite 对应版本
* 2. 生产构建使用 Rollup:需要理解两套工具
* 3. 老旧浏览器兼容:开发环境依赖现代浏览器 ESM 支持
* 4. 大型项目冷启动:首次依赖预构建可能较慢
* 5. 定制能力:不如 Webpack 灵活
*/
37. 何时选择 Vite / Webpack
/**
* 决策树:
*
* 1. 是全新的项目吗?
* - 是 → 优先选择 Vite
* - 否 → 继续
*
* 2. 现有项目使用 Webpack 吗?
* - 是 → 评估迁移成本,谨慎考虑
* - 否 → 继续
*
* 3. 需要支持 IE11 吗?
* - 是 → 选择 Webpack
* - 否 → 继续
*
* 4. 需要特殊的构建需求吗?
* - 是 → 评估 Vite 是否满足,否则选择 Webpack
* - 否 → 选择 Vite
*
* 5. 团队对 Vite 有了解吗?
* - 是 → 选择 Vite
* - 否 → 学习成本可接受 → 选择 Vite
*/
九、配置文件与特性
38. Vite 配置文件(vite.config.js)
定义: Vite 使用 vite.config.js 作为配置文件,支持 ESM 格式和 TypeScript。
基础配置:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig(({ command, mode }) => {
// command: 'serve'(开发)或 'build'(生产)
// mode: 当前模式(development, production, 或自定义)
return {
// 项目根目录
root: process.cwd(),
// 基础路径
base: '/',
// 插件
plugins: [vue()],
// 开发服务器
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist',
sourcemap: false
},
// 路径别名
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
// CSS 配置
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/variables.scss";'
}
}
},
// 环境变量
envDir: 'env',
envPrefix: 'VITE_'
}
})
TypeScript 配置文件:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})
39. Vite 环境变量
定义: Vite 支持通过 .env 文件管理环境变量,以 VITE_ 开头的环境变量会被暴露给客户端代码。
环境变量文件:
# .env # 所有模式加载
# .env.local # 所有模式加载,被 git 忽略
# .env.development # 开发模式加载
# .env.production # 生产模式加载
# .env.staging # staging 模式加载
# 示例 .env.development
VITE_API_URL=http://localhost:3000/api
VITE_APP_TITLE=My App (Dev)
# 示例 .env.production
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
使用环境变量:
// 在代码中使用
console.log(import.meta.env.VITE_API_URL)
console.log(import.meta.env.VITE_APP_TITLE)
// 内置环境变量
console.log(import.meta.env.MODE) // 当前模式
console.log(import.meta.env.DEV) // 是否开发环境
console.log(import.meta.env.PROD) // 是否生产环境
console.log(import.meta.env.BASE_URL) // 基础路径
// TypeScript 类型定义
// env.d.ts
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
readonly MODE: string
readonly DEV: boolean
readonly PROD: boolean
readonly BASE_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
多环境配置示例:
// package.json
{
"scripts": {
"dev": "vite",
"build:dev": "vite build --mode development",
"build:staging": "vite build --mode staging",
"build:prod": "vite build --mode production",
"preview": "vite preview"
}
}
// vite.config.js 中使用环境变量
export default defineConfig(({ mode }) => {
console.log('Current mode:', mode)
return {
define: {
// 可以在构建时注入额外的变量
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
}
}
})
40. Vite 别名配置
// vite.config.js
import { resolve } from 'path'
import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
alias: [
// 基础别名
{
find: '@',
replacement: resolve(__dirname, 'src')
},
{
find: '~',
replacement: resolve(__dirname, 'src/assets')
},
// 正则表达式匹配
{
find: /^~(.*)/,
replacement: '$1'
},
// 函数方式
{
find: (id) => id.startsWith('virtual:'),
replacement: '\0virtual:'
}
]
}
})
// 使用别名
import utils from '@/utils/index.js'
import logo from '~/images/logo.png'
41. Vite 代理配置
// vite.config.js
export default defineConfig({
server: {
proxy: {
// 字符串方式
'/api': 'http://localhost:3000',
// 对象方式(更多选项)
'/api': {
target: 'http://localhost:3000',
changeOrigin: true, // 修改请求头中的 Host
secure: false, // 是否验证 SSL 证书
rewrite: (path) => path.replace(/^\/api/, ''), // 重写路径
// 配置代理超时
timeout: 5000,
// 代理 WebSocket
ws: true,
// 自定义代理逻辑
configure: (proxy, options) => {
proxy.on('error', (err, req, res) => {
console.log('proxy error:', err)
})
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('Sending Request to the Target:', req.method, req.url)
})
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log('Received Response from the Target:', proxyRes.statusCode)
})
}
},
// 正则匹配
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, '')
}
}
}
})
42. Vite CSS 处理
定义: Vite 内置 CSS 处理支持,包括 CSS 导入、CSS Modules、CSS 预处理器等。
CSS 导入:
// main.js
// 导入全局 CSS
import './styles/global.css'
// 导入 CSS Modules
import styles from './Button.module.css'
// 导入 CSS Modules(特定类名)
const { primary, large } = styles
// 在 JSX/TSX 中使用
<button className={styles.primary}>Click</button>
// 导入 Sass/Less
import './styles/variables.scss'
import './styles/theme.less'
CSS Modules:
/* Button.module.css */
.primary {
background-color: #42b883;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
}
.large {
font-size: 18px;
padding: 12px 24px;
}
<!-- Vue 中使用 -->
<template>
<button :class="$style.primary">Click</button>
</template>
// React 中使用
import styles from './Button.module.css'
function Button() {
return <button className={styles.primary}>Click</button>
}
预处理器配置:
// vite.config.js
export default defineConfig({
css: {
// 预处理器选项
preprocessorOptions: {
scss: {
// 注入全局 SCSS 变量
additionalData: `
@import "@/styles/variables.scss";
@import "@/styles/mixins.scss";
`,
// Sass 1.70+ 使用 api: 'modern'
api: 'modern-compiler'
},
less: {
additionalData: '@import "@/styles/variables.less";',
javascriptEnabled: true
},
stylus: {
additionalData: `@import "@/styles/variables.styl"`
}
},
// CSS Modules 配置
modules: {
generateScopedName: '[name]__[local]--[hash:base64:5]',
localsConvention: 'camelCase' // 类名转换为驼峰
},
// PostCSS 配置
postcss: {
plugins: [
// autoprefixer 等
]
}
}
})
43. Vite 静态资源
定义: Vite 支持多种静态资源处理方式,包括图片、字体、音频等。
资源处理方式:
// 1. 小资源内联(默认 < 4KB)
import logo from './logo.png'
// logo 会被转换为 base64 data URL
// 2. 大资源作为独立文件
import bigImage from './big-image.png'
// bigImage 是资源的公共 URL
// 3. 资源 URL 导入
import assetUrl from './asset.png?url'
// 强制作为 URL 处理,不内联
// 4. 资源原始导入
import assetRaw from './data.txt?raw'
// 导入资源内容为字符串
// 5. Worker 导入
import Worker from './worker.js?worker'
const worker = new Worker()
// 6. Web Worker 内联
import Worker from './worker.js?worker&inline'
静态资源配置:
// vite.config.js
export default defineConfig({
build: {
// 小于此值的资源会被内联为 base64(字节)
assetsInlineLimit: 4096,
// 静态资源输出目录
assetsDir: 'assets',
// 忽略资源
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
// 按类型分类输出
if (/\.(png|jpe?g|gif|svg|webp|ico)$/.test(assetInfo.name)) {
return 'images/[name]-[hash][extname]'
}
if (/\.css$/.test(assetInfo.name)) {
return 'css/[name]-[hash][extname]'
}
if (/\.(woff2?|eot|ttf|otf)$/.test(assetInfo.name)) {
return 'fonts/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
}
}
}
},
// public 目录中的文件会被复制到输出目录
publicDir: 'public'
})
44. Vite TypeScript 支持
定义: Vite 内置 TypeScript 支持,开发环境通过 esbuild 转换,生产环境通过 Rollup 处理。
TypeScript 配置:
// tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
Vue + TypeScript:
<!-- App.vue -->
<script lang="ts" setup>
import { ref } from 'vue'
const count = ref<number>(0)
function increment(): void {
count.value++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
注意事项:
/**
* Vite TypeScript 注意事项:
*
* 1. Vite 开发环境只转换 TypeScript,不做类型检查
* - 类型检查需要单独运行 tsc --noEmit
* - 或使用 vue-tsc(Vue 项目)
*
* 2. Vite 使用 esbuild 转换 TS,比 tsc 快 20-30 倍
*
* 3. tsconfig.json 中的 paths 别名需要和 vite.config.js 中的 alias 同步
*
* 4. 类型定义文件(.d.ts)需要正确配置
*/
45. Vite JSX 支持
// React JSX 配置
// vite.config.js
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()]
})
// Vue JSX 配置
// vite.config.js
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [vueJsx()]
})
// JSX 代码示例
// App.jsx
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
return () => (
<div>
<button onClick={() => count.value++}>
Count: {count.value}
</button>
</div>
)
}
})
46. Vite SSR(服务端渲染)
定义: Vite 提供 SSR 支持,可以在服务端渲染 Vue、React 等框架的应用。
SSR 基础配置:
// vite.config.js
export default defineConfig({
build: {
// SSR 构建配置
ssr: true,
rollupOptions: {
input: 'src/entry-server.js'
}
}
})
SSR 入口文件:
// entry-server.js
import { createSSRApp } from 'vue'
import App from './App.vue'
export function render() {
const app = createSSRApp(App)
// 渲染为 HTML 字符串
return { html: '<div id="app">...</div>' }
}
Node.js 服务器:
// 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) => {
// 获取 SSR 入口
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
const { html } = render()
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})
app.listen(3000, () => {
console.log('SSR Server running on http://localhost:3000')
})
}
createServer()
47. Vite 中间件
// vite.config.js
export default defineConfig({
server: {
// 自定义中间件
middlewareMode: false, // 是否仅作为中间件模式
// 通过 configureServer 添加中间件
proxy: {
'/api': 'http://localhost:3000'
}
},
plugins: [
{
name: 'custom-middleware',
configureServer(server) {
// 在 Vite 内置中间件之前
server.middlewares.use((req, res, next) => {
if (req.url === '/custom') {
res.end('Custom response')
} else {
next()
}
})
},
// 或者在 configureServer 返回函数,在内置中间件之后执行
configureServer(server) {
return () => {
server.middlewares.use((req, res, next) => {
// 在所有内置中间件之后执行
console.log('Request:', req.url)
next()
})
}
}
}
]
})
48. Vite 开发服务器
// vite.config.js
export default defineConfig({
server: {
// 服务器主机
host: '0.0.0.0', // 监听所有地址
// 端口
port: 3000,
// 严格端口(端口被占用时报错)
strictPort: false,
// 是否使用 HTTPS
https: false,
// 启动后打开浏览器
open: true,
// CORS
cors: true,
// 响应头
headers: {
'Access-Control-Allow-Origin': '*'
},
// 文件监听
watch: {
// chokidar 选项
usePolling: true, // 轮询模式(Docker 等环境需要)
interval: 100
},
// 预构建配置
warmup: {
// 预热文件(提前转换,提升首次访问速度)
clientFiles: [
'./src/components/*.vue',
'./src/layouts/*.vue'
]
},
// 文件系统访问限制
fs: {
strict: true, // 限制访问工作区外的文件
allow: ['..'], // 允许访问的额外路径
deny: ['/etc', '/root'] // 拒绝访问的路径
}
}
})
49. Vite 常见问题
Q1: HMR 不生效怎么办?
// 排查步骤:
// 1. 检查文件类型是否支持 HMR(.vue, .jsx, .css 等支持)
// 2. 检查是否有正确的 import.meta.hot.accept 处理
// 3. 检查 vite.config.js 中是否有正确的插件
// 4. 检查是否修改了不支持 HMR 的文件(如 vite.config.js)
// 手动添加 HMR 处理
// my-module.js
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
console.log('Module hot updated')
})
}
Q2: 依赖预构建失败怎么办?
// 解决方案:
// 1. 清除缓存
// rm -rf node_modules/.vite
// 2. 强制重新预构建
// vite --force
// 3. 配置中指定依赖
export default defineConfig({
optimizeDeps: {
include: ['problematic-package'],
exclude: ['another-package']
}
})
Q3: 别名在 TypeScript 中不生效?
// 确保 tsconfig.json 中的 paths 和 vite.config.js 中的 alias 一致
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// vite.config.js
export default defineConfig({
resolve: {
alias: {
'@': '/src'
}
}
})
Q4: 生产环境静态资源 404?
// 检查 base 配置
export default defineConfig({
// 如果部署在子路径下
base: '/my-app/'
})
// 检查资源路径引用
// 正确方式
import logo from './logo.png'
// 使用
<img src={logo} />
// 错误方式(不会被处理)
<img src="./logo.png" />
Q5: 环境变量在客户端无法访问?
// 只有 VITE_ 开头的环境变量会暴露给客户端
// .env
VITE_API_URL=https://api.example.com // ✅ 可访问
API_SECRET=secret123 // ❌ 不可访问
// 代码中访问
console.log(import.meta.env.VITE_API_URL) // ✅
console.log(import.meta.env.API_SECRET) // ❌ undefined