Vite利用浏览器原生ESM模块加载能力实现按需编译,开发服务器启动时间与项目规模解耦。其核心依赖esbuild做依赖预打包、Rollup做生产构建、WebSocket做HMR通信。本文从依赖预打包机制、模块图解析、HMR协议三个层面解析Vite内部工作原理。
依赖预打包与esbuild转换流水线
Vite将项目依赖分为两类处理:源码通过浏览器原生ESM按需请求编译,node_modules依赖通过esbuild预打包为单一ESM模块。预打包解决两个问题:CJS转ESM格式转换、减少HTTP请求数量(lodash等库含数百个文件)。
// vite.config.ts 预打包配置
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
optimizeDeps: {
include: ['lodash-es', 'axios'],
exclude: ['@my/local-package'],
esbuildOptions: {
target: 'es2020',
define: {
'process.env.NODE_ENV': '"development"'
},
mainFields: ['module', 'main'],
}
},
resolve: {
alias: {
'@': '/src',
'@components': '/src/components'
},
mainFields: ['module', 'jsnext:main', 'jsnext', 'main'],
conditions: ['module', 'import', 'browser', 'default']
}
})
预打包产物缓存于node_modules/.vite/deps目录,通过package.json的lockfile哈希判断是否需要重新打包。修改vite.config.ts中optimizeDeps配置或lockfile变更时触发重新预打包,浏览器请求的依赖URL附带哈希版本号强制刷新缓存。
开发服务器模块图与按需编译机制
Vite开发服务器拦截浏览器ESM请求,对源文件进行即时转换。每个.vue/.tsx/.scss文件按需编译,未导入的文件不参与编译,冷启动时间恒定在数百毫秒级。
// 浏览器请求 /src/App.tsx 时Vite内部处理流程:
// 1. 拦截GET /src/App.tsx
// 2. 识别文件类型,选择对应插件链转换
// 3. esbuild转换TSX -> JS(含JSX转换)
// 4. 重写导入路径为绝对URL(如 './utils' -> '/src/utils.ts')
// 5. 返回转换后的ESM模块
// vite.config.ts 中自定义编译插件
import { defineConfig, Plugin } from 'vite'
function customTransformPlugin(): Plugin {
return {
name: 'custom-transform',
apply: 'serve',
transform(code, id) {
if (id.endsWith('.md')) {
const html = marked(code)
return {
code: `export default ${JSON.stringify(html)}`,
map: null
}
}
},
configureServer(server) {
server.middlewares.use('/api/mock', (req, res) => {
res.end(JSON.stringify({ data: 'mock response' }))
})
}
}
}
export default defineConfig({
plugins: [customTransformPlugin()],
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
},
modules: {
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
})
HMR热更新WebSocket通信协议
Vite通过WebSocket在开发服务器和浏览器间建立双向通信,推送模块更新指令。HMR边界判定通过模块导入关系图找到最近的可接受更新的祖先模块,避免整页刷新。
// Vite HMR客户端接收的消息类型
// { type: 'connected' } 连接建立
// { type: 'update', updates: [...] } 模块更新
// { type: 'full-reload' } 整页刷新
// { type: 'prune', paths: [...] } 清理失效模块
// { type: 'error', err: {...} } 编译错误
// 组件中声明HMR边界接受
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ChartPanel',
setup() {
const data = ref([])
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
if (newModule) {
console.log('ChartPanel模块已热更新')
}
})
import.meta.hot.dispose(() => {
localStorage.setItem('chart-data', JSON.stringify(data.value))
})
import.meta.hot.data.savedData = data.value
}
return { data }
}
})
// CSS热更新通过style标签替换实现,无模块图参与
// Vite为每个CSS文件注入更新回调:
// 1. 请求新的CSS模块内容(附带时间戳参数防缓存)
// 2. 创建新的style标签
// 3. 移除旧标签
// 4. 不触发JavaScript模块重新执行
// 生产构建配置
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
'utils-vendor': ['lodash-es', 'dayjs', 'axios']
},
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]'
}
},
minify: 'esbuild',
cssCodeSplit: true,
sourcemap: 'hidden',
chunkSizeWarningLimit: 1000,
modulePreload: {
polyfill: true,
resolveDependencies: (filename, deps, { hostId, hostType }) => {
return deps.filter(dep => !dep.includes('polyfill'))
}
}
}
})
插件钩子执行顺序与中间件管线
Vite插件基于Rollup插件接口扩展,增加开发环境专属钩子。理解钩子执行顺序对编写自定义插件至关重要。
// Vite插件钩子执行顺序(开发环境)
// config - 修改Vite配置
// configResolved - 读取最终配置
// options - Rollup选项(仅构建)
// buildStart - 构建开始
// configureServer - 配置开发服务器中间件
// resolveId - 解析模块ID
// load - 加载模块内容
// transform - 转换模块代码
// transformIndexHtml - 转换index.html
// handleHotUpdate - 自定义HMR更新逻辑
function myPlugin(): Plugin {
return {
name: 'my-plugin',
enforce: 'pre',
config(config) {
return {
define: { __APP_VERSION__: '"1.0.0"' }
}
},
transform(code, id) {
if (id.endsWith('.vue')) {
return code.replace(
'',
`\n`
)
}
},
handleHotUpdate(ctx) {
if (ctx.file.endsWith('.scss')) {
ctx.server.ws.send({
type: 'custom',
event: 'style-update',
data: { file: ctx.file }
})
return []
}
}
}
}
enforce: ‘pre’确保自定义插件在Vite内置插件之前执行,适合需要拦截原始请求的场景。HMR更新范围通过模块导入图向上追溯到accept回调的最近边界,Vue/React插件已自动为组件文件注入accept逻辑。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vite-gou-jian-yin-qing-yu-da-bao-ji-zhi-yu-hmr-re-geng-xin/