金沙棋牌官方平台

当前位置:金沙棋牌 > 金沙棋牌官方平台 > 如何 hack Node.js 模块?

如何 hack Node.js 模块?

来源:http://www.logblo.com 作者:金沙棋牌 时间:2019-11-08 00:57

如何 hack Node.js 模块?

2016/10/28 · JavaScript · NodeJS

原稿出处: 天猫前端团队(FED卡塔 尔(阿拉伯语:قطر‎- 宣予   

金沙棋牌官方平台 1

1.描述
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运营情况。
接纳了二个事件驱动、非堵塞式 I/O 的模型,使其轻量又火速。
Node.js 的包微机 npm,是全世界最大的开源库生态系统。
是一门技能,不是一门语言。
作用:
平价测验JavaScript代码的运维条件
repl基本操作
变量、函数、对象
一向运转函数
采纳下划线字符,表示上三个下令的回来结果
repl基本命令
.help .exit

模块

为啥要去 hack?

在专业支付进程中,往往会依赖一些 Node.js 模块,hack 这几个 Node.js 模块的机要指标是在不校勘工具源码的状态下,点窜一些一定的功力。恐怕会是出于以下两种处境的虚构:

  1. 连年存在部分出奇之处必要,不必然能看做工具的通用须求来暴露平常的 API 给更加多的客户。
  2. 偶尔且迫切的必要,提 PTiguan 已经来不如了。
  3. 为啥不直接去改源码?思量到工具会不依期进级,想选拔工具的风靡特性,改源码可维护性太差。

2.下载/安装
第生龙活虎种艺术
https://nodejs.org/en/
下载下来直接设置就能够
第三种形式
nvm情势安装
nvm install latest //安装最新的版本

Node 有简要的模块加载系统。在 Node 里,文件和模块是种种对应的。上面例子里,foo.js加载同二个文件夹里的circle.js模块。

期望

举个栗子:

JavaScript

// a.js module.exports = function(){ dosomething(); } // b.js module.exports = require(a); // c.js console.log(require(b));

1
2
3
4
5
6
7
8
// a.js
module.exports = function(){
  dosomething();
}
// b.js
module.exports = require(a);
// c.js
console.log(require(b));

b 是种类 c 信赖的五个工具模块,b 信赖 a。希望只在档期的顺序 c 中,b 调用 a 时,a 的函数里能注入一些措施 injectSomething()

  • hack 之前 c 的输出

JavaScript

function(){ dosomething(); }

1
2
3
function(){
  dosomething();
}
  • 期望:hack 之后 c 的输出

JavaScript

function(){ injectSomething(); dosomething(); }

1
2
3
4
function(){
  injectSomething();
  dosomething();
}

现实案例譬喻:在做个人自动化学工业具时,供给 mock 一些工具的手动输入;在地面创设时,要求校订通用的营造流程(前边案例部分会详细说卡塔尔

3.版本管理工具nvm
1.下载地址:https://github.com/coreybutler/nvm-windows/releases
2.解压缩到有个别文件夹中(elevate.cmd、elevate.vbs、install.cmd、LICENSE、nvm.exe)。比如nvm
3.直接运营 install.cmd,忽视让输入的内容。间接回车就能够。
4.在C盘根目录会生成 settings.txt
5.settings.txt文书内容
root: D:Program Filesnvm
path: D:Program Filesnodejs
arch: 64
proxy: none
node_mirror: http://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/
6.布局遭遇变量
NVM_HOME D:Program Filesnvm
NVM_SYMLINK D:Program Filesnodejs
7.添加Path
%NVM_HOME%;%NVM_SYMLINK%;
8.cmd测试
nvm v 查看nvm版本
9.命令
翻开当地安装的装有版本 : nvm list/nvm ls
设置钦点版本node : nvm install 版本号
卸载钦定版本node : nvm uninstall 版本号
切换使用钦定版本的node :nvm use 版本号

foo.js内容:

最首要方式

4.helloworld程序
1.创办两个helloworld.js
d:/helloworld.js
2.编辑代码
console.log("hello world");
3.运转程序
node helloworld.js

var circle = require('./circle.js');

采用模块 cache 窜改模块对象属性

这是本人最初采纳的点子,在模块 a 的体系是 object 的时候,可以在投机的体系c 中提前 require 模块 a,依据你的需求改过部分性质,那样当模块 b 再去 require 模块 a 时,从缓存中抽出的模块 a 已是被涂修改的了。

模块 a、b、c 栗子如下:

JavaScript

// a.js module.exports = { p } // b.js const a = require(a); a.p(); // c.js require(b);

1
2
3
4
5
6
7
8
9
// a.js
module.exports = {
  p
}
// b.js
const a = require(a);
a.p();
// c.js
require(b);

自己想修正 a 的秘籍 p,在 c 中张开如下改进就能够,而没有必要直接去改良工具 a、b 的源码:

JavaScript

// c.js const a = require(a); let oldp = a.p; a.p = function(...args){ injectSomething(); oldp.apply(this, args); } require(b);

1
2
3
4
5
6
7
8
// c.js
const a = require(a);
let oldp = a.p;
a.p = function(...args){
   injectSomething();
   oldp.apply(this, args);
}
require(b);

缺陷:在少数模块属性是动态加载的情事,不是那么灵敏,而且只好点窜引用对象。但非常多状态下还能够满意必要的。

5.全局对象global
5.0 说明
global表示Node所在的全局意况,相仿于浏览器的window对象
5.1 console
5.1.1概念变量 global.a=123和a=123的定义是一律的
var a = 123;
global.a = 1234;
console.log(a);
console.log(global.a);
5.1.2 断言 console.assert
var a = 1234;
console.assert(a==123,"剖断退步了a不等于123");
5.1.3 输出代码的实行时间
console.time('time1');
for(var i = 0;i<1000;i++){}
console.timeEnd('time1');
瞩目:必须成对现身time1。
5.1.4 当前文件所在的文件路线 __dirname
console.log(__dirname);
5.1.5 当前文件的全路线
console.log(__filename);
5.2 process
5.6.1 说明
process对象是Node的三个大局对象,提供当前Node进程的消息。能够在本子的妄动地点运用。
5.6.2 使用
process.pid:当前经过的进度号。
console.log(process.pid);
process.version:Node的版本,比如v0.10.18。
console.log(process.version);
process.platform:当前系统平台,比方Linux。
console.log(process.platform);
process.env:指向当前shell的意况变量,比如process.env.HOME。
console.log(process.env);
process.stdout:指向标准输出。
process.stdout.write('shuaige');
process.stdin:指向标准输入。
process.stderr:指向标准错误。
5.3 module
Node内部提供二个Module构造函数,全部模块都以Module的实例
各种模块内部,都有一个module对象,代表当前模块.
module.id 带有相对路线的模块文件名
module.filename 模块的文本名,带有绝对路线
module.loaded 表示模块是或不是曾经做到加载
module.parent 再次回到八个指标,表示调用该模块的模块。
module.children 重返一个数组,表示该模块要用到的任何模块。
module.exports 模块对外出口的值
module.paths
5.4 exports
自己便是全局的,能够间接行使。
5.5 require
require操作的时候实乃去硬盘中去读js文件,把放入module对象当中去,内部存款和储蓄器中缓存中
在Node.js中,require命令用于加载模块文件
读取并奉行二个JavaScript文件
接下来回来该模块的exports对象
风华正茂旦未有意识钦赐模块,会报错
加载法则
参数字符串以"/"初步
参数字符换以"./"开首
参数字符串不以"/"或"./",表示加载大旨模块,恐怕四个身处各级node_modules目录已安装的模块
参数字符串能够简轻松单后缀名
.js、.json、.node
.js会当作JavaScript脚本文件深入分析
.json会以JSON格式深入深入分析
.node会以编译后的二进制文件解析
5.6 模块 module.exports & require / exports & require
第风度翩翩种写法:
代码:add.js
var add = function(a,b){
return a+b;
}
module.exports=add;
代码:one.js
var add = require("./add.js");
console.log(add(1,2));
第三种写法
var add = function(a,b){
return a+b;
}
exports.add=add;

console.log( 'The area of a circle of radius 4 is '

修改require.cache

在遇见模块揭示的是非对象的状态,就需要一向去修改 require 的 cache 对象了。关于修改 require.cache 的平价,会在背后的原理部分详细说,先来一句话来说下操作:

JavaScript

//a.js 揭露的非对象,而是函数 module.exports = function(){ doSomething(); } //c.js const aOld = require(a); let aId = require.resolve(aPath); require.cache[aId] = function(...args){ injectSomething(); aOld.apply(this, args); } require(b);

1
2
3
4
5
6
7
8
9
10
11
12
//a.js 暴露的非对象,而是函数
module.exports = function(){
   doSomething();
}
//c.js
const aOld = require(a);
let aId = require.resolve(aPath);
require.cache[aId] = function(...args){
   injectSomething();
   aOld.apply(this, args);
}
require(b);

症结:只怕世襲调用链路会有人手动去更正 require.cache,比方热加载。

        var add = require("./add.js").add;
        console.log(add(1,2));
        var obj = require("./add.js");
        console.log(obj.add(1,2));

+ circle.area(4));

修改 require

这种措施是平素去代理 require ,是最稳当的措施,可是侵入性相对来讲相比较强。Node.js 文件中的 require 其实是在 Module 的原型方法上,即 Module.prototype.require。前面会详细说,先轻便说下操作:

JavaScript

const Module = require('module'); const _require = Module.prototype.require; Module.prototype.require = function(...args){ let res = _require.apply(this, args); if(args[0] === 'a') { // 只纠正a模块内容 injectSomething(); } return res; }

1
2
3
4
5
6
7
8
9
const Module = require('module');
const _require = Module.prototype.require;
Module.prototype.require = function(...args){
    let res = _require.apply(this, args);
    if(args[0] === 'a') { // 只修改a模块内容
        injectSomething();
    }
    return res;
}

缺欠:对总体 Node.js 进度的 require 操作都怀有侵入性。

金沙棋牌官方平台,6.node的模块
6.1 说明
三个文本正是二个模块
将艺术挂载到exports对象上作为质量就可以定义导出的点子
Node程序由许多模块组成,每一种模块正是二个文件。Node模块采取了CommonJS标准。
Node.js本人正是贰个惊人模块化的二个平台
遵照CommonJS标准,每八个模块都以叁个单独的成效域
CommonJS规定,种种文件对外的接口是module.exports对象,该目的具有属性和措施,都得以被别的文件导入。
6.2 引用模块
模块援用require
6.3 模块标志
总得是相符小驼峰命名的字符串
以.、..最先的相对路线
相对路线
能够没有公文名后缀.js
6.4 模块分类
6.4.1 核心模块 - 通过名字直接引进require('大旨模块名')
fs(file system) 文件模块
http 互联网乞求模块
os 系统模块
path 路径模块
querystring 字符串查询模块,解析url查询字符串
url 地址模块,深入深入分析url
util 提供大器晚成多元实用小工具
.. 等等

circle.js内容:

有关原理

        核心模块的源码都在Node的lib子目录中。为了提高运行速度,它们安装的时候都会被编译成二进制文件
    6.4.2 其它定义模块 - require('路径+模块名')

    6.4.3 模块加载机制
        如果require绝对路径的文件,查找时不会去遍历每一个node_modules目录,其速度最快。其余流程如下:
        1. 从module path数组中取出第一个目录作为查找基准。
        2. 直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找。
        3. 尝试添加.js、.json、.node后缀后查找,如果存在文件,则结束查找。如果不存在,则进行下一条。
        4. 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件。
        5. 尝试查找该文件,如果存在,则结束查找。如果不存在,则进行第3条查找。
        6. 如果继续失败,则取出module path数组中的下一个目录作为基准查找,循环第1至5个步骤。
        7. 如果继续失败,循环第1至6个步骤,直到module path中的最后一个值。
        8. 如果仍然失败,则抛出异常。
6.5 总结
    所有代码都运行在模块作用域,不会污染全局作用域
    模块可以多次加载,但是只会在第一次加载的时候运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果
    模块的加载顺序,按照代码的出现的顺序是同步加载的
    require是同步加载模块的

var PI = Math.PI;

node的启航进度

我们先来探问在运营 node a.js 时产生些什么?node源码
金沙棋牌官方平台 2

上图是node运行 a.js 的叁个主干流程,Node.js 的运转程序 bootstrap_node.js 是在 node::LoadEnvironment 中被当即实践的,bootstrap_node.js 中的 startup() 是包装在二个佚名函数里面包车型地铁,所以在三回施行 node 的行事中 startup() 只会被调用了二遍,来保险 bootstrap_node.js 的所实践的持有信赖只会被加载一回。C++ 语言部分中:

JavaScript

//node_main.cc 假诺在win景况举行wmain(),unix则推行main(),函数最终都奉行了node::Start(argc, argv) #ifdef _WIN32 int wmain() #else int main() #endif //node::Start(argc, argv) 提供载入 Node.js 进度的 V8 意况Environment::AsyncCallbackScope callback_scope(&env); LoadEnvironment(&env); //node::LoadEnvironment(Environment* env) 加载 Node.js 环境 Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate()," bootstrap_node.js"); Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//node_main.cc 如果在win环境执行wmain(),unix则执行main(),函数最后都执行了node::Start(argc, argv)  
#ifdef _WIN32
  int wmain()
#else
  int main()
#endif
 
//node::Start(argc, argv) 提供载入 Node.js 进程的 V8 环境
Environment::AsyncCallbackScope callback_scope(&env);
LoadEnvironment(&env);
 
//node::LoadEnvironment(Environment* env) 加载 Node.js 环境
Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate()," bootstrap_node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);

bootstrap_node.js 中,会去实施 Module 的静态方法 runMain,而 runMain 中则去施行了 Module._load,也正是模块加载的历程。

JavaScript

// bootstrap_node.js const Module = NativeModule.require('module'); …… run(Module.runMain); // Module.js Module.runMain = function() { Module._load(process.argv[1], null, true); process._tickCallback(); };

1
2
3
4
5
6
7
8
9
// bootstrap_node.js
const Module = NativeModule.require('module');
……
run(Module.runMain);
// Module.js
Module.runMain = function() {
    Module._load(process.argv[1], null, true);
    process._tickCallback();
};

7.npm包处理类别
7.1说明
后生可畏种意义是Node.js的开放式模块登记和保管种类
世上之最:最大的模块生态系统,里面全体的模块后面一个说是包,都以开源免费的,拿来即用
https://www.npmjs.com/
另风度翩翩种意义是Node.js暗中认可的模块微机,是一个指令行下的软件,用来安装和关押node模块
7.2底子命令
npm init 【-y】 初阶化叁个package.json文件
npm install 包名 安装七个包
npm install jquery
npm install angular
npm install –save 包新秀安装的包增多到package.json的依据中(dependencies卡塔 尔(阿拉伯语:قطر‎
npm install –g 包名 安装四个命令行工具
npm docs 包名 查看包的文书档案【非常实惠】
npm docs jquery
npm docs angular
npm root -g 查看全局包安装路径
npm config set prefix “路线” 改良全局包安装路线
npm list 查看当前目录下安装的具备包
npm list -g 查看全局包的安装路线下全数的包
npm uninstall 包名 卸载当前目录下某些包
npm uninstall –g 包名 卸载全局安装路线下的某部包
npm update 包名 更新当前目录下有个别包
npm update –g 包名 更新某些全局工具包
npm update 更新当前目录下安装的兼具包
npm update –g 更新全局全部的工具包
7.3装置某些模块

exports.area = function (r) {

二个经过只设有三个 cache 对象?

先来拜访 module._load 干了什么样?

JavaScript

Module._load = function(request, parent, isMain) { var filename = Module._resolveFilename(request, parent, isMain); var cachedModule = Module._cache[filename]; // get cache if (cachedModule) { return cachedModule.exports; } …… var module = new Module(filename, parent); …… Module._cache[filename] = module; // set cache tryModuleLoad(module, filename); return module.exports; };

1
2
3
4
5
6
7
8
9
10
11
12
13
Module._load = function(request, parent, isMain) {
  var filename = Module._resolveFilename(request, parent, isMain);
  var cachedModule = Module._cache[filename]; // get cache
  if (cachedModule) {
    return cachedModule.exports;
  }
  ……
  var module = new Module(filename, parent);
  ……
  Module._cache[filename] = module; // set cache
  tryModuleLoad(module, filename);
  return module.exports;
};

能够见到,在 load 的一个模块时,会先读缓存 Module._cache,若无就能够去 new 三个 Module 的实例,
接下来再把实例放到缓存里。由前面的 Node.js 运营进度可见, bootstrap_node.js 中的 startup() 只会进行了三次,当中产生的 Module 对象在全部node进度调用链路中只会存在三个,从而 Module._cache 独有贰个。

7.4package.json

return PI r * r;*

Module._cacherequire.cache 的关系

能够看下 Module.prototype._compile 那几个措施,那在那之中会对大家写的 Node.js 文件举行四个装进,注入一些上下文,满含 require:

JavaScript

var require = internalModule.makeRequireFunction.call(this); var args = [this.exports, require, this, filename, dirname]; var depth = internalModule.requireDepth; var result = compiledWrapper.apply(this.exports, args);

1
2
3
4
var require = internalModule.makeRequireFunction.call(this);
var args = [this.exports, require, this, filename, dirname];
var depth = internalModule.requireDepth;
var result = compiledWrapper.apply(this.exports, args);

而在 internalModule.makeRequireFunction 中大家会意识

JavaScript

// 在 makeRequireFunction 中 require.cache = Module._cache;

1
2
// 在 makeRequireFunction 中
require.cache = Module._cache;

所以,Module._cacherequire.cache 是大器晚成律的,那么大家平昔改变 require.cache 的缓存内容,在三个 Node.js 进程里都以平价的。

};

require 区别场景的挂载

最伊始笔者以为 require 是挂载在 global 上的,为了图省事,日常用 Node.js repl 来测验:

JavaScript

$ node > global.require { [Function: require] resolve: [Function: resolve], main: undefined, extensions: { '.js': [Function], '.json': [Function], '.node': [Function] }, cache: {} }

1
2
3
4
5
6
7
$ node
> global.require
{ [Function: require]
  resolve: [Function: resolve],
  main: undefined,
  extensions: { '.js': [Function], '.json': [Function], '.node': [Function] },
  cache: {} }

能够见到,repl 下,global.require 是存在的,假设感到能够间接在 Node.js 文件中代理 global.require 那就踩坑了,因为若是在 Node.js 文件中使用会发觉:

JavaScript

console.log(global.require); // undefined

1
2
console.log(global.require);
// undefined

从上文可以知道,Node.js 文件中的 require 其实是来源于于 Module.prototype._compile 中注入的 Module.prototype.require, 而最后的针对其实是 Module._load,并不曾挂载到 module 上下文景况中的 global 对象上。

而 repl 中也有 module 实例,于是小编尝试在 repl 中打字与印刷:

JavaScript

$ node > global.require === module.require false

1
2
3
$ node
> global.require === module.require
  false

结果有一些匪夷所思,于是自个儿一连追究了下。在 bootstrap_node.js 中找到 repl 的调用文件 repl.js

JavaScript

const require = internalModule.makeRequireFunction.call(module); context.module = module; context.require = require;

1
2
3
const require = internalModule.makeRequireFunction.call(module);
context.module = module;
context.require = require;

收获结论:在 repl 中,module.requireglobal.require 最后的调用方法是同样的,只是函数指向分裂而已。

exports.circumference = function (r) {

注意点

return 2 PI * r;*

path路径

require.cache 是八个 key、value 的 map,key 看上去是模块所在的相对路线,然则是不可能用相对路线直接去用的,须要 require.resolve 来剖判路径,拆解解析后才是 cache 中正确的 key 格式。

下面前碰到比下分别:

JavaScript

// 模块的相对路径/Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/@ali/builder-cake-kpm/node_modules/@ali/cake-webpack-config/index.js // 用 require.resolve 转义后的结果 /Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/.0.16.23@@ali/cake-webpack-config/index.js

1
2
3
4
5
// 模块的绝对路径
/Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/@ali/builder-cake-kpm/node_modules/@ali/cake-webpack-config/index.js
 
// 用 require.resolve 转义后的结果
/Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/.0.16.23@@ali/cake-webpack-config/index.js

};

多进度的动静

模块间调用的链路相比长,有希望会新建子进度,必要考虑你项目中的入口文件和您须求代理的文件是或不是在贰个进度中,轻易的点子正是在入口文件和您须求代理的文本打字与印刷pid:

JavaScript

console.log(process.pid)

1
console.log(process.pid)

万风度翩翩生机勃勃致,那么直接在入口调用前代理就能够,不然事态会更复杂点,需求找到相应的进度调用场进行代理。

circle.js模块输出了area()和circumference()函数。想要给根模块增添函数和指标,你能够将她们增添到一定的exports对象。

案例

DEF 是天猫前端的合并开辟情形,协理前端模块创立、构建打包、公布等一文山会海流程。 在以下案例中,重要 hack 的 Node.js 项目正是 DEF。

加载到模块里的变量是私家的,就如模块是包括在三个函数里。在此个例子里,PI是circle.js的村办变量。

点窜输入(prompt)

气象:使用 DEF 成立模块 or 揭橥模块时

缘由:想风度翩翩键实现批量创设 or 批量颁发,不想手动输入。

赶尽杀绝进度:以创建立模型块为例

  • 第生龙活虎找到 DEF 的入口文件,即三个 bin 目录下的路子,能够由此那个进口文件不断追溯下去,发掘创立模块的 generator 用的是 yeoman-generator 的措施。对 prompt 的措施开展代理,能够将该根底库提前 require,修改掉其 prompt 的不二法门就可以。
  • 屈居示例代码(示例只窜改 def add 模块的开创项目,别的输入的点窜方法肖似卡塔 尔(阿拉伯语:قطر‎:

JavaScript

#!/usr/bin/env node 'use strict'; require('shelljs/global'); const path = require('path'); const HOME = process.env.HOME; const yeomanRouter = require(path.join(HOME, '.def/def_modules/.generators/@ali/generator-abs-router/node_modules/@ali/generator-abs-router/node_modules/yeoman-generator')); yeomanRouter.generators.Base.prototype.prompt = function(list, callback) { let item = list[0]; let prop = {}; prop[item.name] = 'kissy-pc'; // 让模块类型输入自动为pc callback(prop); }; //require real def path const defPath = which('def').stdout; require(defPath);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env node
 
'use strict';
 
require('shelljs/global');
const path = require('path');
const HOME = process.env.HOME;
 
const yeomanRouter = require(path.join(HOME, '.def/def_modules/.generators/@ali/generator-abs-router/node_modules/@ali/generator-abs-router/node_modules/yeoman-generator'));
 
yeomanRouter.generators.Base.prototype.prompt = function(list, callback) {
  let item = list[0];
  let prop = {};
  prop[item.name] = 'kissy-pc'; // 让模块类型输入自动为pc
  callback(prop);
};
 
//require real def path
const defPath = which('def').stdout;
require(defPath);

要是您想模块里的根像一个函数同样的输出(举个例子构造函数卡塔 尔(英语:State of Qatar),或然您想出口四个完全对象,那就分派给module.exports,并非exports。

窜改创设流程(webpackconfig卡塔尔

气象:二个天猫商城的前端组件,供给在应用def当地调节和测量试验时提前转移贰个文书内容。(天猫组件的营造会奉公守法组件类型统意气风发创设器,并非各类组件单独去布署卡塔尔

缘由:平常的话,这种意况能够筛选注释代码大法,本地调试时张开注释,发布前干掉。但那样形成代码十分不佳看,也易于引起误操作。无妨在本地调试的 reflect 过程中动态转变掉就好了。

解决进度:

  • 追溯 def dev 调用链路,找到最终reflect的文书, 在这里个营造器 @ali/builder-cake-kpm 项目里。所选用的webpack的布署项在 @ali/cake-webpack-config 下。
  • 现今正是往 webpack 配置项里动态注入四个 webpack loader 的经过了,作者急需的 loader 是三个preLoader,代码特别轻易,作者把它放在工文章种的公文里:

JavaScript

module.exports = function(content) { return content.replace('require('./plugin')', "require('./localPlugin')"); };

1
2
3
module.exports = function(content) {
    return content.replace('require('./plugin')', "require('./localPlugin')");
};
  • @ali/cake-webpack-config 暴光的是个函数而非对象,所以必得从 require 入手了,最终附上案例的代理进程:

JavaScript

#!/usr/bin/env node 'use strict'; require('shelljs/global'); const path = require('path'); const HOME = process.env.HOME; const CWD = process.cwd(); const cakeWcPath = path.join(HOME, '.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/@ali/builder-cake-kpm/node_modules/@ali/cake-webpack-config'); const preLoaderPath = path.join(CWD, 'debug/plugin_compile.js'); // 注入的loader路径 const cakeWebpackConfig = require(cakeWcPath); const requireId = require.resolve(cakeWcPath); require.cache[requireId].exports = (options) => { if (options.callback) { let oldCb = options.callback; options.callback = function(err, obj) { obj.module.preLoaders = [{ 'test': /index.js$/, 'loader': preLoaderPath }]; oldCb(err, obj); } } cakeWebpackConfig(options); } //require real def path const defPath = which('def').stdout; require(defPath);

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
#!/usr/bin/env node
'use strict';
 
require('shelljs/global');
const path = require('path');
const HOME = process.env.HOME;
const CWD = process.cwd();
 
const cakeWcPath = path.join(HOME, '.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/@ali/builder-cake-kpm/node_modules/@ali/cake-webpack-config');
const preLoaderPath = path.join(CWD, 'debug/plugin_compile.js'); // 注入的loader路径
const cakeWebpackConfig = require(cakeWcPath);
const requireId = require.resolve(cakeWcPath);
require.cache[requireId].exports = (options) => {
  if (options.callback) {
    let oldCb = options.callback;
    options.callback = function(err, obj) {
      obj.module.preLoaders = [{
        'test': /index.js$/,
        'loader': preLoaderPath
      }];
      oldCb(err, obj);
    }
  }
  cakeWebpackConfig(options);
}
 
//require real def path
const defPath = which('def').stdout;
require(defPath);

bar.js使用square模块, 它输出了构造函数:

结束语

去 hack 一个 Node.js 模块,须求对该 Node.js 模块的调用链路有一定的问询,在众多状态下,不必然是最优的主意,但也真是生龙活虎种缓和方案。风趣的是,Node.js 源码中实际上有意气风发行那样的讲授:

JavaScript

// Hello, and welcome to hacking node.js! // some descriptions

1
2
// Hello, and welcome to hacking node.js!
// some descriptions

So, just hacking for fun!

1 赞 2 收藏 评论

金沙棋牌官方平台 3

var square = require('./square.js');

var mySquare = square(2);

console.log('The area of my square is ' + mySquare.area());

square定义在square.js文件里:

// assigning to exports will not modify module, must use module.exports

module.exports = function(width) {

return {

area: function() {

return width width;*

}

};

}

模块系统在require("module")模块里实现。

Cycles

环形调用require(),当重临时模块恐怕都没试行实现。

假造以下场景:

a.js:

console.log('a starting');

exports.done = false;

var b = require('./b.js');

console.log('in a, b.done = %j', b.done);

exports.done = true;

console.log('a done');

b.js:

console.log('b starting');

exports.done = false;

var a = require('./a.js');

console.log('in b, a.done = %j', a.done);

exports.done = true;

console.log('b done');

main.js:

console.log('main starting');

var a = require('./a.js');

var b = require('./b.js');

console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

当main.js加载a.js,a.js加载b.js。那时候,b.js试着加载a.js。为了阻止循环调用,a.js输出对象的不完全拷贝再次来到给b.js模块。b.js会结束加载,并且它的exports对象提需求a.js模块。

main.js加载完四个模块时,它们都会终结。那几个程序的输出如下:

$ node main.js

main starting

a starting

b starting

in b, a.done = false

b done

in a, b.done = true

a done

in main, a.done=true, b.done=true

假使您的主次有环形模块信任,须要保证是线性的。

主干模块

Node 有数不尽模块编写翻译成二进制。那么些模块在本文书档案的任何处方有更详细的陈述。

主干模块定义在 Node 的源代码lib/目录里。

require()总是会事先加载宗旨模块。举例,require('http')总是回到编译好的 HTTP 模块,而不论是这几个文件的名字。

文本模块

假若依照文件名从未找到模块,那么 Node 会试着加载增添了后缀.js,.json的文件,假诺还未有好到,再试着加载增加了后缀.node的文书。

.js会深入深入分析为 JavaScript 的文书文件,.json会解析为 JSON 文本文件,.node会分析为编写翻译过的插件模块,由dlopen担负加载。

模块的前缀'/'表示相对路线。比方require('/home/marco/foo.js')将会加载/home/marco/foo.js文件。

模块的前缀'./'表示相对于调用require()的渠道。正是说,circle.js必须和foo.js在 同四个目录里,require('./circle')本事找到。

文件前并没有/或./前缀,表示模块只怕是core module,恐怕曾经从node_modules文件夹里加载过了。

假诺钦命的渠道不设有,require()将会抛出二个code属性为'MODULE_NOT_FOUND'的异常。

从node_modules目录里加载

如传递给require()的模块不是七个本地模块,况且不以'/','../', 或'./'开首,那么 Node 会今后时此刻模块的父目录起头,尝试在它的node_modules文件夹里加载模块。

若是未有找到,那么会到父目录,直到到文件系统的根目录里找。

诸如,即使'/home/ry/projects/foo.js'里的文本加载require('bar.js'),那么 Node 将会规行矩步上边包车型大巴相继查找:

/home/ry/projects/node_modules/bar.js

/home/ry/node_modules/bar.js

/home/node_modules/bar.js

/node_modules/bar.js

如此允许程序独立,不会生出冲突。

能够诉求钦点的公文或布满子目录里的模块,在模块名后增多路线后缀。例如,require('example-module/path/to/file')会解决path/to/file相对于example-module的加载地方。路线后缀使用相近语法。

文本夹作为模块

能够把程序和库放到独门的文书夹里,并提供单纯的输入指向他们。有二种艺术能够将文件夹作为参数字传送给require()。

先是个办法是,在文书夹的根创制一个package.json文件,它钦命了main模块。package.json的例子如下:

{ "name" : "some-library",

"main" : "./lib/some-library.js" }

设若那是在./some-library里的文书夹,require('./some-library')将会试着加载./some-library/lib/some-library.js。

倘诺文件夹里未有package.json文件,Node 会试着加载index.js或index.node文件。举例,倘诺地点的例证里从未 package.json 文件。那么require('./some-library')将会试着加载:

./some-library/index.js

./some-library/index.node

缓存

模块第三次加载后会被被缓存。那即是说,每便调用require('foo')都会回到同四个指标,当然,必须每一回都要深入分析到同叁个文本。

屡屡调用require('foo')大概不会促成模块代码数十次施行。那是超级重大的风味,那样就足以回到 "partially done" 对象,允许加载过渡性的依赖关系,就算大概会滋生环形调用。

若果您期待数十次调用二个模块,那么就输出贰个函数,然后调用这么些函数。

模块换到预先警示

模块的缓存注重于深入分析后的文书名。由此随着调用地方的不及,模块可能拆解深入分析到分化的文本(譬喻,从node_modules文件夹加载卡塔尔国。假诺言之有序为区别的公文,require('foo')或者会再次回到分化的对象。

module对象

{Object}

在各样模块中,变量module是叁个代表当前模块的靶子的援引。为了便于,module.exports能够通过exports全局模块访谈。module不是事实上的大局对象,而是各样模块内部的。

module.exports

{Object}

模块系统创立module.exports对象。相当多人期待团结的模块是有些类的实例。由此,把就要导出的对象赋值给module.exports。注意,将想要的指标赋值给exports,只是轻易的将它绑定到当地exports变量,那有可能实际不是你想要的。

诸如,纵然大家有贰个模块叫a.js。

var EventEmitter = require('events').EventEmitter;

module.exports = new EventEmitter();

// Do some work, and after some time emit

// the 'ready' event from the module itself.

setTimeout(function() {

module.exports.emit('ready');

}, 1000);

另一个文件能够这么写:

var a = require('./a');

a.on('ready', function() {

console.log('module a is ready');

});

只顾,赋给module.exports必得马上实行,而且不能在回调中施行。

x.js:

setTimeout(function() {

module.exports = { a: "hello" };

}, 0);

y.js:

var x = require('./x');

console.log(x.a);

exports alias

exports变量在援引到module.exports的模块里可用。和别的变量同样,即使您给她赋一个新的值,它不再指向老的值。

为了显得这几个特点,假诺完毕:require():

function require(...) {

// ...

function (module, exports) {

// Your module code here

exports = some_func;        // re-assigns exports, exports is no longer

// a shortcut, and nothing is exported.

module.exports = some_func; // makes your module export 0

} (module, module.exports);

return module;

}

设若您对exports和module.exports间的关联以为头晕,这就只用module.exports就好。

module.require(id)

id{String}

回去: {Object} 已经深入分析模块的module.exports

module.require方法提供了生机勃勃种像require()同样从早先时代的模块加载三个模块的议程。

为了能那样做,你必需获得module对象的援用。require()重临module.exports,并且module是二个名列三甲的只可以在一定模块作用域内有效的变量,倘若要选取它,就非得了然的导出。

module.id

{String}

模块的标志符。平时是一点一滴解析的文书名。

module.filename

{String}

模块完全解析的文件名。

module.loaded

{Boolean}

模块是风流倜傥度加载实现,照旧在加载中。

module.parent

{Module Object}

引进那几个模块的模块。

module.children

{Array}

由这么些模块引进的模块。

其他...

为了博取将在用require()加载的规范文件名,还可以require.resolve()函数。

回顾,上面用伪代码的高档算法情势演示了 require.resolve 的干活流程:

require(X) from module at path Y

  1. If X is a core module,

a. return the core module

b. STOP

  1. If X begins with './' or '/' or '../'

a. LOAD_AS_FILE(Y + X)

b. LOAD_AS_DIRECTORY(Y + X)

  1. LOAD_NODE_MODULES(X, dirname(Y))

  2. THROW "not found"

LOAD_AS_FILE(X)

  1. If X is a file, load X as JavaScript text.  STOP

  2. If X.js is a file, load X.js as JavaScript text.  STOP

  3. If X.json is a file, parse X.json to a JavaScript Object.  STOP

  4. If X.node is a file, load X.node as binary addon.  STOP

LOAD_AS_DIRECTORY(X)

  1. If X/package.json is a file,

a. Parse X/package.json, and look for "main" field.

b. let M = X + (json main field)

c. LOAD_AS_FILE(M)

  1. If X/index.js is a file, load X/index.js as JavaScript text.  STOP

  2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP

  3. If X/index.node is a file, load X/index.node as binary addon.  STOP

LOAD_NODE_MODULES(X, START)

  1. let DIRS=NODE_MODULES_PATHS(START)

  2. for each DIR in DIRS:

a. LOAD_AS_FILE(DIR/X)

b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)

  1. let PARTS = path split(START)

  2. let I = count of PARTS - 1

  3. let DIRS = []

  4. while I >= 0,

a. if PARTS[I] = "node_modules" CONTINUE

c. DIR = path join(PARTS[0 .. I] + "node_modules")

b. DIRS = DIRS + DIR

c. let I = I - 1

  1. return DIRS

从全局文件夹加载

只要情况变量NODE_PATH设置为冒号分割的相对路线列表,并且在模块在别的地点并未有找到,Node 将会招来那些渠道。(注意,Windows 里,NODE_PATH用分号分割 卡塔尔。

除此以外, Node 将会寻觅那么些路子。

1:$HOME/.node_modules

2:$HOME/.node_libraries

3:$PREFIX/lib/node

$HOME是客户的 home 文件夹,$PREFIX是 Node 里铺排的node_prefix。

这基本上是野史由来照成的。猛烈提出将所以来的模块放到node_modules文件夹里。那样加载会越来越快。

做客主模块

当 Node 运转三个文件时,require.main就能够安装为它的module。约等于说你能够由此测量试验推断文件是或不是被直接运转。

require.main === module

对于foo.js文件。 假若直接运转node foo.js,重回true, 如若通过require('./foo')是直接运维。

因为module提供了filename属性(经常等于__filename卡塔 尔(英语:State of Qatar),程序的入口点能够通过检查require.main.filename来收获。

附录: 包管理能力

Node 的require()函数语义定义的足足通用,它能支撑各样正规目录结构。诸如dpkg,rpm, 和npm包管理程序,不用修改就足以从 Node 模块营造地面包。

上边大家介绍一个卓有效率的目录结构:

假如大家有三个文书夹/usr/lib/node//,包括内定版本的包内容。

一个包能够依靠于别的包。为了设置包 foo,大概须求设置特定版本的bar包。bar包或然有投机的包重视,有个别标准下,重视关系可能会发生矛盾或产生巡回。

因为 Node 会查找他所加载的模块的realpath(也正是说会分析符号链接卡塔尔国,然后依据上文描述的章程在 node_modules 目录中寻觅信任关系,这种景色跟以下种类布局特别雷同:

/usr/lib/node/foo/1.2.3/-foo包, version 1.2.3.

/usr/lib/node/bar/4.3.2/-foo依赖的bar包内容

/usr/lib/node/foo/1.2.3/node_modules/bar- 指向/usr/lib/node/bar/4.3.2/的暗记链接

/usr/lib/node/bar/4.3.2/node_modules/*- 指向bar包所重视的包的号子链接

之所以,就算存在循环正视或依赖矛盾,每种模块还足以拿到他所依据的包得可用版本。

当foo包里的代码调用foo,将会收获符号链接/usr/lib/node/foo/1.2.3/node_modules/bar指向的本子。然后,当 bar 包中的代码调用require('queue'),将会获得符号链接/usr/lib/node/bar/4.3.2/node_modules/quux指向的本子。

除此以外,为了让模块找出越来越快些,不要将包直接放在/usr/lib/node目录中,而是将它们放在/usr/lib/node_modules//目录中。 那样在借助的包找不到的景况下,就不会直接寻觅/usr/node_modules目录或/node_modules目录了。基于调用 require() 的文本所在真实路线,因而包本人能够放在别的岗位。

为了让 Node 模块对 Node REPL 可用,大概要求将/usr/lib/node_modules文件夹路线增加到景况变量$NODE_PATH。由于模块查找$NODE_PATH文件夹都是相对路线,由此包能够停屏弃何岗位。

本文由金沙棋牌发布于金沙棋牌官方平台,转载请注明出处:如何 hack Node.js 模块?

关键词: