WebSocket 实时通信与 热更新 应用
起因是这样的:最近在改一个小项目,纯静态的 HTML + JS,没上任何构建工具。每次改完代码都得切到浏览器按一下 F5,改一下午下来光刷新就按了上百次,人都麻了。想着 Vite 那个热更新用起来不是挺舒服的吗,一查资料才发现它底层靠的就是 WebSocket,那干脆自己动手写个乞丐版,顺便把 WebSocket 这东西摸熟一点。
为什么是 WebSocket,不是轮询
最朴素的方案是轮询:前端 setInterval 每秒发个请求问一句“文件变了没”,大部分时候都是白问,服务端白回,还做不到真正实时。WebSocket 就不一样了,握手一次之后连接一直挂着,服务端那边一有变化随时往下推,双向通道,这才是“推送”该有的样子。热更新本质上就是三步:服务端发现文件变了 → 通过 WebSocket 把消息推给浏览器 → 浏览器只更新该更新的部分。
服务端:静态服务 + 文件监听
思路很简单,用 Node 写一个静态文件服务器,再用 chokidar 盯着目录,文件一变就给所有连着的客户端发消息。先装依赖:
npm init -y
npm install ws chokidar
WebSocket 库就用 ws,够轻,socket.io 那种全家桶这里用不上。不装 chokidar 用 Node 自带的 fs.watch 也能凑合,但尤其是 Windows 上,改一次文件能触发好几次回调,还得自己 debounce,chokidar 都帮你处理好了,别折腾了。
然后是 server.js:
const http = require('http')
const fs = require('fs')
const path = require('path')
const { WebSocketServer } = require('ws')
const chokidar = require('chokidar')
const PORT = 3000
const root = process.cwd()
const MIME = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
}
const server = http.createServer((req, res) => {
const url = decodeURIComponent(req.url.split('?')[0])
const file = path.join(root, url === '/' ? 'index.html' : url)
fs.readFile(file, (err, data) => {
if (err) {
res.writeHead(404)
return res.end('Not Found')
}
// html 的话把热更新脚本插到 </body> 前面
if (file.endsWith('.html')) {
const inject = `<script>new WebSocket('ws://' + location.host).onmessage = e => {
if (e.data === 'reload') location.reload()
}</script>`
data = data.toString().replace('</body>', inject + '\n</body>')
}
res.writeHead(200, { 'Content-Type': MIME[path.extname(file)] || 'application/octet-stream' })
res.end(data)
})
})
const wss = new WebSocketServer({ server })
chokidar.watch(root, { ignored: /node_modules/ }).on('change', file => {
// css 变了发 'css',其他变了发 'reload'
const msg = file.endsWith('.css') ? 'css' : 'reload'
wss.clients.forEach(client => {
if (client.readyState === 1) client.send(msg)
})
console.log('[hot]', file, '->', msg)
})
server.listen(PORT, () => console.log('http://localhost:' +
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。