内容简介:热加载,最初接触的时候是使用再后来编写静态页面的时候使用 VS Code 的插件有一天喝酒回家后,睡的特别好,醒来后突然脑袋一晃,出现一个念头,世界那么大。我想看看 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; };
可以看到这里有两种handler,NodeFsHandler和FsEventsHandler。 还没没有得到是咋监听的,那么继续go on, 先看看NodeFsHandler._addToNodeFs。
打开 chokidar/lib/nodefs-handler.js
_addToNodeFs ==> _handleFile ==> _watchWithNodeFs ==> setFsWatchListener ==> createFsWatchInstance
var fs = require('fs'); ...... function createFsWatchInstance(path, options, listener, errHandler, emitRaw) { var handleEvent = function(rawEvent, evPath) { listener(path); emitRaw(rawEvent, evPath, {watchedPath: path}); // emit based on events occurring for files from a directory's watcher in // case the file's watcher misses it (and rely on throttling to de-dupe) if (evPath && path !== evPath) { fsWatchBroadcast( sysPath.resolve(path, evPath), 'listeners', sysPath.join(path, evPath) ); } }; try { return fs.watch(path, options, handleEvent); } catch (error) { errHandler(error); } }
调用的就是fs模块的 watch 。
呵呵,感觉自己读书少,还是得多看文档。
我们再看看FsEventsHandler
_addToFsEvents ==>_watchWithFsEvents==> createFSEventsInstance==>setFSEventsListener
try { fsevents = require('fsevents'); } catch (error) { if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error) } // Returns new fsevents instance function createFSEventsInstance(path, callback) { return (new fsevents(path)).on('fsevent', callback).start(); }
那我们再接着看看fsevents
/* jshint node:true */ 'use strict'; if (process.platform !== 'darwin') { throw new Error(`Module 'fsevents' is not compatible with platform '${process.platform}'`); } const { stat } = require('fs'); const Native = require('./fsevents.node'); const { EventEmitter } = require('events'); const native = new WeakMap(); class FSEvents { constructor(path, handler) { if ('string' !== typeof path) throw new TypeError('path must be a string'); if ('function' !== typeof handler) throw new TypeError('function must be a function'); Object.defineProperties(this, { path: { value: path }, handler: { value: handler } }); } start() { if (native.has(this)) return; const instance = Native.start(this.path, this.handler); native.set(this, instance); return this; }
- 平台只支持darwin,这是嘛呢,我问node开发,告诉我大致是Mac OS吧,那我就相信吧。
- require(‘./fsevents.node’) 引用的是c++扩展
- Native.start(this.path, this.handler) 就是监听,哦哦,原来是这样。
最后我们打开 webpack-dev-server 的 /lib/Server.js 文件。
const watcher = chokidar.watch(watchPath, options); watcher.on('change', () => { this.sockWrite(this.sockets, 'content-changed'); });
也是这个chokidar, 那么我感觉我能做好多事情了。
亲,你做一个修改后直接发布的应用吧,好歹,好歹。
当然这里,只是弄明白监听和通知的大概。
等有时间,好好研究一下webpack-dev-server.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
创新者的窘境(全新修订版)
克莱顿•克里斯坦森 / 胡建桥 / 中信出版社 / 2014-1-1 / 48.00元
全球商业领域中,许多企业曾叱咤风云,但面对市场变化及新技术的挑战,最终惨遭淘汰。究其原因,竟然是因为它们精于管理,信奉客户至上等传统商业观念。这就是所有企业如今都正面临的“创新者的窘境”。 在《创新者的窘境》中,管理大师克里斯坦森指出,一些看似很完美的商业动作——对主流客户所需、赢利能力最强的产品进行精准投资和技术研发——最终却很可能毁掉一家优秀的企业。他分析了计算机、汽车、钢铁等多个行业的......一起来看看 《创新者的窘境(全新修订版)》 这本书的介绍吧!