小Cの已经记不起来的博客

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:' +

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放