Subsecciones

Modulos

Introducción

[~/javascript/node.js/creating_modules(master)]$ cat foo.js 
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
           + circle.area(4));

[~/javascript/node.js/creating_modules(master)]$ cat circle.js 
var PI = Math.PI;

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

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

[~/javascript/node.js/creating_modules(master)]$ node foo.js The area of a circle of radius 4 is 50.26548245743669

El módulo circle.js exporta las funciones area() y circumference(). Para exportar un objeto lo añadimos al objeto expecial exports.

Las variables locales al módulo serán privadas. En este ejemplo la variable PI es privada a circle.js.

[~/javascript/node.js/creating_modules(master)]$ node debug foo.js 
< debugger listening on port 5858
connecting... ok
break in foo.js:1
  1 var circle = require('./circle.js');
  2 console.log( 'The area of a circle of radius 4 is '
  3            + circle.area(4));
debug> n
break in foo.js:2
  1 var circle = require('./circle.js');
  2 console.log( 'The area of a circle of radius 4 is '
  3            + circle.area(4));
  4 
debug> repl
Press Ctrl + C to leave debug repl
> circle
{ circumference: [Function],
  area: [Function] }
> circle.area(2)
12.566370614359172
> PI
ReferenceError: PI is not defined
>

Ciclos

[~/javascript/node.js/creating_modules/cycles(master)]$ cat 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');

[~/javascript/node.js/creating_modules/cycles(master)]$ cat 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');

[~/javascript/node.js/creating_modules/cycles(master)]$ cat 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 the b.js module. b.js then finishes loading, and its exports object is provided to the a.js module.

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

[~/javascript/node.js/creating_modules/cycles(master)]$ 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

Especificación de Ficheros Conteniendo Módulos

  1. 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.

  2. .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

  3. 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.

  4. A module prefixed with './' is relative to the file calling require().

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

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

Carga desde Carpetas node_modules

  1. 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_mo1dules, and attempts to load the module from that location.

  2. 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' called require('bar.js'), then node would look in the following locations, in this order:

/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.

Las Carpetas Usadas Como Módulos

It is convenient to organize programs and libraries into self-contained directories, and then provide a single entry point to that library.

There are a few ways in which a folder may be passed to require() as an argument.

  1. 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:

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

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

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

  2. 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

  1. 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.

  2. 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.

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

  4. 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 guarantee that require('foo') will always return the exact same object, if it would resolve to different files.

El Objeto module y module.exports

  1. In each module, the module free variable is a reference to the object representing the current module.

  2. In particular module.exports is the same as the exports object.

  3. module isn't actually a global but rather local to each module.

  4. 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.

    La asignación a module.exports debe hacerse inmediatamente. No puede hacerse en un callback.

Algoritmo de Búsqueda Ejecutado por require

require(X) from module at path Y

  1. If X is a core module,
    1. return the core module
    2. STOP
  2. If X begins with './' or '/' or '../'
    1. LOAD_AS_FILE(Y + X)
    2. 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

Casiano Rodríguez León
2013-04-23