一篇带你了解Node.js

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

[[436475]]

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

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

module-wrap.js

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

测试例子。server.js

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

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

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

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

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

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

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

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

3.如何写一个 js loader

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

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

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

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性能数据

2021-07-28 10:02:54

建造者模式代码

2023-05-12 08:19:12

Netty程序框架

2021-07-14 08:24:23

TCPIP 通信协议

2021-06-30 00:20:12

Hangfire.NET平台

2021-08-11 07:02:21

npm包管理器工具

2021-11-08 08:42:44

CentOS Supervisor运维

2021-08-02 06:34:55

Redis删除策略开源

2021-12-15 11:52:34

GPLLinuxGNU

2020-11-10 10:48:10

JavaScript属性对象

2021-06-04 09:56:01

JavaScript 前端switch

2021-08-14 10:01:43

Python条件语句Python基础

2021-08-26 05:27:08

Base64 字节流算法

2021-01-29 18:41:16

JavaScript函数语法

2021-07-08 06:30:03

Linux CPULinux 系统

2022-11-10 16:55:41

ReactFiber
点赞
收藏

51CTO技术栈公众号