hot load那点事

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

前言



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


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


Live Server



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


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


  1. 肯定是监听文件变化
  2. WebSocket 监听服务端变化的通知,刷新文件


于是打开Live 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



到达这里,基本的东西就完了。 我们要好奇心多一点。我们再多看看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.


写在最后



不忘初衷,有所得,而不为所累,如果你觉得不错,你的一赞一评就是我前行的最大动力。


技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,一起学习。


相关文章
|
存储 PyTorch 算法框架/工具
Error(s) pytorch 加载checkpoint state_dict出错:Missing key(s) && Unexpected key(s) in state_dict
Error(s) pytorch 加载checkpoint state_dict出错:Missing key(s) && Unexpected key(s) in state_dict
724 0
Error(s) pytorch 加载checkpoint state_dict出错:Missing key(s) && Unexpected key(s) in state_dict
Warning: Can save best model only with val_acc available, skipping
本文解决了在使用DenseNet网络结构保存最优模型时出现的"Warning: Can save best model only with val_acc available, skipping"问题,方法是将`ModelCheckpoint`回调函数中的`monitor`参数值从`val_acc`改为`val_accuracy`。
|
8月前
|
机器学习/深度学习 PyTorch 算法框架/工具
通过实例学习Pytorch加载权重.load_state_dict()与保存权重.save()
通过实例学习Pytorch加载权重.load_state_dict()与保存权重.save()
111 0
|
8月前
|
机器学习/深度学习 人工智能
【CatBoost报错解决】CatBoostError: Bad value for num feature[non default doc idx=0,feature idx=19]=
【CatBoost报错解决】CatBoostError: Bad value for num feature[non default doc idx=0,feature idx=19]=
|
运维 算法 数据挖掘
Clustering-Base and Classification -Base Approaches|学习笔记
快速学习 Clustering-Base and Classification -Base Approaches
Clustering-Base and Classification -Base Approaches|学习笔记
|
数据可视化 PyTorch 算法框架/工具
Pychram Pytorch Tensorboard 报错 “No dashboards are active for the current data set.“ 解决方案
Pychram Pytorch Tensorboard 报错 “No dashboards are active for the current data set.“ 解决方案
Pychram Pytorch Tensorboard 报错 “No dashboards are active for the current data set.“ 解决方案
《Audio Tagging with Compact Feedforward Sequential Memory Network and Audio-to-Audio Ratio Based Data Augmentation》电子版地址
Audio Tagging with Compact Feedforward Sequential Memory Network and Audio-to-Audio Ratio Based Data Augmentation
86 0
《Audio Tagging with Compact Feedforward Sequential Memory Network and Audio-to-Audio Ratio Based Data Augmentation》电子版地址
|
存储 索引
Elastic:data_hot,data_warm,data_cold角色有什么用
data_hot:热点数据节点,热点数据节点在进入es时存储时间序列数据,热层必须快速读取和写入,并且需要更多的硬件资源 data_warm;暖数据节点,存储不再定期更新但仍在查询的索引。查询两的频率通常低于索引出于热层时的频率,性能较低的硬件通常可用于此层中的节点 data_cold:冷数据节点,存储访问频率较低的只读索引,磁层使用性能较低的硬件,并且可以利用可搜索快照索引来最小化所需的资源
181 0
Elastic:data_hot,data_warm,data_cold角色有什么用
|
机器人
GazeboRosControlPlugin::Load 函数详解
GazeboRosControlPlugin::Load 函数详解
GazeboRosControlPlugin::Load 函数详解
|
算法框架/工具 Windows

热门文章

最新文章