Skip to content

Commit 7b166f2

Browse files
committed
[New] Add isDirectory; use to speed up node_modules lookups
This is a backport of 4cf8928 and fa11d48 (browserify#190 and browserify#191) to the 1.x branch. This adds the `isDirectory` option which is needed to drive the directory lookups. This offers a small but useful performance improvement by avoiding unnecessary stat calls.
1 parent d098e92 commit 7b166f2

File tree

5 files changed

+79
-6
lines changed

5 files changed

+79
-6
lines changed

lib/async.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ var defaultIsFile = function isFile(file, cb) {
1515
});
1616
};
1717

18+
var defaultIsDir = function isDirectory(dir, cb) {
19+
fs.stat(dir, function (err, stat) {
20+
if (!err) {
21+
return cb(null, stat.isDirectory());
22+
}
23+
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
24+
return cb(err);
25+
});
26+
};
27+
1828
module.exports = function resolve(x, options, callback) {
1929
var cb = callback;
2030
var opts = options;
@@ -32,6 +42,7 @@ module.exports = function resolve(x, options, callback) {
3242
opts = normalizeOptions(x, opts);
3343

3444
var isFile = opts.isFile || defaultIsFile;
45+
var isDirectory = opts.isDirectory || defaultIsDir;
3546
var readFile = opts.readFile || fs.readFile;
3647

3748
var extensions = opts.extensions || ['.js'];
@@ -208,8 +219,14 @@ module.exports = function resolve(x, options, callback) {
208219
if (dirs.length === 0) return cb(null, undefined);
209220
var dir = dirs[0];
210221

211-
var file = path.join(dir, x);
212-
loadAsFile(file, opts.package, onfile);
222+
isDirectory(dir, isdir);
223+
224+
function isdir(err, isdir) {
225+
if (err) return cb(err);
226+
if (!isdir) return processDirs(cb, dirs.slice(1));
227+
var file = path.join(dir, x);
228+
loadAsFile(file, opts.package, onfile);
229+
}
213230

214231
function onfile(err, m, pkg) {
215232
if (err) return cb(err);

lib/sync.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ var defaultIsFile = function isFile(file) {
1515
return stat.isFile() || stat.isFIFO();
1616
};
1717

18+
var defaultIsDir = function isDirectory(dir) {
19+
try {
20+
var stat = fs.statSync(dir);
21+
} catch (e) {
22+
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false;
23+
throw e;
24+
}
25+
return stat.isDirectory();
26+
};
27+
1828
module.exports = function (x, options) {
1929
if (typeof x !== 'string') {
2030
throw new TypeError('Path must be a string.');
@@ -23,6 +33,7 @@ module.exports = function (x, options) {
2333

2434
var isFile = opts.isFile || defaultIsFile;
2535
var readFileSync = opts.readFileSync || fs.readFileSync;
36+
var isDirectory = opts.isDirectory || defaultIsDir;
2637

2738
var extensions = opts.extensions || ['.js'];
2839
var basedir = opts.basedir || path.dirname(caller());
@@ -145,10 +156,12 @@ module.exports = function (x, options) {
145156
var dirs = nodeModulesPaths(start, opts, x);
146157
for (var i = 0; i < dirs.length; i++) {
147158
var dir = dirs[i];
148-
var m = loadAsFileSync(path.join(dir, '/', x));
149-
if (m) return m;
150-
var n = loadAsDirectorySync(path.join(dir, '/', x));
151-
if (n) return n;
159+
if (isDirectory(dir)) {
160+
var m = loadAsFileSync(path.join(dir, '/', x));
161+
if (m) return m;
162+
var n = loadAsDirectorySync(path.join(dir, '/', x));
163+
if (n) return n;
164+
}
152165
}
153166
}
154167
};

readme.markdown

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ options are:
5959

6060
* opts.isFile - function to asynchronously test whether a file exists
6161

62+
* opts.isDirectory - function to asynchronously test whether a directory exists
63+
6264
* `opts.packageFilter(pkg, pkgfile)` - transform the parsed package.json contents before looking at the "main" field
6365
* pkg - package data
6466
* pkgfile - path to package.json
@@ -101,6 +103,15 @@ default `opts` values:
101103
return cb(err);
102104
});
103105
},
106+
isDirectory: function isDirectory(dir, cb) {
107+
fs.stat(dir, function (err, stat) {
108+
if (!err) {
109+
return cb(null, stat.isDirectory());
110+
}
111+
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
112+
return cb(err);
113+
});
114+
},
104115
moduleDirectory: 'node_modules',
105116
preserveSymlinks: true
106117
}
@@ -121,6 +132,8 @@ options are:
121132

122133
* opts.isFile - function to synchronously test whether a file exists
123134

135+
* opts.isDirectory - function to synchronously test whether a directory exists
136+
124137
* `opts.packageFilter(pkg, dir)` - transform the parsed package.json contents before looking at the "main" field
125138
* pkg - package data
126139
* dir - directory for package.json (Note: the second argument will change to "pkgfile" in v2)
@@ -157,6 +170,15 @@ default `opts` values:
157170
}
158171
return stat.isFile() || stat.isFIFO();
159172
},
173+
isDirectory: function isDirectory(dir) {
174+
try {
175+
var stat = fs.statSync(dir);
176+
} catch (e) {
177+
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false;
178+
throw e;
179+
}
180+
return stat.isDirectory();
181+
},
160182
moduleDirectory: 'node_modules',
161183
preserveSymlinks: true
162184
}

test/mock.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,19 @@ test('mock package', function (t) {
9494
main: './baz.js'
9595
});
9696

97+
var dirs = {};
98+
dirs[path.resolve('/foo')] = true;
99+
dirs[path.resolve('/foo/node_modules')] = true;
100+
97101
function opts(basedir) {
98102
return {
99103
basedir: path.resolve(basedir),
100104
isFile: function (file, cb) {
101105
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
102106
},
107+
isDirectory: function (dir, cb) {
108+
cb(null, !!dirs[path.resolve(dir)]);
109+
},
103110
readFile: function (file, cb) {
104111
cb(null, files[path.resolve(file)]);
105112
}
@@ -122,12 +129,19 @@ test('mock package from package', function (t) {
122129
main: './baz.js'
123130
});
124131

132+
var dirs = {};
133+
dirs[path.resolve('/foo')] = true;
134+
dirs[path.resolve('/foo/node_modules')] = true;
135+
125136
function opts(basedir) {
126137
return {
127138
basedir: path.resolve(basedir),
128139
isFile: function (file, cb) {
129140
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
130141
},
142+
isDirectory: function (dir, cb) {
143+
cb(null, !!dirs[path.resolve(dir)]);
144+
},
131145
'package': { main: 'bar' },
132146
readFile: function (file, cb) {
133147
cb(null, files[path.resolve(file)]);

test/mock_sync.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,19 @@ test('mock package', function (t) {
4848
main: './baz.js'
4949
});
5050

51+
var dirs = {};
52+
dirs[path.resolve('/foo')] = true;
53+
dirs[path.resolve('/foo/node_modules')] = true;
54+
5155
function opts(basedir) {
5256
return {
5357
basedir: path.resolve(basedir),
5458
isFile: function (file) {
5559
return Object.prototype.hasOwnProperty.call(files, file);
5660
},
61+
isDirectory: function (dir) {
62+
return !!dirs[path.resolve(dir)];
63+
},
5764
readFileSync: function (file) {
5865
return files[file];
5966
}

0 commit comments

Comments
 (0)