Skip to content

Commit

Permalink
node_modules module lookup, +docs and test.
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs authored and ry committed Feb 9, 2011
1 parent dafd6d9 commit 4651348
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 3 deletions.
23 changes: 22 additions & 1 deletion doc/api/modules.markdown
Expand Up @@ -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.
Expand Down
38 changes: 36 additions & 2 deletions lib/module.js
Expand Up @@ -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)) {
Expand All @@ -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?
Expand Down Expand Up @@ -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';
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/node_modules/asdf.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions test/fixtures/node_modules/bar.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions test/fixtures/node_modules/baz/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions test/fixtures/node_modules/baz/node_modules/asdf.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/fixtures/node_modules/foo.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/fixtures/node_modules/node_modules/bar.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions test/simple/test-module-loading.js
Expand Up @@ -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');
Expand Down

0 comments on commit 4651348

Please sign in to comment.