【亚洲必赢官网】什么 hack Node.js 模块?

如何 hack Node.js 模块?

2016/10/28 · JavaScript
· NodeJS

原文出处: 天猫前端团队(FED)-
宣予   

亚洲必赢官网 1

为什么要去 hack?

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. 暂时且热切的急需,提 PR 已经来不及了。
  3. 何以不直接去改源码?考虑到工具会不定期升级,想使用工具的风行特性,改源码可维护性太差。

在作业支付进度中,往往会借助一些 Node.js 模块,hack 那些 Node.js
模块的首要目标是在不修改工具源码的事态下,篡改一些一定的意义。可能会是由于以下两种意况的设想:

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
一些工具的手动输入;在本土营造时,需求修改通用的营造流程
(前面案例部分会详细说)

连接存在部分卓越的地点需要,不自然能当做工具的通用必要来暴光正常的 API
给越来越多的用户。
临时且殷切的要求,提 PR 已经来不及了。
缘何不直接去改源码?考虑到工具会不定期升级,想利用工具的摩登特性,改源码可维护性太差。

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 Files\nvm
path: D:\Program Files\nodejs
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 Files\nvm
NVM_SYMLINK D:\Program Files\nodejs
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,例如热加载。

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

        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 操作都兼备侵入性。

b 是序列 c 看重的一个工具模块,b 信赖 a。希望只在品种 c 中,b 调用 a
时,a 的函数里能注入一些主意 injectSomething()

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内容:

有关原理

hack 之前 c 的输出

        核心模块的源码都在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【亚洲必赢官网】什么 hack Node.js 模块?。,而
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();
};

function(){
dosomething();
}

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
唯有一个。

期望:hack 之后 c 的输出

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 进度里都是行得通的。

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

};

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
最后的调用方法是千篇一律的,只是函数指向分歧而已。

具体案例比如:在做个人自动化工具时,要求 mock
一些工具的手动输入;在地头打造时,须要修改通用的营造流程(后边案例部分会详细说)

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

动用模块 cache 篡改模块对象属性

};

多进程的状态

模块间调用的链路比较长,有可能会新建子进度,须要考虑你项目中的入口文件和您要求代理的文本是或不是在一个经过中,简单的格局就是在进口文件和您需求代理的文件打印
pid:

JavaScript

console.log(process.pid)

1
console.log(process.pid)

一经相同,那么直接在输入调用前代理即可,否则事态会更复杂点,须要找到呼应的进程调用处举行代理。

那是自身最早拔取的法子,在模块 a 的档次是 object 的时候,可以在大团结的种类c 中提前 require 模块 a,依照你的要求修改部分品质,那样当模块 b 再去
require 模块 a 时,从缓存中取出的模块 a 已经是被改动过的了。

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

案例

DEF
是Taobao前端的并轨开发条件,协助前端模块创制、创设打包、发布等一种类流程。
在偏下案例中,主要 hack 的 Node.js 项目便是 DEF。

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

加载到模块里的变量是私房的,就像是模块是带有在一个函数里。在那么些事例里,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);

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

一经你想模块里的根像一个函数一样的出口(比如
构造函数),或者你想出口一个完完全全对象,这就分派给module.exports,而不是exports。

篡改创设流程(webpackconfig)

情景:一个天猫的前端组件,必要在应用def本地调试时提前转移一个文件内容。(Taobao组件的营造会安分守己组件类型统一创设器,并不是各样组件单独去安插)

原因:一般的话,那种情况可以选拔注释代码大法,本地调试时打开注释,公布前干掉。但诸如此类造成代码很不佳看,也易于招惹误操作。不妨在本土调试的
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);

自我想修改 a 的措施 p,在 c 中展开如下修改即可,而无需直接去修改工具 a、b
的源码:

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

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

var square = require(‘./square.js’);

缺点:在一些模块属性是动态加载的情景,不是那么灵敏,而且不得不篡改引用对象。但多数动静下或者可以满意须求的。

var mySquare = square(2);

修改require.cache

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

在遇见模块暴光的是是非非对象的意况,就须求直接去修改 require 的 cache
对象了。关于修改 require.cache
的一蹴而就,会在背后的原理部分详细说,先来简单来讲下操作:

square定义在square.js文件里:

//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);

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

缺点:可能继续调用链路会有人手动去修改 require.cache,例如热加载。

module.exports = function(width) {

修改 require

return {

那种办法是一贯去代理 require
,是最稳妥的主意,但是侵入性相对来说比较强。Node.js 文件中的 require
其实是在 Module 的原型方法上,即
Module.prototype.require。前边会详细说,先不难说下操作:

area: function() {

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;
}

return width \ width;*

症结:对全体 Node.js 进程的 require 操作都富有侵入性。

}

连锁原理

};

node的开行进度

}

俺们先来探视在运作 node a.js 时暴发些什么?

模块系统在require(“module”)模块里达成。

