附录: 包管理技巧
Node有一个简单的模块加载系统。在Node中,文件和模块 inone-to-one 通讯。如下例子,foo.js在相同目录下加载模块circle.js
foo.js
内容如下:
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4));
circle.js
内容如下:
var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};
circle.js模块有两个输出函数
area()
和 circumference()
。为输出对象, 添加详细的输出对象(exports
object)。
模块的局部变量是私有的。在这个例子中标量PI是circle.js私有的。
当互相加载时,当期返回时一个模块可能不会执行完成
考虑这种情况:
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。为了预防无限循环拷贝未完成的返回给b.js的a.js的输出对象。 b.js完成加载,并且将其输出对象提供给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文档中有详尽的描述。
核心模块的定义在node的源码文件夹lib/文件夹中。
如果require()鉴定通过,核心模块将优先加载。例如,require("http")将总是返回建立的http模块,即使有一个文件的名字。
如果执行的文件名没有找到,node将尝试加载请求后缀为.js、.json和.node 文件名相同的文件。.js文件解析成JavaScript的文本文件,
.json文件解析成JSON文本文件,.node文件解析成用dlopen加载的编译的附加模块
模块前缀为“./”是一个调用require()加载的文件的相对路径。也就是说,用require('./circle')加载时找到此文件,circle.js必须要和foo.js在同一目录下面。
没有'/'或者'./'前缀表示文件时,模块是核心模块或者是从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
这允许程序设置依赖的本地化,所以不会引起冲突。
这非常方便地组织程序和libraries 到一个独立的目录下,并且对library提供一个简单的入口点。在某个文件夹有三种方式作为参数传递给require()。
第一种在文件夹的根路径下(在main 模块中申明描述的)创建一个package.json文件。一个package.json文件的例子如下:
{ "name" : "some-library",
"main" : "./lib/some-library.js" } 如果实在 ./some-library
的文件夹下,require('./some-library')
尝试着加载。./some-library/lib/some-library.js
。 这是package.json文件的Node意识扩展。 如果在目录中没有package.json存在,node将尝试着加载文件夹外的index.js或者index.node文件。例如:上例子中没有package.json文件, 然后 require('./some-library')
尝试着加载
./some-library/index.js
./some-library/index.node
当第一次加载时模块都将被缓存。也就是说如果在相同的文件中,每次调用 require('foo')
将精确地获取返回的相同的对象,
多次调用require("foo")可能不会引起多次调用。这是一个很只要的特征。随之,部分完成对象可以返回,因此即时互调时都可依靠加载来传递。
如果想要模块代码多次调用,需要外置(export)一个函数,并调用此函数。
模块的缓存基于决定的文件名。因为模块基于调用模块本地解析可能使用不同的文件名(从node_modules文件夹中加载)。如果不同的文件解析,不保证require("foo")将会总是返回准确相同的对象。
exports对象是通过系统模块创建的。有时这不是合要求的,很多时候需要一些类的实体。为了做此分配,描述module.exports的export对象。例如建立一个叫a.js的模块(module)。
var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();
//一段时间后从这个模块本身触发'ready'事件。
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);
module.require方法提供一个加载模块的方法,就如从原始的module中调用require()。
为了做这些,必须获取module对象的引用。require()返回exports,module仅在模块范围内可用,为了使用必须明确外置(export)。
当require()被调用时,为获取加载需要的精确 的文件名,使用require.resolve()函数。
将上述所有综合,这是高等级(high-level)需要请求的伪代码(pseudocode)算法(algorithmin )。
如下:
停止
require(X) from module at path Y
在路径Y上从模块中请求X
1、如果X是代码模块
a.返回代码模块。
b.停止
2、如果X以'./' 或者 '/' 或者 '../'开头。
a. 以文件加载(LOAD_AS_FILE(Y + X))
b. 以目录加载(LOAD_AS_DIRECTORY(Y + X))
3、加载节点模块(LOAD_NODE_MODULE(X, dirname(Y)))
4、抛出未找到异常"not found"。
作为文件加载 LOAD_AS_FILE(X) 1、如果X是一个文件,以JavaScript文本加载X。停止 2、如果X.js是一个文件,以JavaScript文本加载X。
3、如果X.node是一个文件,以二进制插件加载X.node。停止
作为目录加载 LOAD_AS_DIRECTORY(X) 1、如果X/package.json是一个文件, a.解析X/package.json,并查找主要域('main' field) b. 设置M = X + json主域(json main field) c. 加载文件M 2、如果X/index.js是一个文件, 作为JavaScript文本加载X/inde.js。停止。 3、如果X/index.node是一个文件,作为一个二进制插件加载,停止。
加载节点模块 LOAD_NODE_MODULES(X, START) 1、设置DIRS = NODE_MODULES_PATHS(START)。 2、对于目录下的每个目录 a.加载文件(DIR/X) LOAD_AS_FILE(DIR/X) b.加载目录(DIR/X) LOAD_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1、设置PARTS = path split(START) 2、设置ROOT = PARTS中‘node_modules’的第一个实体的索引。
3、设置I = PARTS - 1
4、设置DIRS = []
5、while I > ROOT
a. 如果PARTS[I] = "node_modules" CONTINUE
c.DIR = (PARTS[0...I]+ 'node_modules'的联合路径)
b.DIRS = DIRS + DIR
c.设置I = I - 1 1. let PARTS = path split(START)
6、返回DIRS
如果NODE_PATH环境变量设置成冒号分割(colon-delimited)的绝对路径的集合,如果在任何地方都没找到,node将会为模块查找这些目录。
(注意:在Windows下,NODE_PATH分割是用分号代替冒号)
此外,node还会搜索一下位置:
1. $HOME/.node_modules
2. $HOME/.node_libraries
3. $PREFIX/lib/node
这很具有历史意义。非常建议将依赖文件或包放置在node_modules
文件夹中,这将会被快速加载,并且可靠。
当一个文件直接在node中运行时,require.main被这只到module。意思就是说你可以决定一个文件在测试时是否已经被直接运行。
require.main === module
对于文件foo.js,如果执行node foo.js 将为true。但通过require('./foo')时运行时为false。
节点的语义require()函数被设计成一般足以支持一些健全的目录结构。包管理程序(Package manager )如:dpkg,rpm,和npm将有希望从node模块中不需要修改创建本地包。
下面给出目录结构的建议:
在/usr/node/<some-package>/<some-version>目录下面放置一个包的详细版本说明。
包能够以来其它的。为了装载foo包,你必须装载包bar的版本说明。包bar有自己的依赖,在一些情况下,他们的依赖包可能形成碰撞,或互调循环。
/usr/lib/node/foo/1.2.3/
- foo包的详细内容, 版本 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包中代码使用requirt("bar"),将给出版本连接到/usr/lib/node/foo/1.2.3/node_modules/bar。然后在bar包的代码中调用reuire(''quux),将给出版本连接到/usr/lib/node/bar/4.3.2/node_module/quux。
此外,为使的模块最佳检查process,不是把包直接放置在/usr/lib/node下,可以将其放置在/usr/lib/node_moduls/<name>/<version>。node将不会再/usr/node_modules或/node_modules.查找缺失的依赖包。
为使得模块对nod REPL可用,将/usr/lib/node_modules文件夹设置到$NODE_PATH的环境变量下可能很有用。因为模块检查使用的node_mudules文件夹都是相对路径,
并且基于真实路径的文件调用require(),所以包可以在任何地方。