Skip to content

Commit 4651348

Browse files
isaacsry
authored andcommitted
node_modules module lookup, +docs and test.
1 parent dafd6d9 commit 4651348

File tree

9 files changed

+89
-3
lines changed

9 files changed

+89
-3
lines changed

doc/api/modules.markdown

+22-1
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,32 @@ then `require('./foo/bar')` would load the file at
8282
entry point to their module, while structuring their package how it
8383
suits them.
8484

85+
Any folders named `"node_modules"` that exist in the current module path
86+
will also be appended to the effective require path. This allows for
87+
bundling libraries and other dependencies in a 'node_modules' folder at
88+
the root of a program.
89+
90+
To avoid overly long lookup paths in the case of nested packages,
91+
the following 2 optimizations are made:
92+
93+
1. If the module calling `require()` is already within a `node_modules`
94+
folder, then the lookup will not go above the top-most `node_modules`
95+
directory.
96+
2. Node will not append `node_modules` to a path already ending in
97+
`node_modules`.
98+
99+
So, for example, if the file at
100+
`/usr/lib/node_modules/foo/node_modules/bar.js` were to do
101+
`require('baz')`, then the following places would be searched for a
102+
`baz` module, in this order:
103+
104+
* 1: `/usr/lib/node_modules/foo/node_modules`
105+
* 2: `/usr/lib/node_modules`
106+
85107
`require.paths` can be modified at runtime by simply unshifting new
86108
paths onto it, or at startup with the `NODE_PATH` environmental
87109
variable (which should be a list of paths, colon separated).
88110

89-
90111
The second time `require('foo')` is called, it is not loaded again from
91112
disk. It looks in the `require.cache` object to see if it has been loaded
92113
before.

lib/module.js

+36-2
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,35 @@ Module._findPath = function(request, paths) {
163163
return false;
164164
};
165165

166+
// 'from' is the __dirname of the module.
167+
Module._nodeModulePaths = function(from) {
168+
// guarantee that 'from' is absolute.
169+
from = path.resolve(from);
170+
171+
// note: this approach *only* works when the path is guaranteed
172+
// to be absolute. Doing a fully-edge-case-correct path.split
173+
// that works on both Windows and Posix is non-trivial.
174+
var splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//;
175+
// yes, '/' works on both, but let's be a little canonical.
176+
var joiner = process.platform === 'win32' ? '\\' : '/';
177+
var paths = [];
178+
var parts = from.split(splitRe);
179+
180+
var root = parts.indexOf('node_modules') - 1;
181+
if (root < 0) root = 0;
182+
183+
var tip = parts.length - 1;
184+
185+
for (var tip = parts.length - 1; tip >= root; tip --) {
186+
// don't search in .../node_modules/node_modules
187+
if (parts[tip] === 'node_modules') continue;
188+
var dir = parts.slice(0, tip + 1).concat('node_modules').join(joiner);
189+
paths.push(dir);
190+
}
191+
192+
return paths;
193+
}
194+
166195

167196
Module._resolveLookupPaths = function(request, parent) {
168197
if (NativeModule.exists(request)) {
@@ -171,14 +200,18 @@ Module._resolveLookupPaths = function(request, parent) {
171200

172201
var start = request.substring(0, 2);
173202
if (start !== './' && start !== '..') {
174-
return [request, Module._paths];
203+
var paths = Module._paths;
204+
if (parent) paths = paths.concat(parent.paths);
205+
return [request, paths];
175206
}
176207

177208
// with --eval, parent.id is not set and parent.filename is null
178209
if (!parent || !parent.id || !parent.filename) {
179210
// make require('./path/to/foo') work - normally the path is taken
180211
// from realpath(__filename) but with eval there is no filename
181-
return [request, ['.'].concat(Module._paths)];
212+
var mainPaths = ['.'].concat(Module._paths);
213+
mainPaths = mainPaths.concat(Module._nodeModulePaths('.'));
214+
return [request, mainPaths];
182215
}
183216

184217
// Is the parent an index module?
@@ -268,6 +301,7 @@ Module.prototype.load = function(filename) {
268301

269302
assert(!this.loaded);
270303
this.filename = filename;
304+
this.paths = Module._nodeModulePaths(path.dirname(filename));
271305

272306
var extension = path.extname(filename) || '.js';
273307
if (!Module._extensions[extension]) extension = '.js';

test/fixtures/node_modules/asdf.js

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/bar.js

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/baz/index.js

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/foo.js

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/node_modules/node_modules/bar.js

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/simple/test-module-loading.js

+5
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ var root = require('../fixtures/cycles/root'),
7777
assert.equal(root.foo, foo);
7878
assert.equal(root.sayHello(), root.hello);
7979

80+
common.debug('test node_modules folders');
81+
// asserts are in the fixtures files themselves,
82+
// since they depend on the folder structure.
83+
require('../fixtures/node_modules/foo');
84+
8085
common.debug('test name clashes');
8186
// this one exists and should import the local module
8287
var my_path = require('./path');

0 commit comments

Comments
 (0)