|
|
|
|
公众号矩阵

一篇带你了解Node.js

这是一次危险的探索,但是或许某些场景下可以用到。主要想做的事情是劫持所有的 Node.js 函数,在函数执行前后,插入钩子做些事情。

作者:theanarkh来源:编程杂技|2021-11-24 08:51

1.如何监听 Node.js 的所有函数

这是一次危险的探索,但是或许某些场景下可以用到。主要想做的事情是劫持所有的 Node.js 函数,在函数执行前后,插入钩子做些事情。但是由于场景很多而且负责,劫持的风险非常高,如果你使用以下代码有问题,可以提个 issue。以下代码可以通过预加载方式加载或者在你的代码执行前加载。

module-wrap.js

  1. const { Module } = require('module'); 
  2.  
  3. function before(...args) { 
  4.     console.log(`before call function args: ${args}`);} 
  5. function after(...args) { 
  6.     console.log(`after call function result: ${args}`)}const originRequire = Module.prototype.require;// hack to make console init  
  7. console.log(''); 
  8.  
  9. function newRequire(...args){ 
  10.     let exports = originRequire.call(this, ...args); 
  11.     function patch(originFunc, key = originFunc.name) { 
  12.         function dummy(...args) { 
  13.             // you can do something before the function will be executed 
  14.             before([key, ...args]); 
  15.             let result; 
  16.             // if the function call by new, we call by new too 
  17.             if (new.target) { 
  18.                 result = new originFunc(...args); 
  19.                 // make the constructor point to new.target instead of originFunc because new.target maybe be a subclass of originFunc 
  20.                 result.constructor = new.target; 
  21.             } else { 
  22.                 result = originFunc.call(this, ...args); 
  23.             } 
  24.             const params = [key]; 
  25.             if (result) { 
  26.                 params.push(result); 
  27.             } 
  28.             // you can do something after the function have executed 
  29.             after(params); 
  30.             return result; 
  31.         } 
  32.         // we need merge the fields which is writable of originFunc into dummy 
  33.         for (const [key, descriptionInfo] of Object.entries(Object.getOwnPropertyDescriptors(originFunc))) { 
  34.             if (descriptionInfo.writable) { 
  35.                 Object.defineProperty(dummy, key, descriptionInfo); 
  36.             } 
  37.         } 
  38.         // change the function name to the name of originFunc 
  39.         Object.defineProperty(dummy, 'name', { configurable: true, value: originFunc.name }); 
  40.         Object.defineProperty(dummy, 'name', { configurable: false }); 
  41.         // the prototype of dummy need point to originFunc.prototype 
  42.         dummy.prototype = originFunc.prototype; 
  43.         return dummy; 
  44.     } 
  45.  
  46.     // wrapper all functions in export, but now we don not handle the exports recursively 
  47.     if (Object.prototype.toString.call(exports) === '[object Object]') { 
  48.         for (const [key, value] of Object.entries(exports)) { 
  49.             if (typeof value === 'function') { 
  50.                 exports[key] = patch(value, key); 
  51.             } 
  52.         } 
  53.     } else if (Object.prototype.toString.call(exports) === '[object Function]') { 
  54.         exports = patch(exports); 
  55.     } 
  56.     return exports;} 
  57.  
  58. Module.prototype.require = newRequire; 

测试例子。server.js

  1. const http = require('http'); 
  2. http.createServer((req, res) => { 
  3.     res.end('ok');}).listen(8888); 

执行 node -r ./module-wraper.js server.js 将会看到输出

  1. before call function args: createServer,(req, res) => { 
  2.     res.end('ok');} 
  3. after call function result: createServer,[object Object] 

你可以在钩子里做你想做的事情。

2.如何实现直接执行 ts 代码

ts-node 相信很多同学都使用过,它可以直接执行 ts 模块。下面的代码同样可以做到。

  1. const { Module } = require('module');const fs = require('fs');const path = require('path');const ts = require('typescript');const { compileFunction } = process.binding('contextify'); 
  2. Module._extensions['.ts'] = function(module, filename) { 
  3.     const content = fs.readFileSync(filename, 'utf8'); 
  4.     const { outputText } = ts.transpileModule(content, { compilerOptions: { module: ts.ModuleKind.CommonJS }}); 
  5.     const result = compileFunction( 
  6.         outputText, 
  7.         filename, 
  8.         0, 
  9.         0, 
  10.         undefined, 
  11.         false
  12.         undefined, 
  13.         [], 
  14.         [ 
  15.           'exports'
  16.           'require'
  17.           'module'
  18.           '__filename'
  19.           '__dirname'
  20.         ] 
  21.     ); 
  22.     result.function.call(this, module.exports, (...args) => module.require(...args), module, filename, path.dirname(filename));}; 

