hot load那点事

栏目: Html5 · 发布时间: 5年前

内容简介:热加载,最初接触的时候是使用再后来编写静态页面的时候使用 VS Code 的插件有一天喝酒回家后,睡的特别好,醒来后突然脑袋一晃,出现一个念头,世界那么大。我想看看 hot load 是咋实现的。

热加载,最初接触的时候是使用 create-react-app 的时候,创建一个项目出来,修改一点代码,页面自动刷新了,贫道当时就感叹,这是造福开发者的事情。

再后来编写静态页面的时候使用 VS Code 的插件 Liver Server , 也是及时刷新,平僧幸福感慢慢,什么单不单身,狗不狗的,都不重要了。

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

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

  1. 肯定是监听文件变化
  2. 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.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTTP Essentials

HTTP Essentials

Stephen A. Thomas、Stephen Thomas / Wiley / 2001-03-08 / USD 34.99

The first complete reference guide to the essential Web protocol As applications and services converge and Web technologies not only assume HTTP but require developers to manipulate it, it is be......一起来看看 《HTTP Essentials》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

SHA 加密
SHA 加密

SHA 加密工具