index.js 20 KB

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