Node.js之Modules

Modules#  模块,组件

Stability: 5 - Locked

Node has a simple module loading system.  In Node, files and modules are in one-to-one correspondence.  As an example, foo.js loads the modulecircle.js in the same directory.

Node.js有一套简单的模块加载系统。在Node.js中文件和模块都是一一对应联系的。如下实例:foo.js在同一目录下加载了模块circle.js

The contents of foo.js:

var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
           + circle.area(4));

The contents of circle.js:

var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};

The module circle.js has exported the functions area() andcircumference().  To export an object, add to the special exportsobject.

Variables local to the module will be private. In this example the variable PI is private to circle.js.

The module system is implemented in the require("module") module.

模块circle.js定义了area()和circumference()方法。输出一个对象,添加到特殊exports对象上。Node.js中用于输出的接口吧

本模块的变量为私有的。该实例中的PI变量就是circle.js模块的私有变量

该模块系统是通过require('module')模块来进行实施应用的。

Cycles#

When there are circular require() calls, a module might not be done being executed when it is returned.

当出现循环require()请求时,当模块返回时,将不会运行已经被执行的模块。

实例如下:

Consider this situation:

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

When main.js loads a.js, then a.js in turn loads b.js.  At that point, b.js tries to load a.js.  In order to prevent an infinite loop an unfinished copy of the a.js exports object is returned to theb.js module.  b.js then finishes loading, and its exports object is provided to the a.js module.

当main.js加载a.js时,然后a.js按顺序加载b.js.在这时,b.js要加载a.js.

但为了阻止死循环一个未完成的a.js的copy。exports对象直接返回到b.js模块。b.js然后完成了加载,并它的exports对象提供给了a.js模块。

By the time main.js has loaded both modules, they're both finished. The output of this program would thus be:

当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

If you have cyclic module dependencies in your program, make sure to plan accordingly.

所以,如果在你的代码中存在独立的循环模块,请保证它们按顺序运行。

Core Modules#核心模块(组件)

Node has several modules compiled into the binary.  These modules are described in greater detail elsewhere in this documentation.

The core modules are defined in node's source in the lib/ folder.

Node.js把几个模块解析成了二进制。这些模块在本文档中的其他地方已有了非常详尽的解释。

核心模块被定义在node.js源文件的lib文件夹下。

Core modules are always preferentially loaded if their identifier is passed to require().  For instance, require('http') will always return the built in HTTP module, even if there is a file by that name.

如果它们的标示传给了require()方法,核心模块总是预先被加载。例如,即使存在同名文件,require('http')总是返回的内建HTTP模块。

File Modules#文件模块(组件)

If the exact filename is not found, then node will attempt to load the required filename with the added extension of .js, .json, and then .node.

如果指定的文件名没有找到,node.js会去依次加载后缀名为.js、.json和.node的文件。

.js files are interpreted as JavaScript text files, and .json files are parsed as JSON text files. .node files are interpreted as compiled addon modules loaded with dlopen.

.js后缀文件理解为JavaScript文本文件,.json后缀文件可认为是JSON文本文件;.node后缀文件可解释为用dlopen加载的编译的插件模块。

A module prefixed with '/' is an absolute path to the file.  For example, require('/home/marco/foo.js') will load the file at/home/marco/foo.js.

一个模块前缀带有’/’对一个文件来说是个绝对路径。例如:require('/home/marco/foo.js')将加载在/home/marco目录下的foo.js文件。

A module prefixed with './' is relative to the file calling require(). That is, circle.js must be in the same directory as foo.js forrequire('./circle') to find it.

一个模块前缀带有的是’./’对于文件调用require()方法时是相对的。因此 circle.js必须和foo.js在同一目录下,require('./circle')才能找到circle.js文件。

Without a leading '/' or './' to indicate a file, the module is either a "core module" or is loaded from a node_modules folder.

没有前导‘/‘或’./'表示一个文件,那么该模块不是核心模块,那就是从node_modules文件夹加载来的。

