hot load那点事

热加载,最初接触的时候是使用create-react-app的时候,创建一个项目出来,修改一点代码,页面自动刷新了,贫道当时就感叹,这是造福开发者的事情。
再后来编写静态页面的时候使用 VS Code 的插件 Liver Server, 也是及时刷新,平僧幸福感慢慢,什么单不单身,狗不狗的,都不重要了。

有一天喝酒回家后,睡的特别好,醒来后突然脑袋一晃,出现一个念头,世界那么大。我想看看 hot load 是咋实现的。

当然这里有两点应该是确认

肯定是监听文件变化

WebSocket 监听服务端变化的通知,刷新文件

于是打开Liver Server 找到源码ritwickdey/vscode-live-server,再通过 lib/live-server/index.js 的标注

#!/usr/bin/env node "use strict"; /* Taken from https://github.com/tapio/live-server for modification */

找到live-server,就开始了奇妙的探索之旅。

按照正常流程打开 index.js, 先略去非核心代码:

chokidar = require('chokidar'); ...... // Setup file watcher LiveServer.watcher = chokidar.watch(watchPaths, { ignored: ignored, ignoreInitial: true }); function handleChange(changePath) { var cssChange = path.extname(changePath) === ".css" && !noCssInject; if (LiveServer.logLevel >= 1) { if (cssChange) console.log("CSS change detected".magenta, changePath); else console.log("Change detected".cyan, changePath); } clients.forEach(function(ws) { if (ws) ws.send(cssChange ? 'refreshcss' : 'reload'); }); } LiveServer.watcher .on("change", handleChange) .on("add", handleChange) .on("unlink", handleChange) .on("addDir", handleChange) .on("unlinkDir", handleChange) .on("ready", function () { if (LiveServer.logLevel >= 1) console.log("Ready for changes".cyan); }) .on("error", function (err) { console.log("ERROR:".red, err); }); return server; };

从上可以得知,通过 chokidar 监听文件或者目录,当 change|add|addDir 等等时调用 handleChange。
handleChange 判断了一下变更的文件是不是 css,然后通过 socket 发送不通的事件。

那么问题来了, 如果客服端要能接受事件,必然要创建 WebSocket 连接。当然有人说,可以轮询或者 SSE 等这种嘛。我就不这么认为。

再看一段代码

es = require("event-stream") var INJECTED_CODE = fs.readFileSync(path.join(__dirname, "injected.html"), "utf8"); ...... function inject(stream) { if (injectTag) { // We need to modify the length given to browser var len = INJECTED_CODE.length + res.getHeader('Content-Length'); res.setHeader('Content-Length', len); var originalPipe = stream.pipe; stream.pipe = function(resp) { originalPipe.call(stream, es.replace(new RegExp(injectTag, "i"), INJECTED_CODE + injectTag)).pipe(resp); }; } } send(req, reqpath, { root: root }) .on('error', error) .on('directory', directory) .on('file', file) .on('stream', inject) .pipe(res); };

可以看到,如果需要注入,就会注入代码, 这里是直接更新了 stream。
插曲, 这个 es 就是那个搞事情的 event-stream, 哈哈。

我们再看看 INJECTED_CODE 的内容

<!-- Code injected by live-server --> <script type="text/javascript"> // <![CDATA[ <-- For SVG support if ("WebSocket" in window) { (function() { function refreshCSS() { var sheets = [].slice.call( document.getElementsByTagName("link") ); var head = document.getElementsByTagName("head")[0]; for (var i = 0; i < sheets.length; ++i) { var elem = sheets[i]; head.removeChild(elem); var rel = elem.rel; if ( (elem.href && typeof rel != "string") || rel.length == 0 || rel.toLowerCase() == "stylesheet" ) { var url = elem.href.replace( /(&|\?)_cacheOverride=\d+/, "" ); elem.href = url + (url.indexOf("?") >= 0 ? "&" : "?") + "_cacheOverride=" + new Date().valueOf(); } head.appendChild(elem); } } var protocol = window.location.protocol === "http:" ? "ws://" : "wss://"; var address = protocol + window.location.host + window.location.pathname + "/ws"; var socket = new WebSocket(address); socket.onmessage = function(msg) { if (msg.data == "reload") window.location.reload(); else if (msg.data == "refreshcss") refreshCSS(); }; console.log("Live reload enabled."); })(); } // ]]> </script>

简单的来讲,如果是 refreshcss 就先删除原来的 css 标签 link, 然后插入新的,并更新
_cacheOverride 的值, 强制刷新。
否则就是 reload 整个页面。

到达这里,基本的东西就完了。 我们要好奇心多一点。我们再多看看chokidar

同理,先看 index.js
这个add方法就是添加监听的方法。

var NodeFsHandler = require('./lib/nodefs-handler'); var FsEventsHandler = require('./lib/fsevents-handler'); ...... FSWatcher.prototype.add = function(paths, _origAdd, _internal) { ...... if (this.options.useFsEvents && FsEventsHandler.canUse()) { if (!this._readyCount) this._readyCount = paths.length; if (this.options.persistent) this._readyCount *= 2; paths.forEach(this._addToFsEvents, this); } else { if (!this._readyCount) this._readyCount = 0; this._readyCount += paths.length; asyncEach(paths, function(path, next) { this._addToNodeFs(path, !_internal, 0, 0, _origAdd, function(err, res) { if (res) this._emitReady(); next(err, res); }.bind(this)); }.bind(this), function(error, results) { results.forEach(function(item) { if (!item || this.closed) return; this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item)); }, this); }.bind(this)); } return this; };

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsxfj.html