Skip to content

Commit b743e43

Browse files
committed
*way* faster implementation of node crawling.
1 parent 275af0c commit b743e43

2 files changed

Lines changed: 100 additions & 61 deletions

File tree

packages/jest-haste-map/src/crawlers/node.js

Lines changed: 100 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,75 +9,120 @@
99

1010
'use strict';
1111

12-
const denodeify = require('denodeify');
13-
const fs = require('graceful-fs');
14-
const path = require('../fastpath');
12+
const fs = require('fs');
13+
const os = require('os');
14+
const path = require('path');
15+
const spawn = require('child_process').spawn;
1516

16-
const statFile = denodeify(fs.stat);
17-
const readDir = denodeify(fs.readdir);
17+
function find(roots, extensions, ignorePattern, callback) {
18+
const result = [];
19+
let activeCalls = 0;
1820

19-
function nodeCrawl(roots, extensions, ignorePattern, data) {
20-
const extensionsPattern = new RegExp('\.(' + extensions.join('|') + ')$');
21-
const files = Object.create(null);
22-
const queue = roots.slice();
21+
function search(curDir) {
22+
activeCalls++;
23+
fs.readdir(curDir, (err, names) => {
24+
activeCalls--;
2325

24-
function search() {
25-
const root = queue.shift();
26-
if (!root) {
27-
return Promise.resolve();
28-
}
26+
for (let i = 0; i < names.length; i++) {
27+
names[i] = path.join(curDir, names[i]);
28+
}
2929

30-
return readDir(root)
31-
.then(files => files.map(f => path.join(root, f)))
32-
.then(files => Promise.all(
33-
files.map(
34-
f => statFile(f).catch(handleBrokenLink).then(stat => [f, stat])
35-
)
36-
))
37-
.then(list => {
38-
list.forEach((fileData, i) => {
39-
const name = fileData[0];
40-
const stat = fileData[1];
41-
// Ignore broken links.
42-
if (!stat || ignorePattern.test(name)) {
43-
return;
44-
}
30+
names.forEach(file => {
31+
if (ignorePattern.test(file)) {
32+
return;
33+
}
34+
activeCalls++;
4535

46-
if (stat.isDirectory()) {
47-
queue.push(name);
48-
return;
49-
}
36+
fs.lstat(file, (err, stat) => {
37+
activeCalls--;
5038

51-
if (extensionsPattern.test(name)) {
52-
const mtime = stat.mtime.getTime();
53-
const existingFile = data.files[name];
54-
if (existingFile && existingFile.mtime === mtime) {
55-
//console.log('exists', name);
56-
files[name] = existingFile;
39+
if (!err && stat && !stat.isSymbolicLink()) {
40+
if (stat.isDirectory()) {
41+
search(file);
5742
} else {
58-
//console.log('add', name);
59-
files[name] = {
60-
id: null,
61-
mtime,
62-
visited: false,
63-
};
43+
const ext = path.extname(file).substr(1);
44+
if (extensions.indexOf(ext) !== -1) {
45+
result.push([file, stat.mtime.getTime()]);
46+
}
6447
}
6548
}
49+
if (activeCalls === 0) {
50+
callback(result);
51+
}
6652
});
67-
68-
return search();
6953
});
54+
55+
if (activeCalls === 0) {
56+
callback(result);
57+
}
58+
});
7059
}
7160

72-
return search().then(() => {
73-
data.files = files;
74-
return data;
75-
});
61+
roots.forEach(search);
7662
}
7763

78-
function handleBrokenLink(e) {
79-
console.warn('WARNING: error stating, possibly broken symlink', e.message);
80-
return Promise.resolve();
64+
function findNative(roots, extensions, ignorePattern, callback) {
65+
const args = [].concat(roots);
66+
args.push('-type', 'f');
67+
extensions.forEach((ext, index) => {
68+
if (index) {
69+
args.push('-o');
70+
}
71+
args.push('-iname');
72+
args.push('*' + ext);
73+
});
74+
75+
const child = spawn('find', args);
76+
let stdout = '';
77+
child.stdout.setEncoding('utf-8');
78+
child.stdout.on('data', data => stdout += data);
79+
80+
child.stdout.on('close', code => {
81+
const lines = stdout.trim()
82+
.split('\n')
83+
.filter(x => !ignorePattern.test(x));
84+
const result = [];
85+
let count = lines.length;
86+
lines.forEach(path => {
87+
fs.stat(path, (err, stat) => {
88+
if (stat && !stat.isDirectory()) {
89+
result.push([path, stat.mtime.getTime()]);
90+
}
91+
if (--count === 0) {
92+
callback(result);
93+
}
94+
});
95+
});
96+
});
8197
}
8298

83-
module.exports = nodeCrawl;
99+
module.exports = function nodeCrawl(roots, extensions, ignorePattern, data) {
100+
return new Promise(resolve => {
101+
const callback = list => {
102+
const files = Object.create(null);
103+
list.forEach(fileData => {
104+
const name = fileData[0];
105+
const mtime = fileData[1];
106+
const existingFile = data.files[name];
107+
if (existingFile && existingFile.mtime === mtime) {
108+
//console.log('exists', name);
109+
files[name] = existingFile;
110+
} else {
111+
//console.log('add', name);
112+
files[name] = {
113+
mtime,
114+
visited: false,
115+
};
116+
}
117+
});
118+
data.files = files;
119+
resolve(data);
120+
};
121+
122+
if (os.platform() == 'win32') {
123+
find(roots, extensions, ignorePattern, callback);
124+
} else {
125+
findNative(roots, extensions, ignorePattern, callback);
126+
}
127+
});
128+
};

packages/jest-haste-map/src/worker.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ const path = require('./fastpath');
1414

1515
const PACKAGE_JSON = path.sep + 'package.json';
1616

17-
// Make sure uncaught errors are logged before we exit.
18-
process.on('uncaughtException', err => {
19-
console.error(err.stack);
20-
process.exit(1);
21-
});
22-
2317
const formatError = error => {
2418
if (typeof error === 'string') {
2519
return {

0 commit comments

Comments
 (0)