Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80529 views
1
var fs = require('fs');
2
var path = require('path');
3
4
var browserResolve = require('browser-resolve');
5
var nodeResolve = require('resolve');
6
var detective = require('detective');
7
var through = require('through2');
8
var concat = require('concat-stream');
9
var parents = require('parents');
10
var combine = require('stream-combiner2');
11
var duplexer = require('duplexer2');
12
var xtend = require('xtend');
13
var defined = require('defined');
14
15
var inherits = require('inherits');
16
var Transform = require('readable-stream').Transform;
17
18
module.exports = Deps;
19
inherits(Deps, Transform);
20
21
function Deps (opts) {
22
var self = this;
23
if (!(this instanceof Deps)) return new Deps(opts);
24
Transform.call(this, { objectMode: true });
25
26
if (!opts) opts = {};
27
28
this.basedir = opts.basedir || process.cwd();
29
this.cache = opts.cache;
30
this.pkgCache = opts.packageCache || {};
31
this.pkgFileCache = {};
32
this.pkgFileCachePending = {};
33
this._emittedPkg = {};
34
this.visited = {};
35
this.walking = {};
36
this.entries = [];
37
this._input = [];
38
39
this.paths = opts.paths || process.env.NODE_PATH || '';
40
if (typeof this.paths === 'string') {
41
var delimiter = path.delimiter || (process.platform === 'win32' ? ';' : ':');
42
this.paths = this.paths.split(delimiter);
43
}
44
this.paths = this.paths
45
.filter(Boolean)
46
.map(function (p) {
47
return path.resolve(self.basedir, p);
48
});
49
50
this.transforms = [].concat(opts.transform).filter(Boolean);
51
this.globalTransforms = [].concat(opts.globalTransform).filter(Boolean);
52
this.resolver = opts.resolve || browserResolve;
53
this.options = xtend(opts);
54
if (!this.options.modules) this.options.modules = {};
55
56
// If the caller passes options.expose, store resolved pathnames for exposed
57
// modules in it. If not, set it anyway so it's defined later.
58
if (!this.options.expose) this.options.expose = {};
59
this.pending = 0;
60
this.inputPending = 0;
61
62
var topfile = path.join(this.basedir, '__fake.js');
63
this.top = {
64
id: topfile,
65
filename: topfile,
66
paths: this.paths,
67
basedir: this.basedir
68
};
69
}
70
71
Deps.prototype._transform = function (row, enc, next) {
72
var self = this;
73
if (typeof row === 'string') {
74
row = { file: row };
75
}
76
if (row.transform && row.global) {
77
this.globalTransforms.push([ row.transform, row.options ]);
78
return next();
79
}
80
else if (row.transform) {
81
this.transforms.push([ row.transform, row.options ]);
82
return next();
83
}
84
85
self.pending ++;
86
var basedir = defined(row.basedir, self.basedir);
87
88
if (row.entry !== false) {
89
self.entries.push(path.resolve(basedir, row.file || row.id));
90
}
91
92
self.lookupPackage(row.file, function (err, pkg) {
93
if (err && self.options.ignoreMissing) {
94
self.emit('missing', row.file, self.top);
95
self.pending --;
96
return next();
97
}
98
if (err) return self.emit('error', err)
99
self.pending --;
100
self._input.push({ row: row, pkg: pkg });
101
next();
102
});
103
};
104
105
Deps.prototype._flush = function () {
106
var self = this;
107
var files = {};
108
self._input.forEach(function (r) {
109
var w = r.row, f = files[w.file || w.id];
110
if (f) {
111
f.row.entry = f.row.entry || w.entry;
112
var ex = f.row.expose || w.expose;
113
f.row.expose = ex;
114
if (ex && f.row.file === f.row.id && w.file !== w.id) {
115
f.row.id = w.id;
116
}
117
}
118
else files[w.file || w.id] = r;
119
});
120
121
Object.keys(files).forEach(function (key) {
122
var r = files[key];
123
var pkg = r.pkg || {};
124
var dir = path.dirname(r.row.file);
125
if (!pkg.__dirname) pkg.__dirname = dir;
126
self.walk(r.row, xtend(self.top, {
127
filename: path.join(dir, '_fake.js')
128
}));
129
});
130
if (this.pending === 0) this.push(null);
131
this._ended = true;
132
};
133
134
Deps.prototype.resolve = function (id, parent, cb) {
135
var self = this;
136
var opts = self.options;
137
138
if (xhas(self.cache, parent.id, 'deps', id)
139
&& self.cache[parent.id].deps) {
140
var file = self.cache[parent.id].deps[id];
141
var pkg = self.pkgCache[file];
142
if (pkg) return cb(null, file, pkg);
143
return self.lookupPackage(file, function (err, pkg) {
144
cb(null, file, pkg);
145
});
146
}
147
148
parent.packageFilter = function (p, x) {
149
var pkgdir = path.dirname(x);
150
if (opts.packageFilter) p = opts.packageFilter(p, x);
151
p.__dirname = pkgdir;
152
153
return p;
154
};
155
156
if (opts.extensions) parent.extensions = opts.extensions;
157
if (opts.modules) parent.modules = opts.modules;
158
159
self.resolver(id, parent, function onresolve (err, file, pkg) {
160
if (err) return cb(err);
161
if (!file) return cb(new Error(
162
'module not found: "' + id + '" from file '
163
+ parent.filename
164
));
165
166
if (!pkg || !pkg.__dirname) {
167
self.lookupPackage(file, function (err, p) {
168
if (err) return cb(err);
169
if (!p) p = {};
170
if (!p.__dirname) p.__dirname = path.dirname(file);
171
self.pkgCache[file] = p;
172
onresolve(err, file, opts.packageFilter
173
? opts.packageFilter(p, p.__dirname) : p
174
);
175
});
176
}
177
else cb(err, file, pkg);
178
});
179
};
180
181
Deps.prototype.readFile = function (file, id, pkg) {
182
var self = this;
183
var tr = through();
184
if (this.cache && this.cache[file]) {
185
tr.push(this.cache[file].source);
186
tr.push(null);
187
return tr;
188
}
189
var rs = fs.createReadStream(file);
190
rs.on('error', function (err) { self.emit('error', err) });
191
this.emit('file', file, id);
192
return rs;
193
};
194
195
Deps.prototype.getTransforms = function (file, pkg, opts) {
196
if (!opts) opts = {};
197
var self = this;
198
199
var isTopLevel;
200
if (opts.builtin) isTopLevel = false;
201
else isTopLevel = this.entries.some(function (main) {
202
var m = path.relative(path.dirname(main), file);
203
return m.split(/[\\\/]/).indexOf('node_modules') < 0;
204
});
205
if (!isTopLevel && !opts.builtin) {
206
var m = path.relative(this.basedir, file);
207
isTopLevel = m.split(/[\\\/]/).indexOf('node_modules') < 0;
208
}
209
210
var transforms = [].concat(isTopLevel ? this.transforms : [])
211
.concat(getTransforms(pkg, {
212
globalTransform: this.globalTransforms,
213
transformKey: this.options.transformKey
214
}))
215
;
216
if (transforms.length === 0) return through();
217
218
var pending = transforms.length;
219
var streams = [];
220
var input = through();
221
var output = through();
222
var dup = duplexer(input, output);
223
224
for (var i = 0; i < transforms.length; i++) (function (i) {
225
makeTransform(transforms[i], function (err, trs) {
226
if (err) return self.emit('error', err)
227
streams[i] = trs;
228
if (-- pending === 0) done();
229
});
230
})(i);
231
return dup;
232
233
function done () {
234
var middle = combine.apply(null, streams);
235
middle.on('error', function (err) {
236
err.message += ' while parsing file: ' + file;
237
if (!err.filename) err.filename = file;
238
self.emit('error', err);
239
});
240
input.pipe(middle).pipe(output);
241
}
242
243
function makeTransform (tr, cb) {
244
var trOpts = {};
245
if (Array.isArray(tr)) {
246
trOpts = tr[1];
247
tr = tr[0];
248
}
249
if (typeof tr === 'function') {
250
var t = tr(file, trOpts);
251
self.emit('transform', t, file);
252
nextTick(cb, null, wrapTransform(t));
253
}
254
else {
255
loadTransform(tr, trOpts, function (err, trs) {
256
if (err) return cb(err);
257
cb(null, wrapTransform(trs));
258
});
259
}
260
}
261
262
function loadTransform (id, trOpts, cb) {
263
var params = { basedir: path.dirname(file) };
264
nodeResolve(id, params, function nr (err, res, again) {
265
if (err && again) return cb && cb(err);
266
267
if (err) {
268
params.basedir = pkg.__dirname;
269
return nodeResolve(id, params, function (e, r) {
270
nr(e, r, true)
271
});
272
}
273
274
if (!res) return cb(new Error(
275
'cannot find transform module ' + tr
276
+ ' while transforming ' + file
277
));
278
279
var r = require(res);
280
if (typeof r !== 'function') {
281
return cb(new Error(
282
'Unexpected ' + typeof r + ' exported by the '
283
+ JSON.stringify(res) + ' package. '
284
+ 'Expected a transform function.'
285
));
286
}
287
288
var trs = r(file, trOpts);
289
self.emit('transform', trs, file);
290
cb(null, trs);
291
});
292
}
293
};
294
295
Deps.prototype.walk = function (id, parent, cb) {
296
var self = this;
297
var opts = self.options;
298
this.pending ++;
299
300
var rec = {};
301
var input;
302
if (typeof id === 'object') {
303
rec = xtend(id);
304
if (rec.entry === false) delete rec.entry;
305
id = rec.file || rec.id;
306
input = true;
307
this.inputPending ++;
308
}
309
310
self.resolve(id, parent, function (err, file, pkg) {
311
if (rec.expose) {
312
// Set options.expose to make the resolved pathname available to the
313
// caller. They may or may not have requested it, but it's harmless
314
// to set this if they didn't.
315
self.options.expose[rec.expose] =
316
self.options.modules[rec.expose] = file;
317
}
318
if (pkg && !self._emittedPkg[pkg.__dirname]) {
319
self._emittedPkg[pkg.__dirname] = true;
320
self.emit('package', pkg);
321
}
322
323
if (opts.postFilter && !opts.postFilter(id, file, pkg)) {
324
if (--self.pending === 0) self.push(null);
325
if (input) --self.inputPending;
326
return cb(null, undefined);
327
}
328
if (err && rec.source) {
329
file = rec.file;
330
331
var ts = self.getTransforms(file, pkg);
332
ts.pipe(concat(function (body) {
333
rec.source = body.toString('utf8');
334
fromSource(file, rec.source, pkg);
335
}));
336
return ts.end(rec.source);
337
}
338
if (err && self.options.ignoreMissing) {
339
if (--self.pending === 0) self.push(null);
340
if (input) --self.inputPending;
341
self.emit('missing', id, parent);
342
return cb && cb(null, undefined);
343
}
344
if (err) return self.emit('error', err);
345
if (self.visited[file]) {
346
if (-- self.pending === 0) self.push(null);
347
if (input) --self.inputPending;
348
return cb && cb(null, file);
349
}
350
self.visited[file] = true;
351
352
if (rec.source) {
353
var ts = self.getTransforms(file, pkg);
354
ts.pipe(concat(function (body) {
355
rec.source = body.toString('utf8');
356
fromSource(file, rec.source, pkg);
357
}));
358
return ts.end(rec.source);
359
}
360
361
var c = self.cache && self.cache[file];
362
if (c) return fromDeps(file, c.source, c.package, Object.keys(c.deps));
363
364
self.readFile(file, id, pkg)
365
.pipe(self.getTransforms(file, pkg, {
366
builtin: has(parent.modules, id)
367
}))
368
.pipe(concat(function (body) {
369
fromSource(file, body.toString('utf8'), pkg);
370
}))
371
;
372
});
373
374
function fromSource (file, src, pkg) {
375
var deps = rec.noparse ? [] : self.parseDeps(file, src);
376
if (deps) fromDeps(file, src, pkg, deps);
377
}
378
379
function fromDeps (file, src, pkg, deps) {
380
var p = deps.length;
381
var resolved = {};
382
383
if (input) --self.inputPending;
384
385
(function resolve () {
386
if (self.inputPending > 0) return setTimeout(resolve);
387
deps.forEach(function (id) {
388
if (opts.filter && !opts.filter(id)) {
389
resolved[id] = false;
390
if (--p === 0) done();
391
return;
392
}
393
var current = {
394
id: file,
395
filename: file,
396
paths: self.paths,
397
package: pkg
398
};
399
self.walk(id, current, function (err, r) {
400
resolved[id] = r;
401
if (--p === 0) done();
402
});
403
});
404
if (deps.length === 0) done();
405
})();
406
407
function done () {
408
if (!rec.id) rec.id = file;
409
if (!rec.source) rec.source = src;
410
if (!rec.deps) rec.deps = resolved;
411
if (!rec.file) rec.file = file;
412
413
if (self.entries.indexOf(file) >= 0) {
414
rec.entry = true;
415
}
416
self.push(rec);
417
418
if (cb) cb(null, file);
419
if (-- self.pending === 0) self.push(null);
420
}
421
}
422
};
423
424
Deps.prototype.parseDeps = function (file, src, cb) {
425
if (this.options.noParse === true) return [];
426
if (/\.json$/.test(file)) return [];
427
428
if (Array.isArray(this.options.noParse)
429
&& this.options.noParse.indexOf(file) >= 0) {
430
return [];
431
}
432
433
try { var deps = detective(src) }
434
catch (ex) {
435
var message = ex && ex.message ? ex.message : ex;
436
this.emit('error', new Error(
437
'Parsing file ' + file + ': ' + message
438
));
439
return;
440
}
441
return deps;
442
};
443
444
Deps.prototype.lookupPackage = function (file, cb) {
445
var self = this;
446
447
var cached = this.pkgCache[file];
448
if (cached) return nextTick(cb, null, cached);
449
if (cached === false) return nextTick(cb, null, undefined);
450
451
var dirs = parents(path.dirname(file));
452
453
(function next () {
454
if (dirs.length === 0) {
455
self.pkgCache[file] = false;
456
return cb(null, undefined);
457
}
458
var dir = dirs.shift();
459
if (dir.split(/[\\\/]/).slice(-1)[0] === 'node_modules') {
460
return cb(null, undefined);
461
}
462
463
var pkgfile = path.join(dir, 'package.json');
464
465
var cached = self.pkgCache[pkgfile];
466
if (cached) return nextTick(cb, null, cached);
467
else if (cached === false) return next();
468
469
var pcached = self.pkgFileCachePending[pkgfile];
470
if (pcached) return pcached.push(onpkg);
471
pcached = self.pkgFileCachePending[pkgfile] = [];
472
473
fs.readFile(pkgfile, function (err, src) {
474
if (err) return onpkg();
475
try { var pkg = JSON.parse(src) }
476
catch (err) {
477
return onpkg(new Error([
478
err + ' while parsing json file ' + pkgfile
479
].join('')))
480
}
481
pkg.__dirname = dir;
482
483
self.pkgCache[pkgfile] = pkg;
484
self.pkgCache[file] = pkg;
485
onpkg(null, pkg);
486
});
487
488
function onpkg (err, pkg) {
489
if (self.pkgFileCachePending[pkgfile]) {
490
var fns = self.pkgFileCachePending[pkgfile];
491
delete self.pkgFileCachePending[pkgfile];
492
fns.forEach(function (f) { f(err, pkg) });
493
}
494
if (err) cb(err)
495
else if (pkg) cb(null, pkg)
496
else {
497
self.pkgCache[pkgfile] = false;
498
next();
499
}
500
}
501
})();
502
};
503
504
function getTransforms (pkg, opts) {
505
var trx = [];
506
if (opts.transformKey) {
507
var n = pkg;
508
var keys = opts.transformKey;
509
for (var i = 0; i < keys.length; i++) {
510
if (n && typeof n === 'object') n = n[keys[i]];
511
else break;
512
}
513
if (i === keys.length) {
514
trx = [].concat(n).filter(Boolean);
515
}
516
}
517
return trx.concat(opts.globalTransform || []);
518
}
519
520
function nextTick (cb) {
521
var args = [].slice.call(arguments, 1);
522
process.nextTick(function () { cb.apply(null, args) });
523
}
524
525
function xhas (obj) {
526
if (!obj) return false;
527
for (var i = 1; i < arguments.length; i++) {
528
var key = arguments[i];
529
if (!has(obj, key)) return false;
530
obj = obj[key];
531
}
532
return true;
533
}
534
535
function has (obj, key) {
536
return obj && Object.prototype.hasOwnProperty.call(obj, key);
537
}
538
539
function wrapTransform (tr) {
540
if (typeof tr.read === 'function') return tr;
541
var input = through(), output = through();
542
input.pipe(tr).pipe(output);
543
var wrapper = duplexer(input, output);
544
tr.on('error', function (err) { wrapper.emit('error', err) });
545
return wrapper;
546
}
547
548