modules(模块)
node有一个简单的模块加载系统。
在Node中,文件和模块 inone-to-one 通讯。
例子,foo.js在相同目录下加载模块circle.js
foo.js内容:
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私有的。
Cycles (周期)
当互相加载时,当期返回时一个模块可能不会执行完成
考虑这种情况:
a.js:
b.js:
main.js:
当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
如果在你程序中有循环依赖模块中,确保规划中间的关系。
Core Modules (核心模块)
Node有几个编译成二进制的模块。这些模块在NODE文档中有详尽的描述。
核心模块的定义在node的源码文件夹lib/文件夹中。
如果require()鉴定通过,核心模块将优先加载。例如,require("http")将总是返回建立的http模块,即使有一个文件的名字。
File Modules (文件模块)
如果执行的文件名没有找到,node将尝试加载请求后缀为.js、.json和.node 文件名相同的文件。.js文件解析成javascript的文本文件,
.json文件解析成JSON文本文件,.node文件解析成用dlopen加载的编译的附加模块
模块前缀为"/"是一个文件的绝对路径。如:require('/home/marco/foo.js')将加载在/home/marco/foo.js下的文件。
模块前缀为“./”是一个调用require()加载的文件的相对路径。也就是说,用require('./circle')加载时找到此文件,circle.js必须要和foo.js在同一目录下面。
没有'/'或者'./'前缀表示文件时,模块是核心模块或者是从node_modules文件夹里加载。
从`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
这允许程序设置依赖的本地化,所以不会引起冲突。
文件夹作为模块(Folders as Modules )
这非常方便地组织程序和libraries 到一个独立的目录下,并且对library提供一个简单的入口点。在某个文件夹有三种方式作为参数传递给require()。
第一种在文件夹的根路径下(在main 模块中申明描述的)创建一个package.json文件。
一个package.json文件的例子如下:
如果实在 ./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
缓存 (Caching )
当第一次加载时模块都将被缓存。也就是说如果在相同的文件中,每次调用 require('foo')将精确地获取返回的相同的对象,
多次调用require("foo")可能不会引起多次调用。这是一个很只要的特征。随之,部分完成对象可以返回,因此即时互调时都可依靠加载来传递。
如果想要模块代码多次调用,需要外置(export)一个函数,并调用此函数。
模块缓存说明(Module Caching Caveats )
模块的缓存基于决定的文件名。因为模块基于调用模块本地解析可能使用不同的文件名(从node_modules文件夹中加载)。如果不同的文件解析,不保证require("foo")将会总是返回准确相同的对象。
模块外置接口(module.exports )
exports对象是通过系统模块创建的。有时这不是合要求的,很多时候需要一些类的实体。为了做此分配,描述module.exports的export对象。
例如,建立一个叫a.js的模块(module)。
var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();
//一段时间后从这个模块本身触发'ready'事件。
setTimeout(function() {
module.exports.emit('ready');
}, 1000);
在另外一个文件中,可以这么做:
注意,分配module.exports必须立即完成。不能在任何回调函数中完成。
如下就不能生效:
x.js:
y.js:
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
从全局文件夹中加载 Loading from the global folders
如果NODE_PATH环境变量设置成冒号分割(colon-delimited)的绝对路径的集合,如果在任何地方都没找到,node将会为模块查找这些目录。
(注意:在Windows下,NODE_PATH分割是用分号代替冒号)
此外,node还会搜索一下位置:
1. $HOME/.node_modules
2. $HOME/.node_libraries
3. $PREFIX/lib/node
$HOME是用户home目录,$PREFIX是节点配置的安装前缀(installPrefix)。
这很具有历史意义。非常建议将依赖文件或包放置在node_modules文件夹中,这将会被快速加载,并且可靠。
访问主模块 Accessing the main module
当一个文件直接在node中运行时,require.main被这只到module。意思就是说你可以决定一个文件在测试时是否已经被直接运行。
require.main === module
对于文件foo.js,如果执行node foo.js 将为true。但通过require('./foo')时运行时为false。
因为module提供一个文件名属性(一般情况下等于_filename),当前应用的入口点可以通过检查require.main.filename获得。
附录:包管理技巧( Package Manager Tips)
节点的语义require()函数被设计成一般足以支持一些健全的目录结构。包管理程序(Package manager )如:dpkg,rpm,和npm将有希望从node模块中不需要修改创建本地包。
下面给出目录结构的建议:
在/usr/node/<some-package>/<some-version>目录下面放置一个包的详细版本说明。
包能够以来其它的。为了装载foo包,你必须装载包bar的版本说明。包bar有自己的依赖,在一些情况下,他们的依赖包可能形成碰撞,或互调循环。
因为node查找任何模块加载的真实路径,并在node_modules文件夹中根据上面的描述查找他们的依赖包,用下面的架构设计很容易解决尚需问题:
/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(),所以包可以在任何地方。