給大家變個(gè) Node.js 的小魔術(shù)
本文轉(zhuǎn)載自微信公眾號(hào)「神光的編程秘籍」,作者神說(shuō)要有光。轉(zhuǎn)載本文請(qǐng)聯(lián)系神光的編程秘籍公眾號(hào)。
魔術(shù)演出
我們準(zhǔn)備一個(gè) Node.js 的模塊 input.js:
- // input.js
- function func() {
- return '卡頌'
- }
- module.exports = func();
這個(gè)模塊返回的值是啥?
東東:是“卡頌”。
那我在另一個(gè)模塊 test.js 中引入這個(gè) input.js,然后打印一下:
- // test.js
- const data = require('./input.js');
- console.log(data);
之后我在 entry.js 里面引入 test.js:
- require('./test.js');
執(zhí)行之后打印的是啥?
東東:是“卡頌”。
真的么?那我們跑一下:
打印的是啥:
東東:是 “卡帥”,哇,好神奇,怎么做到的。
我:想不想學(xué)?
東東:想。
我:那接下來(lái)就進(jìn)入魔術(shù)揭秘時(shí)間。
魔術(shù)揭秘
Node.js 加載模塊的流程是這樣的:
模塊加載會(huì)調(diào)用 load 方法, load 會(huì)調(diào)用對(duì)應(yīng)后綴名的 _extensions 的方法來(lái)處理,其中會(huì)調(diào)用 _compile 來(lái)編譯并把結(jié)果放入 cache,之后返回。
所以呢?我們想改變 js 模塊的返回值,只需要改造下 Module._extensions['.js'] 就可以了。
- const Module = require('module');
- const fs = require('fs');
- Module._extensions['.js'] = function (module, filename) {
- let content = fs.readFileSync(filename, 'utf8');
- if (filename.includes('input')) {
- content = content.replace('卡頌', '卡帥');
- }
- module._compile(content, filename);
- };
我們對(duì) filename 為 input 的文件,讀取內(nèi)容之后進(jìn)行了替換,之后再調(diào)用 module._compile 來(lái)編譯,后續(xù)流程不變。
模塊引入方式不變,但是模塊內(nèi)容已經(jīng)悄悄的被修改了,這個(gè)魔術(shù)的名字叫做 require hook。
東東:原來(lái)是你藏了一段代碼沒(méi)展示。
我:魔術(shù)都是這樣的啊。而且你別小看了這個(gè) require hook,它能做到很多強(qiáng)大的功能呢。
東東:哦?比如說(shuō)
我:比如說(shuō) ts-node,它是怎么做到直接 require ts 模塊的?就是通過(guò) require hook 偷偷做了編譯,其實(shí)你執(zhí)行的是編譯后的 js。
比如說(shuō) babel-register 它是怎么做到直接執(zhí)行帶有 esnext 新特性的代碼的?也是通過(guò) require hook 偷偷做了編譯。
還有覆蓋率測(cè)試,其實(shí)是通過(guò)函數(shù)插樁做到的,也就是你每執(zhí)行一條語(yǔ)句都會(huì)計(jì)數(shù)。怎么插樁呢?跑單測(cè)的時(shí)候也沒(méi)手動(dòng)插樁啊,就是因?yàn)楣ぞ邇?nèi)部偷偷通過(guò) require hook 做了插樁,才能得到覆蓋率數(shù)據(jù)。
東東:這個(gè)魔術(shù)還挺有用的嘛。學(xué)會(huì)了~
總結(jié)
Node.js 的 js 模塊加載的流程是 load -> _extensions['.js'] -> _compile,可以通過(guò)修改 _extensions['.js'] 來(lái)達(dá)到 hook 的目的,比如在 _compile 之前做一些代碼轉(zhuǎn)換。
這種 hook 在 babel-register、ts-node 還有單測(cè)的覆蓋率測(cè)試中都有應(yīng)用,能夠達(dá)到透明的修改代碼的目的。
因?yàn)殚_(kāi)發(fā)者不知道代碼什么時(shí)候被修改的,所以看起來(lái)比較神奇。