固然这里更可能是菜狗子的一家之言,片面不全,竟然还有一堆谬误。欢迎斧正。javascript
既然说到热更新,咱们不妨扩展下,补充下前端自动更新的实现。 我的才疏学浅,见过的方式大体分两种html
bowersync
的方式,直接reload,简单粗暴,规避了许多问题webpack-dev-server
的HMR
简单讨论下webpack-hot-middleware
到底是怎么实现了热更新。前端
这里咱不讨论如何替换和覆盖以前执行的结果java
我的理解:其实就是一个简单的事件机制node
本质就是一个链接往前端发数据webpack
// server 端
...
publish: function(payload) {
/** * erveryClient 就是对每一个链接都写入。 * 连接报头,确保连接不会被断开 * Content-Type: 'text/event-stream;charset=utf-8', * Connection: 'keep-alive', 这个只会在http1开启 */
everyClient(function(client) {
client.write('data: ' + JSON.stringify(payload) + '\n\n');
});
},
...
复制代码
对接受到数据进行处理。固然这里的数据其实约定过格式。因为这部分代码是运行在前端,剩下的就是基础的dom操做云云了。c++
/** * @see https://github.com/webpack-contrib/webpack-hot-middleware/blob/master/client.js */
function processMessage(obj) {
switch (obj.action) {
case 'building':
if (options.log) {
console.log(
'[HMR] bundle ' +
(obj.name ? "'" + obj.name + "' " : '') +
'rebuilding'
);
}
break;
// ...
default:
if (customHandler) {
customHandler(obj);
}
}
if (subscribeAllHandler) {
subscribeAllHandler(obj);
}
}
复制代码
以前采用 socket.io(目前webpack-dev-server你就能找到相似实现的方式),如今改用EventSource方式。git
顺带一提,里面实现的一套提升稳定性的机制。github
// client
// 每次来信息更新最新活动时间,定时检查是否超出超时时间
function handleOnline() {
if (options.log) console.log('[HMR] connected');
lastActivity = new Date();
}
function handleMessage(event) {
lastActivity = new Date();
for (var i = 0; i < listeners.length; i++) {
listeners[i](event);
}
}
var timer = setInterval(function() {
if (new Date() - lastActivity > options.timeout) {
handleDisconnect();
}
}, options.timeout / 2);
// server
// 定时给前端发信息,用于更新前端的活动时间
...
var interval = setInterval(function heartbeatTick() {
everyClient(function(client) {
client.write('data: \uD83D\uDC93\n\n');
});
}, heartbeat).unref();
...
复制代码
简单讨论完前端的热更新基础以后,咱们来了解下node层面的热更新。web
首先,咱们遇到第一个问题,如何将代码更新到运行中的程序中(emm这种表述怪怪的)
evel
执行文件Function
构造函数,建立函数vm
模块执行代码我试验的时候发现,其实有以下的一个小问题
const vm = require("vm");
var a = 1;
var b = 1;
var c = 1;
d = 1;
vm.runInNewContext(
` console.log('vm',d) console.log('vm',typeof a); a = 2; console.log('vm',a);`,
global
);
console.log("a", a);
console.log("-------");
eval(` console.log('eval',typeof b); b = 2; console.log('eval',b) `);
console.log("b", b);
console.log("-------");
const test = Function(
` console.log('function',d) console.log('function',typeof c); c = 2; console.log('function',c)`
);
test();
console.log("c", c);
/** * 输出结果 vm 1 vm undefined vm 2 a 1 ------- eval number eval 2 b 2 ------- function 1 function undefined function 2 c 1 */
复制代码
以上
Function执行结果
我发现与浏览器端并不一致,细究缘由: 1.Function
只能读取全局变量与其内部的变量 2. node加载执行都会包裹一层函数,而咱们在浏览器中直接在全局声明一个变量至关于为当前全局对象附加一个属性,值为变量值。这里看变量d
就能比较清楚的知道。
回到正题,其实咱们都知道关于加载模块,其实node中自己就提供了一个超级好的方法,能够引入一个文件(js,node等)基于这个咱们彷佛就能很简单就能够实现拍黄片那样的热更新了~咱们不妨先一块儿看下node中是怎么实现的require
的
Talk is cheap ,show me the code. 先扔两个源码地址
如今咱们深刻下,看下node是怎么处理js的模块的,估计我写的也不咋地,你们能够先看下阮一峰大佬的讲解 不过好像他的版本有点点老TAT
node模块加载部分的一些源码,简单讲下整个程序的运行,其实就是程序引入初始化一个main module以后,require的时候每一个引用文件在新建module,同时缓存,记录关系,最后造成一个树形结构。整个过程能够从这里起步开始看。
/** * 删除了debug的一些输出具体,具体要看能够看下源码 * @link https://github.com/nodejs/node/blob/0817840f775032169ddd70c85ac059f18ffcc81c/lib/internal/modules/cjs/loader.js#L874:33 */
Module._load = function(request, parent, isMain) {
const filename = Module._resolveFilename(request, parent, isMain);
/** * 会缓存一次执行结果,因此每一个模块引入只会被执行一次。 * 若是想再次执行须要清楚掉 * 这个cache 会被代理到require上 * 在reqire定义那能够看到:`require.cache = Module._cache;` */
const cachedModule = Module._cache[filename];
if (cachedModule) {
// 这里会记录一次模块的引用关系,对gc回收的引用有影响。
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
const mod = NativeModule.map.get(filename);
if (mod && mod.canBeRequiredByUsers) {
return mod.compileForPublicLoader(experimentalModules);
}
/* * Don't call updateChildren(), Module constructor already does. * 上面这个是原注释。就不翻译了 */
const module = new Module(filename, parent);
if (isMain) {
// 要项目的引用结构其实能够mainModule,递归打印
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
/** * 有兴趣能够看下这个函数 * 这里我只大概梳理下加载过程 * 1. 设定引用可能的文件夹(在module的paths里你能够很清楚的看到结果) * + 所在目录 * + 所在目录的node_modules * + /node_modules 目录 * 2. 获取文件后缀(最后一个“.”开始的内容,tips:以“.”不算哈,会被排除) * 3. 根据文件后缀调用预设的函数解析。 * * 其实这个过程会有一堆的尝试操做。这里不作过多赘述。 * 自行查看'tryPackage,tryFile,tryExtensions' * 因此为了性能能好一丢丢,你们能够把引用尽可能写的清晰。 * 省去程序猜你引用的路径,还要读package.json云云 ***** ***** * emmm 这里其实他还约定了一个experimentalModules * @link https://github.com/nodejs/node-eps/blob/master/002-es-modules.md */
tryModuleLoad(module, filename);
return module.exports;
};
复制代码
这里在简单补充下js文件的解析过程
首先咱们的文件其实都是一些文本信息,首先会被包裹在一个函数里。
tips:(在字符串前加上 `(function (exports, require, module, __filename, __dirname) { `尾部加上后`\n}`);
而后依据是否修改过包裹方式调用`compileFunction` 或者vm模块的`runInThisContext`方法,
emmm这两个方法都是c++那边实现了,就不是很看得懂了TAT
@link https://github.com/nodejs/node/blob/5f8ccecaa2e44c4a04db95ccd278a7078c14dd77/src/node_contextify.h。
复制代码
emmmm 到这里咱们大概能知道node里模块是怎么加载的。大概都忘记标题了吧,回顾下,咱们目前要解决的是如何把内容加载进程序。也顺带知道了这些__filename
这些常量是怎么来的。
【FBI WARNING】这个其实很麻烦,个人作法必定不会很全很完美,可是作的很差就很容易致使内存消耗愈来愈高
旧的不去新的不来,咱们如今已经有法子引来新的,那接着来了解下node中如何去掉旧的。简单了解下gc机制。
我只是菜狗子,看不懂c++源码。webkit技术内幕啥的也没耐着性子看完,只看了一些文章和深刻浅出nodejs,如下内容来自于概括,主要来源于深刻nodejs第五章。
裂墙安利【深刻浅出nodejs】
--max-old-space-size
和--max-new-space-size
设置最大值通过上面那一串逼逼,咱们大体明白了,要删掉旧的,就删除引用就玩球。因此咱们只须要
require
以后删除require.cache
上的内容因此针对node热更新(热部署)我我的给如下方法
require.cache
等等,还有主要是你的引用!require.cache
等等,还有主要是你的引用!require
完结撒花