Skip to content

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')
	})
}

工作原理说明

  1. 开发服务器
    • 启动 Express 静态服务器
    • 监听文件变化 (使用 chokidar)
    • 提供 WebSocket 服务用于 HMR 通信
  2. HMR 流程
    • 当文件修改时,服务器通过 WebSocket 通知客户端
    • 客户端收到通知后,使用动态 import() 重新加载模块
    • 调用预先注册的更新函数应用变更
  3. 与真实 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