js删除节点_Node.js 中妙用 Module.prototype._compile 函数

fadb039fe6114f626f9d8bde9dafdb5b.png

在上篇文章 Node.js 模块机制及源码分析 中,通过分析 Node.js 中模块的加载源码,基本理解了 Node.js 的模块加载原理,其中 Module.prototype._compile 函数主要用于对于第三方 js 文件进行编译加载,所以我们可以巧妙的在 Module.prototype._compile 运行前后执行一些自己的代码,就能实现出意向不到的效果。

最近在看赵坤大神的 《Node.js调试指南》,其中它在讲调试工具和日志定位问题时,引用自己写的两个库: proxy-hot-reload 和 koa-await-breakpoint,proxy-hot-reload 用于实现 Node.js 的热更新,koa-await-breakpoint 用于实现在每个 await 执行的语句前后进行打点工作。这两个库都巧妙的使用了 Module.prototype._compile 函数,下面我们通过分析这两个库的源码来学习如何妙用 Module.prototype._compile 函数。

写这篇文章我只是想借用 proxy-hot-reload 和 koa-await-breakpoint 两个模块来了解一下 Module.prototype._compile 的原理,以及如何去 wrap 这个函数,对于这两个模块的使用,正如 @hyj1991 评论里所说,会有性能开销和内存泄漏:

1.第一个包 delete require.cache 依旧会有内存泄漏的问题,只能在开发环境下使用(话说很多框架都实现了文件变动热重启,也不需要这样的热更新方案)

2.async/await 在目前的机制下有性能损耗的问题(而且是不能忽视的性能损耗),用于大流量的线上服务器一样得关注性能损耗

所以建议不要在生产环境使用,可以作为本地调试工具或者学习 Node 模块机制使用。

proxy-hot-reload 源码分析

使用 proxy—hot-reload 实现热加载功能很简单,只需要在文件中引入如下代码便可:

require

我们知道 Node 模块加载是有缓存的,第一次启动加载成功后便将编译结果放入缓存中,也就是 require.cache 中,后面即使文件内容有更新也不会重新加载,除非你重启整个 Node 进程,如果你想实现对文件的热加载,必须删除 require.cache 中缓存,让其重新加载编译。而 proxy-hot-reload 便是借助 Module.prototype._compile 函数在编译时通过 Proxy 代理 exports 的属性进行拦截实现的:

module

koa-await-breakpoint 源码分析

koa-await-breakpoint 是一个 koa2 的中间件,用于对 await 执行的表达式进行打点,记录当前 await 处于第几步,耗时多长时间,执行结果是多少,并且可以很方便找到当前请求的 ctx,用法如下:

const 

它的实现原理同样是通过对 Module.prototype._compile 函数进行包装改造,Module.prototype._compile,在模块加载的前后修改原始文件的代码,在其中加入打点代码便可实现动态自动打点。

该模块使用到了模块如下:

  • node-glob: 根据模式匹配查找文件
  • esprima:对代码进行语法分析,解析成语法树
  • escodegen:根据语法树重新生成代码
  • async_hooks:于跟踪 Node.Js 中的异步资源的生命周期
  • shimmer:wrap 对象的函数属性

下面的源码分析也是根据这几个模块展开的:

1. node-glob 模块查找需要打点的文件

node-glob 模块可以根据文件模式匹配规则找到你需要的文件:

const 

2. 使用 esprima 生成代码语法树

使用 esprima 模块对每个需要监听的文件的 content 进行语法分析,content 其实就是 Module.prototype._compile 函数的第一个参数的,对 content 调用 esprima.parse 函数得到语法树:

parsedCodes 

分析 parsedCodes 找到 AwaitExpression 节点,包装每个 AwaitExpression 节点的表达式,加入打点代码:

function 

3. 使用 escodegen 重新生成 code content

findAwaitAndWrapLogger 生成了新的语法树,我们可以使用 escodegen 将新的语法树转变成 code content:

const 

4. global[loggerName] 打点记录:

//该函数会对每个 await 节点的代码进行 wrap, fn 是原先在 await 要执行的函数, ctx 是 fn 所在上下文,fnStr 是 await 后的表达式字符串

5. 巧妙使用 async_hooks 获取每个节点对应请求的 ctx

关于 async_hooks 模块如何使用,我在这篇博客 学习使用 Node.js 中 async-hooks 模块 中有详细的介绍,我们主要看一下如何利用 async_hooks 获取每个函数对应的请求上下文:

async_hooks

6. 使用 shimmer 包装 Module.prototype._compile 完成最终需求

shimmer

参考文献

  • Node.js 调试指南
  • Node.js 模块机制及源码分析
  • 学习使用 Node.js 中 async-hooks 模块
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值