Skip to content

Commit 38559e0

Browse files
authored
Merge pull request #192 from keithamus/new-add-isdirectory-use-to-speed-up-node-modules-lookups
[New] Add `isDirectory`; use to speed up `node_modules` lookups
2 parents c36ba3d + d2816d8 commit 38559e0

File tree

5 files changed

+101
-10
lines changed

5 files changed

+101
-10
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ test('mock', function (t) {
88
var files = {};
99
files[path.resolve('/foo/bar/baz.js')] = 'beep';
1010

11+
var dirs = {};
12+
dirs[path.resolve('/foo/bar')] = true;
13+
1114
function opts(basedir) {
1215
return {
1316
basedir: path.resolve(basedir),
1417
isFile: function (file, cb) {
1518
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
1619
},
20+
isDirectory: function (dir, cb) {
21+
cb(null, !!dirs[path.resolve(dir)]);
22+
},
1723
readFile: function (file, cb) {
1824
cb(null, files[path.resolve(file)]);
1925
}
@@ -49,12 +55,18 @@ test('mock from package', function (t) {
4955
var files = {};
5056
files[path.resolve('/foo/bar/baz.js')] = 'beep';
5157

58+
var dirs = {};
59+
dirs[path.resolve('/foo/bar')] = true;
60+
5261
function opts(basedir) {
5362
return {
5463
basedir: path.resolve(basedir),
5564
isFile: function (file, cb) {
5665
cb(null, Object.prototype.hasOwnProperty.call(files, file));
5766
},
67+
isDirectory: function (dir, cb) {
68+
cb(null, !!dirs[path.resolve(dir)]);
69+
},
5870
'package': { main: 'bar' },
5971
readFile: function (file, cb) {
6072
cb(null, files[file]);
@@ -94,12 +106,19 @@ test('mock package', function (t) {
94106
main: './baz.js'
95107
});
96108

109+
var dirs = {};
110+
dirs[path.resolve('/foo')] = true;
111+
dirs[path.resolve('/foo/node_modules')] = true;
112+
97113
function opts(basedir) {
98114
return {
99115
basedir: path.resolve(basedir),
100116
isFile: function (file, cb) {
101117
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
102118
},
119+
isDirectory: function (dir, cb) {
120+
cb(null, !!dirs[path.resolve(dir)]);
121+
},
103122
readFile: function (file, cb) {
104123
cb(null, files[path.resolve(file)]);
105124
}
@@ -122,12 +141,19 @@ test('mock package from package', function (t) {
122141
main: './baz.js'
123142
});
124143

144+
var dirs = {};
145+
dirs[path.resolve('/foo')] = true;
146+
dirs[path.resolve('/foo/node_modules')] = true;
147+
125148
function opts(basedir) {
126149
return {
127150
basedir: path.resolve(basedir),
128151
isFile: function (file, cb) {
129152
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
130153
},
154+
isDirectory: function (dir, cb) {
155+
cb(null, !!dirs[path.resolve(dir)]);
156+
},
131157
'package': { main: 'bar' },
132158
readFile: function (file, cb) {
133159
cb(null, files[path.resolve(file)]);

test/mock_sync.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@ test('mock', function (t) {
88
var files = {};
99
files[path.resolve('/foo/bar/baz.js')] = 'beep';
1010

11+
var dirs = {};
12+
dirs[path.resolve('/foo/bar')] = true;
13+
1114
function opts(basedir) {
1215
return {
1316
basedir: path.resolve(basedir),
1417
isFile: function (file) {
15-
return Object.prototype.hasOwnProperty.call(files, file);
18+
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
19+
},
20+
isDirectory: function (dir) {
21+
return !!dirs[path.resolve(dir)];
1622
},
1723
readFileSync: function (file) {
18-
return files[file];
24+
return files[path.resolve(file)];
1925
}
2026
};
2127
}
@@ -48,14 +54,21 @@ test('mock package', function (t) {
4854
main: './baz.js'
4955
});
5056

57+
var dirs = {};
58+
dirs[path.resolve('/foo')] = true;
59+
dirs[path.resolve('/foo/node_modules')] = true;
60+
5161
function opts(basedir) {
5262
return {
5363
basedir: path.resolve(basedir),
5464
isFile: function (file) {
55-
return Object.prototype.hasOwnProperty.call(files, file);
65+
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
66+
},
67+
isDirectory: function (dir) {
68+
return !!dirs[path.resolve(dir)];
5669
},
5770
readFileSync: function (file) {
58-
return files[file];
71+
return files[path.resolve(file)];
5972
}
6073
};
6174
}

0 commit comments

Comments
 (0)