原理很简单,主要是给 Node.js 增加一个 ts 模块的 加载器,在加载器里通过 typescript 包编译 ts 成 js,然后再调用 V8 的 compileFunction 执行 js。

3.如何写一个 js loader

Node.js 的某些框架的实现模块是在启动前会加载所有的模块成一个树状的结果,下面代码是实现这个 loader 的逻辑。

  1. const fs = require('fs');const { relative } = require('path'); 
  2.  
  3. function load() { 
  4.     return new Promise((resolve, reject) => { 
  5.         const root = process.cwd() + '/a'
  6.         const fileTree = {}; 
  7.         const REGEXP = /\.(js|json|node)$/; 
  8.         const filters = ['node_modules''__tests__']; 
  9.         let request = 0; 
  10.         let done = false
  11.         function _load(currentPath) { 
  12.             request++; 
  13.             fs.readdir(currentPath, (error, dirOrFiles) => { 
  14.                 request--; 
  15.                 if (error) { 
  16.                     console.error(error); 
  17.             if (!done) { 
  18.                 done = true
  19.             reject(error); 
  20.             } 
  21.                 } else if (dirOrFiles.length) { 
  22.                     const absolutePaths = dirOrFiles.filter( (file) => !filters.includes(file) ).map((file) => `${currentPath}/${file}`); 
  23.                     for (let i = 0; i < absolutePaths.length; i++) { 
  24.                         const absolutePath = absolutePaths[i]; 
  25.                         request++; 
  26.                         fs.stat(absolutePath, (error, stat) => { 
  27.                             request--; 
  28.                             if (error) { 
  29.                                 console.error(error); 
  30.                                 if (!done) { 
  31.                   done = true
  32.                   reject(error); 
  33.                     } 
  34.                             } else { 
  35.                                 if (stat.isDirectory()) { 
  36.                                     _load(absolutePath); 
  37.                                 } else { 
  38.                                     try { 
  39.                                         if (REGEXP.test(absolutePath)) { 
  40.                                             const absolutePathWhithoutExt = absolutePath.replace(REGEXP, ''); 
  41.                                             const relativePathWhithoutExt = relative(root, absolutePathWhithoutExt); 
  42.                                             const paths = relativePathWhithoutExt.split('/'); 
  43.                                             let currentNode = fileTree; 
  44.                                             for (let j = 0; j < paths.length - 1; j++) { 
  45.                                                 const path = paths[j]; 
  46.                                                 if (typeof currentNode[path] === 'object' && currentNode[path] !== null) { 
  47.                                                     currentNode = currentNode[path]; 
  48.                                                 } else { 
  49.                                                     currentNode = currentNode[path] = {}; 
  50.                                                 } 
  51.                                             } 
  52.                                             currentNode[paths[paths.length - 1]] = require(absolutePath); 
  53.                                         } 
  54.                                     } catch(e) { 
  55.                                         console.error(e); 
  56.                     if (!done) { 
  57.                       done = true
  58.                       reject(e); 
  59.                      } 
  60.                                     } 
  61.                                 } 
  62.                             } 
  63.                             if (!request && !done) { 
  64.                 done = true
  65.                                 resolve(fileTree); 
  66.                             } 
  67.                         }); 
  68.                     } 
  69.                 } 
  70.                 if (!request && !done) { 
  71.                     resolve(fileTree); 
  72.                 } 
  73.             }); 
  74.         } 
  75.         _load(root); 
  76.     });}load().then(console.log).catch(console.error); 

利用异步读取的方式提高速度。

github 地址:

1. https://github.com/theanarkh/Node.js-Function-Wrapper

2. https://github.com/theanarkh/tiny-ts-node

3. https://github.com/theanarkh/Node.js-Loader

【编辑推荐】

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
  2. 函数计算GB镜像秒级启动:下一代软硬件架构协同优化揭秘
  3. 你不知道的 Node.js util
  4. 使用Node.JS在云服务器搭建WEB
  5. VLookup函数从基础到复杂用法讲解
  6. Node.js 对比 Python:优点、缺点和用例
【责任编辑:姜华 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

带你轻松入门 RabbitMQ

带你轻松入门 RabbitMQ

轻松入门RabbitMQ
共4章 | loong576

57人订阅学习

数据湖与数据仓库的分析实践攻略

数据湖与数据仓库的分析实践攻略

助力现代化数据管理:数据湖与数据仓库的分析实践攻略
共3章 | 创世达人

14人订阅学习

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

42人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微