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
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
paths onto it, or at startup with the `NODE_PATH` environmental
variable (which should be a list of paths, colon separated).
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
before.

38
lib/module.js

@ -163,6 +163,35 @@ Module._findPath = function(request, paths) {
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) {
if (NativeModule.exists(request)) {
@ -171,14 +200,18 @@ Module._resolveLookupPaths = function(request, parent) {
var start = request.substring(0, 2);
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
if (!parent || !parent.id || !parent.filename) {
// make require('./path/to/foo') work - normally the path is taken
// 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?
@ -268,6 +301,7 @@ Module.prototype.load = function(filename) {
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.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.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');
// this one exists and should import the local module
var my_path = require('./path');

Loading…
Cancel
Save