If the given path does not exist, require() will throw an Error with itscode property set to 'MODULE_NOT_FOUND'.

如果给定的路径不存在,require()方法将抛出一个错误,同时它的代码属性将被设为MOULE_NOT_FOUND。

Loading from node_modules Folders#  从node_modules文件夹加载

If the module identifier passed to require() is not a native module, and does not begin with '/', '../', or './', then node starts at the parent directory of the current module, and adds /node_modules, and attempts to load the module from that location.

如果传给require()方法的标示,不是本地的模块,也不是以‘/'、‘../'或’./‘开头的,那么node.js将从当前模块的父目录中开始,并添加’/node_modules’,而且尝试该位置加载模块。

If it is not found there, then it moves to the parent directory, and so on, until the root of the tree is reached.

如果在这里还是没有找到,那就好转移到父目录,如此下去,直到到达根节点为止。

For example, if the file at '/home/ry/projects/foo.js' calledrequire('bar.js'), then node would look in the following locations, in this order:

例子如下:如果文件在'/home/ry/projects/foo.js',运行的是require('bar.js'),接着node.js会按如下顺序检索下面的位置。

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

This allows programs to localize their dependencies, so that they do not clash.

这样使得程序定位自己的依赖性,使得它们不冲突。

Folders as Modules# 文件夹作为模块(组件)

It is convenient to organize programs and libraries into self-contained directories, and then provide a single entry point to that library. There are three ways in which a folder may be passed to require() as an argument.

组织程序和库到独立的目录中很方便,并为库提供单一的接入口。一个文件夹可能作为参数传给require()方法,这有三种方法。如下:

The first is to create a package.json file in the root of the folder, which specifies a main module.  An example package.json file might look like this:

第一种:在文件夹根目录下创建一个.json文件包,指定一个主模块。.json文件包例子如下:

{ "name" : "some-library",
  "main" : "./lib/some-library.js" }

If this was in a folder at ./some-library, thenrequire('./some-library') would attempt to load./some-library/lib/some-library.js.

在./some-libarary目录下文件夹中存在这样的文件,require('./some-library‘)将加载./some-library/lib/some-library.js文件。

This is the extent of Node's awareness of package.json files.

If there is no package.json file present in the directory, then node will attempt to load an index.js or index.node file out of that directory.  For example, if there was no package.json file in the above example, then require('./some-library') would attempt to load:

  • ./some-library/index.js
  • ./some-library/index.node

Caching#

Modules are cached after the first time they are loaded.  This means (among other things) that every call to require('foo') will get exactly the same object returned, if it would resolve to the same file.

Multiple calls to require('foo') may not cause the module code to be executed multiple times.  This is an important feature.  With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles.

If you want to have a module execute code multiple times, then export a function, and call that function.

Module Caching Caveats#

Modules are cached based on their resolved filename.  Since modules may resolve to a different filename based on the location of the calling module (loading from node_modules folders), it is not a guaranteethat require('foo') will always return the exact same object, if it would resolve to different files.

The module Object#

  • {Object}

In each module, the module free variable is a reference to the object representing the current module.  In particularmodule.exports is the same as the exports object.module isn't actually a global but rather local to each module.

module.exports#

  • Object

The exports object is created by the Module system. Sometimes this is not acceptable, many want their module to be an instance of some class. To do this assign the desired export object to module.exports. For example suppose we were making a module called 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);

Then in another file we could do

var a = require('./a');
a.on('ready', function() {
  console.log('module a is ready');
});

Note that assignment to module.exports must be done immediately. It cannot be done in any callbacks.  This does not work:

x.js:

setTimeout(function() {
  module.exports = { a: "hello" };
}, 0);

y.js:

var x = require('./x');
console.log(x.a);

module.require(id)#

  • id String
  • Return: Object exports from the resolved module

The module.require method provides a way to load a module as ifrequire() was called from the original module.

Note that in order to do this, you must get a reference to the moduleobject.  Since require() returns the exports, and the module is typically only available within a specific module's code, it must be explicitly exported in order to be used.

