核心功能
模块热替换(HMR)
模块热替换(Hot Module Replacement,HMR)是 Vite 的核心特性之一。它允许在运行时更新模块而无需完全刷新页面,从而保留应用状态。
HMR 工作原理
Vite 通过原生 ESM 实现了极速的 HMR:
- 开发服务器建立 WebSocket 连接
- 文件修改触发服务器推送更新事件
- 浏览器接收事件并请求更新后的模块
- 新模块替换旧模块,应用状态保持不变
框架 HMR 支持
Vite 为流行框架提供官方 HMR 集成:
| 框架 | 官方插件 | 特点 |
|---|---|---|
| Vue | @vitejs/plugin-vue | 单文件组件完整支持 |
| React | @vitejs/plugin-react | Fast Refresh |
| React SWC | @vitejs/plugin-react-swc | 基于 SWC,更快 |
| Preact | @prefresh/vite | 轻量级 |
| Svelte | vite-plugin-svelte | 官方支持 |
手动处理 HMR
对于自定义模块,可以使用 Vite 提供的 HMR API:
// 检查当前模块是否支持 HMR
if (import.meta.hot) {
// 接受自身更新
import.meta.hot.accept()
// 接受特定依赖的更新
import.meta.hot.accept('./dependency.js', (newModule) => {
// 使用更新后的模块
console.log('dependency updated:', newModule)
})
// 监听数据变化
import.meta.hot.on('vite:beforeUpdate', (data) => {
console.log('before update:', data)
})
// 清理副作用
import.meta.hot.dispose(() => {
// 清理定时器、事件监听等
clearInterval(timer)
})
}
TypeScript 支持
Vite 原生支持 TypeScript,无需额外配置。
仅转译,不类型检查
Vite 使用 esbuild 转译 TypeScript,速度比 tsc 快 20-30 倍。但 Vite 不进行类型检查,原因如下:
- 转译可以按文件进行,与 Vite 的按需编译模型完美契合
- 类型检查需要了解整个模块图,会显著降低速度
推荐的类型检查方案
方案一:IDE 实时检查
现代 IDE(VS Code、WebStorm)会在编辑时实时显示类型错误。
方案二:构建时检查
{
"scripts": {
"build": "tsc --noEmit && vite build",
"type-check": "tsc --noEmit"
}
}
方案三:开发时检查(使用插件)
npm install -D vite-plugin-checker
// vite.config.js
import { defineConfig } from 'vite'
import checker from 'vite-plugin-checker'
export default defineConfig({
plugins: [
checker({ typescript: true }),
],
})
重要编译器选项
{
"compilerOptions": {
// 必须设置为 true
// esbuild 只执行转译,不支持某些需要类型信息的功能
"isolatedModules": true,
// ES2022 或更高版本默认为 true
// 与 TypeScript 4.3.2+ 行为一致
"useDefineForClassFields": true,
// 跳过库类型检查(避免依赖类型问题)
"skipLibCheck": true
}
}
客户端类型
为获得客户端代码的类型支持,添加 vite/client 类型:
{
"compilerOptions": {
"types": ["vite/client"]
}
}
或使用三斜线指令:
/// <reference types="vite/client" />
vite/client 提供以下类型:
- 资源导入(如
.svg文件) import.meta.env的类型import.meta.hotHMR API 的类型
CSS 处理
Vite 对 CSS 提供开箱即用的支持。
基础 CSS
导入 .css 文件会自动将内容注入页面:
import './style.css'
CSS 预处理器
Vite 内置支持 Sass、Less、Stylus:
# Sass
npm install -D sass-embedded # 或 sass
# Less
npm install -D less
# Stylus
npm install -D stylus
使用:
<style lang="scss">
$primary: #42b983;
.container {
background: $primary;
// 嵌套规则
.title {
font-size: 20px;
}
}
</style>
CSS Modules
以 .module.css 结尾的文件被视为 CSS Modules:
/* example.module.css */
.red {
color: red;
}
.bold {
font-weight: bold;
}
import classes from './example.module.css'
document.getElementById('app').className = classes.red
启用 camelCase 命名:
// vite.config.js
export default defineConfig({
css: {
modules: {
localsConvention: 'camelCaseOnly',
},
},
})
// .apply-color -> applyColor
import { applyColor } from './example.module.css'
CSS 内联
使用 ?inline 查询参数阻止 CSS 自动注入:
import './foo.css' // 自动注入到页面
import styles from './bar.css?inline' // 不注入,返回 CSS 字符串
PostCSS
如果项目包含有效的 PostCSS 配置,Vite 会自动应用:
// postcss.config.js
export default {
plugins: {
'postcss-preset-env': {},
autoprefixer: {},
},
}
静态资源处理
资源导入
导入静态资源会返回解析后的 URL:
import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
查询参数修饰符
| 查询参数 | 说明 | 示例 |
|---|---|---|
?url | 显式作为 URL 导入 | import url from './asset.js?url' |
?raw | 作为字符串导入 | import content from './file.txt?raw' |
?inline | 内联为 base64 | import svg from './icon.svg?inline' |
资源内联
小于 build.assetsInlineLimit(默认 4KB)的资源会被内联为 base64:
// vite.config.js
export default defineConfig({
build: {
assetsInlineLimit: 4096, // 4KB
},
})
public 目录
public 目录下的文件会原样复制到输出目录,不会被处理:
public/
├── favicon.ico
├── robots.txt
└── images/
└── logo.png
引用 public 目录资源:
<!-- 使用根路径引用 -->
<img src="/images/logo.png" />
JSON 导入
JSON 文件可以直接导入,支持具名导入:
// 导入整个对象
import json from './example.json'
// 具名导入(有助于 tree-shaking)
import { field } from './example.json'
Glob 导入
Vite 支持通过 import.meta.glob 从文件系统导入多个模块:
基础用法
// 懒加载模式
const modules = import.meta.glob('./dir/*.js')
// 结果:
// {
// './dir/foo.js': () => import('./dir/foo.js'),
// './dir/bar.js': () => import('./dir/bar.js'),
// }
立即加载
const modules = import.meta.glob('./dir/*.js', { eager: true })
// 结果:
// {
// './dir/foo.js': { /* 模块内容 */ },
// './dir/bar.js': { /* 模块内容 */ },
// }
具名导入
const modules = import.meta.glob('./dir/*.js', {
import: 'setup',
eager: true,
})
// 结果:
// {
// './dir/foo.js': /* foo.js 的 setup 导出 */,
// './dir/bar.js': /* bar.js 的 setup 导出 */,
// }
多个模式
const modules = import.meta.glob([
'./dir/*.js',
'./another/*.js',
])
排除文件
const modules = import.meta.glob([
'./dir/*.js',
'!**/bar.js', // 排除 bar.js
])
动态导入
Vite 支持动态导入变量:
const module = await import(`./dir/${file}.js`)
动态导入规则
为安全起见,动态导入必须满足以下规则:
- 必须以
./或../开头 - 必须包含文件扩展名
- 同一目录的导入必须指定文件名模式
// ✅ 有效
import(`./dir/${foo}.js`)
import(`./prefix-${foo}.js`)
// ❌ 无效
import(`${foo}.js`) // 缺少 ./ 或 ../
import(`./dir/${foo}`) // 缺少扩展名
import(`./${foo}.js`) // 同一目录缺少前缀
Web Workers
构造函数方式(推荐)
const worker = new Worker(new URL('./worker.js', import.meta.url))
// 模块 Worker
const moduleWorker = new Worker(
new URL('./worker.js', import.meta.url),
{ type: 'module' }
)
查询后缀方式
import MyWorker from './worker?worker'
const worker = new MyWorker()
Worker 选项
// 内联为 base64
import InlineWorker from './worker?worker&inline'
// 获取 URL
import WorkerUrl from './worker?worker&url'
构建优化
自动优化
Vite 在构建时自动应用以下优化:
- CSS 代码分割:异步 chunk 中的 CSS 会被提取为单独文件
- 预加载指令:自动生成
<link rel="modulepreload"> - 异步 chunk 加载优化:预加载共享 chunk,减少网络往返
代码分割
手动控制代码分割:
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
// 将 vue 相关库打包在一起
vendor: ['vue', 'vue-router', 'pinia'],
// 按路由分割
'route-user': ['./src/views/User.vue'],
},
},
},
},
})
动态导入分割
// 路由懒加载
const User = () => import('./views/User.vue')
// 组件懒加载
const AsyncModal = defineAsyncComponent(() =>
import('./components/Modal.vue')
)
小结
本章我们学习了 Vite 的核心功能:
- HMR:极速的热模块替换,保留应用状态
- TypeScript:原生支持,仅转译不类型检查
- CSS:预处理器、CSS Modules、PostCSS 开箱即用
- 静态资源:智能处理、内联优化、public 目录
- Glob 导入:批量导入模块的强大功能
- Web Workers:多种方式创建 Worker
- 构建优化:自动代码分割和预加载
这些核心功能使 Vite 成为一个强大而灵活的构建工具,能够处理现代 Web 开发的各种需求。