内容简介:热加载,最初接触的时候是使用再后来编写静态页面的时候使用 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.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
游戏化思维
[美] 凯文·韦巴赫(Kevin Werbach)、[美] 丹·亨特(Dan Hunter) / 周逵、王晓丹 / 浙江人民出版社 / 2014-4 / 36.90
[内容简介] ●本书由开设了全世界第一个游戏化课程的沃顿商学院副教授凯文·韦巴赫和丹·亨特所著,第一次全面系统地介绍游戏化的理论,阐述了如何将游戏的理念应用到商业实践中。 ●作者指出,在商业竞争日益激烈的今天,传统的激励方式渐渐失效,未来的管理将更多地建立在员工和消费者的内在动机和自我激励上。这些制作精良、设计巧妙的游戏建立在多年来对人类动机和人类心理的研究基础之上,可以最大限度地激发......一起来看看 《游戏化思维》 这本书的介绍吧!