vite
简易 Vite 热更新脚本
简化版的 Vite 热更新(HMR)机制,模拟 Vite 的按需编译和热更新流程。
核心实现
js
// server.js - 开发服务器
const fs = require('fs')
const path = require('path')
const chokidar = require('chokidar') // 文件监听
const WebSocket = require('ws') // WebSocket 服务
const express = require('express')
const app = express()
const port = 3000
// 1. 创建 WebSocket 服务
const wss = new WebSocket.Server({ port: 8080 })
// 2. 文件监听器
const watcher = chokidar.watch('./src', {
ignored: /(^|[\/\\])\../, // 忽略隐藏文件
persistent: true,
})
// 3. 静态文件服务
app.use(express.static('public'))
// 4. 自定义中间件:处理 ESM 请求
app.use('/src', (req, res, next) => {
const filePath = path.join(__dirname, 'src', req.path)
console.log('filePath: ', filePath)
if (fs.existsSync(filePath)) {
// 模拟按需编译(实际 Vite 会在这里做转换)
const content = fs.readFileSync(filePath, 'utf-8')
res.type('application/javascript').send(content)
} else {
next()
}
})
// 5. 文件变化时的 HMR 逻辑
watcher.on('change', (filePath) => {
console.log(`File changed: ${filePath}`)
// 转换为浏览器可识别的模块路径
const modulePath = `/src/${path.relative(path.join(__dirname, 'src'), filePath)}`
// 通过 WebSocket 通知客户端
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(
JSON.stringify({
type: 'update',
path: modulePath,
})
)
}
})
})
app.listen(port, () => {
console.log(`Dev server running at http://localhost:${port}`)
})
js
// client.js - 浏览器端 HMR 逻辑
const socket = new WebSocket('ws://localhost:8080')
socket.addEventListener('message', async (event) => {
const data = JSON.parse(event.data)
if (data.type === 'update') {
console.log(`[HMR] Updating module: ${data.path}`)
try {
// 使用动态 import 获取新模块
const newModule = await import(`${data.path}?t=${Date.now()}`)
// 这里应该根据实际应用状态更新模块
if (window.__hmr__ && window.__hmr__[data.path]) {
window.__hmr__[data.path](newModule)
}
console.log(`[HMR] Updated: ${data.path}`)
} catch (err) {
console.error(`[HMR] Failed to update: ${data.path}`, err)
}
}
})
// 保存模块的更新函数
window.__hmr__ = {}
HTML 示例 (src/index.html)
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
<meta name="screen-orientation" content="portrait" />
<title>Vite-like HMR Demo</title>
<script type="module">
import './client.js' // 注入 HMR 客户端
// 示例模块
import * as counter from './src/counter.js'
// 注册 HMR 更新函数
window.__hmr__['/src/counter.js'] = (newModule) => {
// 实际应用中这里会更新 UI 状态
document.getElementById('count').textContent = newModule.count
console.log('Counter module updated:', newModule)
}
document.getElementById('count').textContent = counter.count
</script>
</head>
<body>
<h1>Counter: <span id="count">0</span></h1>
<div></div>
</body>
</html>
示例模块 (src/counter.js)
js
export let count = 32142323323
// HMR 支持
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 在真实 Vite 中会处理状态保留
console.log('Counter module hot updated')
})
}
工作原理说明
- 开发服务器:
- 启动 Express 静态服务器
- 监听文件变化 (使用 chokidar)
- 提供 WebSocket 服务用于 HMR 通信
- HMR 流程:
- 当文件修改时,服务器通过 WebSocket 通知客户端
- 客户端收到通知后,使用动态
import()
重新加载模块 - 调用预先注册的更新函数应用变更
- 与真实 Vite 的区别:
- 没有实现真正的按需编译 (如 JSX/TS 转换)
- 缺少依赖图分析和更智能的更新策略
- 没有实现状态保留等高级功能
如何使用
安装依赖:
sh
npm install express ws chokidar
项目结构:
sh
your-project/
├── public/
│ ├── index.html
│ └── client.js
├── src/
│ └── counter.js
└── server.js
启动开发服务器:
sh
node server.js
TreeShaking按需加载
sh
npm install -D unplugin-vue-components
ts
import Components from 'unplugin-vue-components/vite'
import {VantResolver, ElementPlusResolver, VueUseComponentsResolver} from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
vueJsx(),
// vueDevTools(),
Components({
resolvers: [VantResolver(),ElementPlusResolver()],
}),
],
})
运行后,会自动生成components.d.ts