一篇带你了解Node.js

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

[[436475]]

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

 

责任编辑:姜华 来源: 编程杂技
相关推荐

2021-07-03 08:04:10

io_uringNode.js异步IO

2023-05-12 07:31:58

NuxtVue.js

2021-05-20 06:57:16

RabbitMQ开源消息

2021-08-25 06:33:52

Node.jsVscode调试工具

2022-03-20 06:40:31

Node.jsperf_hooks性能数据

2023-05-12 08:19:12

Netty程序框架

2021-06-30 00:20:12

Hangfire.NET平台

2021-07-14 08:24:23

TCPIP 通信协议

2021-07-28 10:02:54

建造者模式代码

2021-08-11 07:02:21

npm包管理器工具

2021-08-02 06:34:55

Redis删除策略开源

2021-11-08 08:42:44

CentOS Supervisor运维

2021-12-15 11:52:34

GPLLinuxGNU

2023-05-05 07:41:42

执行上下文JavaScript

2021-01-29 18:41:16

JavaScript函数语法

2021-06-04 09:56:01

JavaScript 前端switch

2020-11-10 10:48:10

JavaScript属性对象

2021-07-08 06:30:03

Linux CPULinux 系统

2021-08-14 10:01:43

Python条件语句Python基础

2022-02-17 08:35:59

OLTPOLAP数据仓库
点赞
收藏

51CTO技术栈公众号