上图是node运行 a.js 的一个骨干流程,Node.js 的起步程序
bootstrap_node.js 是在 node::LoadEnvironment
中被登时实施的,bootstrap_node.js 中的 startup()
是包装在一个匿名函数里面的,所以在一回施行 node 的表现中 startup()
只会被调用了一遍,来确保 bootstrap_node.js
的所执行的所有看重只会被加载一次。C++ 语言部分中:

Cycles

//node_main.cc
如若在win环境举行wmain(),unix则实施main(),函数最后都履行了node::Start(argc,
argv)

环形调用require(),当再次回到时模块可能都没实施完结。

ifdef _WIN32

int wmain()

考虑以下场景:

else

int main()

a.js:

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,也就是模块加载的进程。

// 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();
};

一个历程只存在一个 cache 对象?

先来探视 module._load 干了什么?

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 只有一个。

Module._cache 和 require.cache 的关系

可以看下 Module.prototype._compile 这么些法子,那其中会对我们写的 Node.js
文件举办一个包装,注入一些上下文,包罗 require:

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 中大家会意识

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

所以,Module._cache 和 require.cache 是同样的,那么大家直接修改
require.cache 的缓存内容,在一个 Node.js 进度里都是一蹴而就的。

require 分歧景观的挂载

最开始我以为 require 是挂载在 global 上的,为了图方便,一般用 Node.js
repl 来测试:

$ 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
文件中应用会意识:

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

从上文可见,Node.js 文件中的 require 其实是源于于
Module.prototype._compile 中流入的 Module.prototype.require,
而最后的指向其实是 Module._load,并从未挂载到 module 上下文环境中的
global 对象上。

而 repl 中也是有 module 实例,于是我尝试在 repl 中打印:

$ node

global.require === module.require
false

结果有点奇怪,于是我继续深究了下。在 bootstrap_node.js 中找到 repl
的调用文件 repl.js

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

获得结论:在 repl 中,module.require 和 global.require
最后的调用方法是如出一辙的,只是函数指向不一样而已。

注意点

path路径

require.cache 是一个 key、value 的 map,key
看上去是模块所在的相对路径,不过是不可以用相对路径直接去用的,需要require.resolve 来分析路径,解析后才是 cache 中科学的 key 格式。

上面相比下分别:

// 模块的相对路径
/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:

console.log(process.pid)

若果一致,那么直接在输入调用前代理即可,否则事态会更复杂点,须求找到相应的经过调用处进行代理。

案例

DEF
是Tmall前端的合并开发环境,协助前端模块创设、打造打包、发表等一各类流程。
在以下案例中,首要 hack 的 Node.js 项目便是 DEF。

篡改输入(prompt)

情景:使用 DEF 创立模块 or 发布模块时

案由:想一键完结批量创办 or 批量公布,不想手动输入。

化解进度:以创制模块为例

先是找到 DEF 的进口文件,即一个 bin
目录下的路线,可以经过这些进口文件不断追溯下去,发现创制模块的 generator
用的是 yeoman-generator 的法子。对 prompt
的办法举行代理,可以将该基础库提前 require,更改掉其 prompt
的主意即可。
沾满示例代码(示例只篡改 def add
模块的创造项目,其余输入的歪曲方法类似):

console.log(‘a starting’);

!/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);

篡改创设流程(webpackconfig)

此情此景:一个Tmall的前端组件,必要在使用def本地调试时提前转移一个文本内容。(Taobao组件的营造会听从组件类型统一打造器,并不是每个组件单独去安排)

缘由:一般的话,那种处境能够选用注释代码大法,本地调试时打开注释,发表前干掉。但那样造成代码很不美观,也便于滋生误操作。不妨在地头调试的
reflect 进程中动态转换掉就好了。

杀鸡取卵进度:

顺藤摸瓜 def dev 调用链路,找到最终reflect的文本, 在那一个创设器
@ali/builder-cake-kpm 项目里。所采纳的webpack的配备项在
@ali/cake-webpack-config 下。
当今就是往 webpack 配置项里动态注入一个 webpack loader
的进度了,我索要的 loader 是一个
preLoader,代码卓殊简单,我把它身处工作类型的公文里:

module.exports = function(content) {
return content.replace(‘require(‘./plugin’)’,
“require(‘./localPlugin’)”);
};

@ali/cake-webpack-config 揭穿的是个函数而非对象,所以必须从 require
下手了,最终附上案例的代办进度:

exports.done = false;

!/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);

结束语

去 hack 一个 Node.js 模块,要求对该 Node.js
模块的调用链路有自然的摸底,在广大情状下,不肯定是最优的不二法门,但也不失为一种缓解方案。有趣的是,Node.js
源码中实际有一行这样的注释:

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

So, just hacking for fun!

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),程序的入口点可以经过检查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文件夹都是相对路径,因而包可以放置任何岗位。

网站地图xml地图