热更新(hmr)全称 Hot Module Reload,常常在构建工具里面出现。
在我们开发时候修改代码后页面会立即自动更新。
这是怎么做到的呢?
现在我们通过一行一行代码来动手操作,构建一个最简单的热更新。
问题是我们怎么做到文件变动到页面自动响应更新呢?
首先我们把整个过程分成3步:
写出伪代码:
// server
watch(file); // 1
fileContent = readFile(file) // 2
send(fileContent); // 3
nodejs 里面有个fs.watch
api。它可以监听文件变动。
(fs.FSWatcher) fs.watch(filename[, options][, listener])
node原生的 watch api,存在些许兼容性问题。 这个时候最好去找些库来实现.
使用 chokidar
来实现文件的监听
const chokidar = require('chokidar');
// One-liner for current directory
chokidar.watch('demo/chokidar').on('all', (event, path) => {
console.log(path);
});
成功监听到了文件的变动,但是发现启动服务起后,就直接打印路径。 现在我们只想要文件变更时候触发事件。把代码改成这样 all
替换成 change
去掉 event 参数.
const chokidar = require('chokidar');
// One-liner for current directory
chokidar.watch('demo/chokidar').on('change', (path) => {
console.log(path);
});
这个比较简单,因为是在服务端直接读取文件,我们就直接用 nodejs 中的 fs.readFileSync
来实现
const chokidar = require('chokidar');
const fs = require('fs');
const path = require('path');
chokidar.watch('demo/ws-chokidar').on('change', (relativePath) => {
const filePath = path.resolve(__dirname, '../../', relativePath);
const data = fs.readFileSync(filePath, 'utf-8');
console.log(data, relativePath);
ws.send(data);
});
由于监听到的 path 参数是相对路径,而readFileSync
需要读取的绝对文件路径,使用 path
进行库进行拼接。现在看看效果
当我们随意修改 change.html 内容时,控制台输出了我们的文件内容和路径。
我们知道,通过 AJAX
走 http
协议只能客户端发请求到服务器,接受响应。
那怎么让服务器主动通知客户端更新了哪些文件了?
答案就是 WebSocket
。 虽然 WebSocket
在浏览器和 node
都有api 支持,但是解密 socket
的数据还是挺麻烦。 这里我们需要用到 WebSocket
的库。
一种是 socket.io,一种是 ws。这里只用 ws 作为例子。
我们先写一个前端页面。简单构造一个 socket 发送和监听器。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>socket</title>
</head>
<body>
<button class="button">触发</button>
<h2>修改整个文件会被监听并实时改变:</h2>
<div class="response-data"></div>
<script>
var oButtons = document.getElementsByClassName("button");
var oResponseDatas = document.getElementsByClassName("response-data");
var oResponseData = oResponseDatas.length && oResponseDatas[0];
if (oButtons.length) {
oButtons[0].addEventListener("click", onClick, false);
}
function onClick() {
var socket = new WebSocket("ws://localhost:3000");
socket.open = function () {
socket.send("12dd");
};
socket.onmessage = function (event) {
// console.log(event.data);
oResponseData.innerHTML = event.data;
};
socket.onclose = function () {
console.log("closed");
};
}
</script>
</body>
</html>
每次接收到服务端数据,更新 data 后面的内容。
然后我们使用ws给服务器端构建发送和接收器:
const chokidar = require("chokidar");
const WebSocket = require("ws");
const fs = require("fs");
const path = require("path");
const wss = new WebSocket.Server({ port: 3000 });
wss.on("connection", function connection(ws) {
ws.on("message", function incoming(message) {
console.log("received: %s", message);
});
chokidar.watch("./ws-chokidar").on("change", (relativePath) => {//监听文件变化
const filePath = path.resolve(__dirname, "", relativePath);
const data = fs.readFileSync(filePath, "utf-8");
// console.log(data, relativePath);
ws.send(data);//把文件内容返回到浏览器
});
});
vite对webpack没有任何依赖, hmr, socket是它自己用ws实现的。
全部代码