Browse Source

node_modules module lookup, +docs and test.

v0.7.4-release
isaacs 14 years ago
committed by Ryan Dahl
parent
commit
46513483cd
  1. 23
      doc/api/modules.markdown
  2. 38
      lib/module.js
  3. 3
      test/fixtures/node_modules/asdf.js
  4. 2
      test/fixtures/node_modules/bar.js
  5. 13
      test/fixtures/node_modules/baz/index.js
  6. 2
      test/fixtures/node_modules/baz/node_modules/asdf.js
  7. 3
      test/fixtures/node_modules/foo.js
  8. 3
      test/fixtures/node_modules/node_modules/bar.js
  9. 5
      test/simple/test-module-loading.js

23
doc/api/modules.markdown

@ -82,11 +82,32 @@ then `require('./foo/bar')` would load the file at
entry point to their module, while structuring their package how it entry point to their module, while structuring their package how it
suits them. suits them.
Any folders named `"node_modules"` that exist in the current module path
will also be appended to the effective require path. This allows for
bundling libraries and other dependencies in a 'node_modules' folder at
the root of a program.
To avoid overly long lookup paths in the case of nested packages,
the following 2 optimizations are made:
1. If the module calling `require()` is already within a `node_modules`
folder, then the lookup will not go above the top-most `node_modules`
directory.
2. Node will not append `node_modules` to a path already ending in
`node_modules`.
So, for example, if the file at
`/usr/lib/node_modules/foo/node_modules/bar.js` were to do
`require('baz')`, then the following places would be searched for a
`baz` module, in this order:
* 1: `/usr/lib/node_modules/foo/node_modules`
* 2: `/usr/lib/node_modules`
`require.paths` can be modified at runtime by simply unshifting new `require.paths` can be modified at runtime by simply unshifting new
paths onto it, or at startup with the `NODE_PATH` environmental paths onto it, or at startup with the `NODE_PATH` environmental
variable (which should be a list of paths, colon separated). variable (which should be a list of paths, colon separated).
The second time `require('foo')` is called, it is not loaded again from The second time `require('foo')` is called, it is not loaded again from
disk. It looks in the `require.cache` object to see if it has been loaded disk. It looks in the `require.cache` object to see if it has been loaded
before. before.

38
lib/module.js

@ -163,6 +163,35 @@ Module._findPath = function(request, paths) {
return false; return false;
}; };
// 'from' is the __dirname of the module.
Module._nodeModulePaths = function(from) {
// guarantee that 'from' is absolute.
from = path.resolve(from);
// note: this approach *only* works when the path is guaranteed
// to be absolute. Doing a fully-edge-case-correct path.split
// that works on both Windows and Posix is non-trivial.
var splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//;
// yes, '/' works on both, but let's be a little canonical.
var joiner = process.platform === 'win32' ? '\\' : '/';
var paths = [];
var parts = from.split(splitRe);
var root = parts.indexOf('node_modules') - 1;
if (root < 0) root = 0;
var tip = parts.length - 1;
for (var tip = parts.length - 1; tip >= root; tip --) {
// don't search in .../node_modules/node_modules
if (parts[tip] === 'node_modules') continue;
var dir = parts.slice(0, tip + 1).concat('node_modules').join(joiner);
paths.push(dir);
}
return paths;
}
Module._resolveLookupPaths = function(request, parent) { Module._resolveLookupPaths = function(request, parent) {
if (NativeModule.exists(request)) { if (NativeModule.exists(request)) {
@ -171,14 +200,18 @@ Module._resolveLookupPaths = function(request, parent) {
var start = request.substring(0, 2); var start = request.substring(0, 2);
if (start !== './' && start !== '..') { if (start !== './' && start !== '..') {
return [request, Module._paths]; var paths = Module._paths;
if (parent) paths = paths.concat(parent.paths);
return [request, paths];
} }
// with --eval, parent.id is not set and parent.filename is null // with --eval, parent.id is not set and parent.filename is null
if (!parent || !parent.id || !parent.filename) { if (!parent || !parent.id || !parent.filename) {
// make require('./path/to/foo') work - normally the path is taken // make require('./path/to/foo') work - normally the path is taken
// from realpath(__filename) but with eval there is no filename // from realpath(__filename) but with eval there is no filename
return [request, ['.'].concat(Module._paths)]; var mainPaths = ['.'].concat(Module._paths);
mainPaths = mainPaths.concat(Module._nodeModulePaths('.'));
return [request, mainPaths];
} }
// Is the parent an index module? // Is the parent an index module?
@ -268,6 +301,7 @@ Module.prototype.load = function(filename) {
assert(!this.loaded); assert(!this.loaded);
this.filename = filename; this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js'; var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js'; if (!Module._extensions[extension]) extension = '.js';

3
test/fixtures/node_modules/asdf.js

@ -0,0 +1,3 @@
console.error(__filename);
console.error(module.paths.join('\n')+'\n');
throw new Error('Should not ever get here.');

2
test/fixtures/node_modules/bar.js

@ -0,0 +1,2 @@
console.error(__filename);
console.error(module.paths.join('\n')+'\n');

13
test/fixtures/node_modules/baz/index.js

@ -0,0 +1,13 @@
console.error(__filename);
console.error(module.paths.join('\n')+'\n');
// this should work, and get the one that doesn't throw
require('bar');
// since this is inside a node_modules folder,
// it should be impossible to ever see /node_modules in the
// lookup paths, since it's rooted on the uppermost node_modules
// directory.
require('assert').equal(-1, module.paths.indexOf('/node_modules'));
// this should work, and get the one in ./node_modules/asdf.js
require('asdf');

2
test/fixtures/node_modules/baz/node_modules/asdf.js

@ -0,0 +1,2 @@
console.error(__filename);
console.error(module.paths.join('\n')+'\n');

3
test/fixtures/node_modules/foo.js

@ -0,0 +1,3 @@
console.error(__filename);
console.error(module.paths.join('\n')+'\n');
require('baz');

3
test/fixtures/node_modules/node_modules/bar.js

@ -0,0 +1,3 @@
console.error(__filename);
console.error(module.paths.join('\n')+'\n');
throw new Error('Should not ever get here.');

5
test/simple/test-module-loading.js

@ -77,6 +77,11 @@ var root = require('../fixtures/cycles/root'),
assert.equal(root.foo, foo); assert.equal(root.foo, foo);
assert.equal(root.sayHello(), root.hello); assert.equal(root.sayHello(), root.hello);
common.debug('test node_modules folders');
// asserts are in the fixtures files themselves,
// since they depend on the folder structure.
require('../fixtures/node_modules/foo');
common.debug('test name clashes'); common.debug('test name clashes');
// this one exists and should import the local module // this one exists and should import the local module
var my_path = require('./path'); var my_path = require('./path');

Loading…
Cancel
Save