衔接上文,获取修改文件的路径后,我们便可以开始开发热更新插件了
思路
- 文件监听到修改文件的路径,执行回调,执行打包命令,带上包名
- 增量打包更改的文件
- 构建工具埋点update事件,热更新在update时通知前端
- 前端接收变更内容,增量更新
思考
- 增量打包前面已经实现了
- 埋点的依据是是否为增量打包,即是否传入了入口文件和修改文件
- 前端如何直到服务端发生更新?
1.用户触发行为,也就是事件监听
2.递归setTimeout
3.setIntVal
4.http长连接
5.websocket
毫无疑问,我们选择websocket通知前端更新 - 通知什么内容呢?
1.更改的代码
2.新产生的包名 - 前端获取代码后如何操作更新呢?
此处做法是有入口文件重新执行一遍,由于未更改的包都有module缓存,所以会使用缓存的值,我们把修改文件的代码块替换,重新执行一遍即可获取新module
实现
构建工具埋点
/**
* 1.存在更改文件路径
* 2.文件非新增文件
* 3.本地缓存还在,能够增量更新
*/
const pathIndex = pathIndexMap[getFilePath(changeFilePath)];
if (changeFilePath && pathIndex !== undefined && moduleFuncCache.length) {
moduleFuncCache[pathIndex] = singleCodeSplicing(changeFilePath);
execHook("update", moduleFuncCache, pathIndex);
} else {
// 为每个require的模块拼接代码,为其提供module实例,并返回module.exports
codeSplicing(path);
}
构建工具添加update事件插件处理
即https://www.jianshu.com/p/d120cde6dae0的ctx添加update项
update: {
pluginMap: {},
tap(pluginName, callback) {
this.pluginMap[pluginName] = callback;
},
},
插件代码
class HotModuleReplacementPlugin {
constructor() {
const wsConnectList = [];
//接收
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
wsConnectList.push(ws);
ws.on("message", (data) => {
// fs.writeFileSync("./log.conf", data);
log("messagedsadas");
log(data);
wsConnectList.forEach((curWs) => {
curWs.send(data);
});
});
});
wss.on("error", (err) => {
console.log(err);
});
}
apply(compiler) {
const that = this;
compiler.update.tap(
"HotModuleReplacementPlugin",
(moduleFuncCache, pathIndex) => {
const newCode = moduleFuncCache[pathIndex];
const updateCode = `
moduleCache[${pathIndex}] = undefined
formatModuleFuncCache[${pathIndex}] = ${newCode}
//执行入口文件代码
formatModuleFuncCache[${moduleFuncCache.length - 1}]()
`;
const ws = new WebSocket("ws://localhost:8080");
ws.on("open", () => {
ws.send(JSON.stringify({ code: updateCode }));
});
}
);
}
}
我们可以看到,在打包时会初始化该插件,则开启一个websocket服务,在update时依然会初始化该实例,会报错端口占用(被之前的websocket),我选择拦截报错。
热更新时会触发update事件,执行我们所有插件注册的update回调,此时我们获取更改的路径,读取其内容,替换原来的模块代码,并发送给前端重新执行。
测试

SDGIF_Rusult_1.gif
由图我们可以看到:修改文件并保存,触发文件监听,内部会重新打包修改内容,然后将变更内容通过websocket发送给前端,前端只处理变更内容,不会刷新页面(input框的文字还保留),至此我们已经实现了一个commonjs构建工具最常用的功能!