module.id#

  • String

The identifier for the module.  Typically this is the fully resolved filename.

module.filename#

  • String

The fully resolved filename to the module.

module.loaded#

  • Boolean

Whether or not the module is done loading, or is in the process of loading.

module.parent#

  • Module Object

The module that required this one.

module.children#

  • Array

The module objects required by this one.

All Together...#

To get the exact filename that will be loaded when require() is called, use the require.resolve() function.

Putting together all of the above, here is the high-level algorithm in pseudocode of what require.resolve does:

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with './' or '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. 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.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)
2. If X/index.js is a file, load X/index.js as JavaScript text.  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 ROOT = index of first instance of "node_modules" in PARTS, or 0
3. let I = count of PARTS - 1
4. let DIRS = []
5. while I > ROOT,
   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
6. return DIRS

Loading from the global folders#

If the NODE_PATH environment variable is set to a colon-delimited list of absolute paths, then node will search those paths for modules if they are not found elsewhere.  (Note: On Windows, NODE_PATH is delimited by semicolons instead of colons.)

Additionally, node will search in the following locations:

  • 1: $HOME/.node_modules
  • 2: $HOME/.node_libraries
  • 3: $PREFIX/lib/node

Where $HOME is the user's home directory, and $PREFIX is node's configured node_prefix.

These are mostly for historic reasons.  You are highly encouraged to place your dependencies locally in node_modules folders.  They will be loaded faster, and more reliably.

Accessing the main module#

When a file is run directly from Node, require.main is set to itsmodule. That means that you can determine whether a file has been run directly by testing

require.main === module

For a file foo.js, this will be true if run via node foo.js, butfalse if run by require('./foo').

Because module provides a filename property (normally equivalent to__filename), the entry point of the current application can be obtained by checking require.main.filename.

Addenda: Package Manager Tips#

The semantics of Node's require() function were designed to be general enough to support a number of sane directory structures. Package manager programs such as dpkg, rpm, and npm will hopefully find it possible to build native packages from Node modules without modification.

Below we give a suggested directory structure that could work:

Let's say that we wanted to have the folder at/usr/lib/node/<some-package>/<some-version> hold the contents of a specific version of a package.

Packages can depend on one another. In order to install package foo, you may have to install a specific version of package bar.  The bar package may itself have dependencies, and in some cases, these dependencies may even collide or form cycles.

Since Node looks up the realpath of any modules it loads (that is, resolves symlinks), and then looks for their dependencies in thenode_modules folders as described above, this situation is very simple to resolve with the following architecture:

  • /usr/lib/node/foo/1.2.3/ - Contents of the foo package, version 1.2.3.
  • /usr/lib/node/bar/4.3.2/ - Contents of the bar package that foodepends on.
  • /usr/lib/node/foo/1.2.3/node_modules/bar - Symbolic link to/usr/lib/node/bar/4.3.2/.
  • /usr/lib/node/bar/4.3.2/node_modules/* - Symbolic links to the packages that bar depends on.

Thus, even if a cycle is encountered, or if there are dependency conflicts, every module will be able to get a version of its dependency that it can use.

When the code in the foo package does require('bar'), it will get the version that is symlinked into /usr/lib/node/foo/1.2.3/node_modules/bar. Then, when the code in the bar package calls require('quux'), it'll get the version that is symlinked into/usr/lib/node/bar/4.3.2/node_modules/quux.

Furthermore, to make the module lookup process even more optimal, rather than putting packages directly in /usr/lib/node, we could put them in/usr/lib/node_modules/<name>/<version>.  Then node will not bother looking for missing dependencies in /usr/node_modules or /node_modules.

In order to make modules available to the node REPL, it might be useful to also add the /usr/lib/node_modules folder to the $NODE_PATH environment variable.  Since the module lookups using node_modules folders are all relative, and based on the real path of the files making the calls torequire(), the packages themselves can be anywhere.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值