123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 |
- var fs = require('fs');
- var path = require('path');
- var relativePath = require('cached-path-relative');
- var browserResolve = require('browser-resolve');
- var nodeResolve = require('resolve');
- var detective = require('detective');
- var through = require('through2');
- var concat = require('concat-stream');
- var parents = require('parents');
- var combine = require('stream-combiner2');
- var duplexer = require('duplexer2');
- var xtend = require('xtend');
- var defined = require('defined');
- var inherits = require('inherits');
- var Transform = require('readable-stream').Transform;
- module.exports = Deps;
- inherits(Deps, Transform);
- function Deps (opts) {
- var self = this;
- if (!(this instanceof Deps)) return new Deps(opts);
- Transform.call(this, { objectMode: true });
-
- if (!opts) opts = {};
-
- this.basedir = opts.basedir || process.cwd();
- this.persistentCache = opts.persistentCache || function (file, id, pkg, fallback, cb) {
- process.nextTick(function () {
- fallback(null, cb);
- });
- };
- this.cache = opts.cache;
- this.fileCache = opts.fileCache;
- this.pkgCache = opts.packageCache || {};
- this.pkgFileCache = {};
- this.pkgFileCachePending = {};
- this._emittedPkg = {};
- this._transformDeps = {};
- this.visited = {};
- this.walking = {};
- this.entries = [];
- this._input = [];
-
- this.paths = opts.paths || process.env.NODE_PATH || '';
- if (typeof this.paths === 'string') {
- var delimiter = path.delimiter || (process.platform === 'win32' ? ';' : ':');
- this.paths = this.paths.split(delimiter);
- }
- this.paths = this.paths
- .filter(Boolean)
- .map(function (p) {
- return path.resolve(self.basedir, p);
- });
-
- this.transforms = [].concat(opts.transform).filter(Boolean);
- this.globalTransforms = [].concat(opts.globalTransform).filter(Boolean);
- this.resolver = opts.resolve || browserResolve;
- this.detective = opts.detect || detective;
- this.options = xtend(opts);
- if (!this.options.modules) this.options.modules = {};
- // If the caller passes options.expose, store resolved pathnames for exposed
- // modules in it. If not, set it anyway so it's defined later.
- if (!this.options.expose) this.options.expose = {};
- this.pending = 0;
- this.inputPending = 0;
-
- var topfile = path.join(this.basedir, '__fake.js');
- this.top = {
- id: topfile,
- filename: topfile,
- paths: this.paths,
- basedir: this.basedir
- };
- }
- Deps.prototype._isTopLevel = function (file) {
- var isTopLevel = this.entries.some(function (main) {
- var m = relativePath(path.dirname(main), file);
- return m.split(/[\\\/]/).indexOf('node_modules') < 0;
- });
- if (!isTopLevel) {
- var m = relativePath(this.basedir, file);
- isTopLevel = m.split(/[\\\/]/).indexOf('node_modules') < 0;
- }
- return isTopLevel;
- };
- Deps.prototype._transform = function (row, enc, next) {
- var self = this;
- if (typeof row === 'string') {
- row = { file: row };
- }
- if (row.transform && row.global) {
- this.globalTransforms.push([ row.transform, row.options ]);
- return next();
- }
- else if (row.transform) {
- this.transforms.push([ row.transform, row.options ]);
- return next();
- }
-
- self.pending ++;
- var basedir = defined(row.basedir, self.basedir);
-
- if (row.entry !== false) {
- self.entries.push(path.resolve(basedir, row.file || row.id));
- }
-
- self.lookupPackage(row.file, function (err, pkg) {
- if (err && self.options.ignoreMissing) {
- self.emit('missing', row.file, self.top);
- self.pending --;
- return next();
- }
- if (err) return self.emit('error', err)
- self.pending --;
- self._input.push({ row: row, pkg: pkg });
- next();
- });
- };
- Deps.prototype._flush = function () {
- var self = this;
- var files = {};
- self._input.forEach(function (r) {
- var w = r.row, f = files[w.file || w.id];
- if (f) {
- f.row.entry = f.row.entry || w.entry;
- var ex = f.row.expose || w.expose;
- f.row.expose = ex;
- if (ex && f.row.file === f.row.id && w.file !== w.id) {
- f.row.id = w.id;
- }
- }
- else files[w.file || w.id] = r;
- });
-
- Object.keys(files).forEach(function (key) {
- var r = files[key];
- var pkg = r.pkg || {};
- var dir = r.row.file ? path.dirname(r.row.file) : self.basedir;
- if (!pkg.__dirname) pkg.__dirname = dir;
- self.walk(r.row, xtend(self.top, {
- filename: path.join(dir, '_fake.js')
- }));
- });
- if (this.pending === 0) this.push(null);
- this._ended = true;
- };
- Deps.prototype.resolve = function (id, parent, cb) {
- var self = this;
- var opts = self.options;
-
- if (xhas(self.cache, parent.id, 'deps', id)
- && self.cache[parent.id].deps[id]) {
- var file = self.cache[parent.id].deps[id];
- var pkg = self.pkgCache[file];
- if (pkg) return cb(null, file, pkg);
- return self.lookupPackage(file, function (err, pkg) {
- cb(null, file, pkg);
- });
- }
-
- parent.packageFilter = function (p, x) {
- var pkgdir = path.dirname(x);
- if (opts.packageFilter) p = opts.packageFilter(p, x);
- p.__dirname = pkgdir;
- return p;
- };
- // have `resolve` do all the package.json lookups,
- // see discussion in https://github.com/browserify/browser-resolve/issues/93#issuecomment-667837808
- parent.package = undefined;
-
- if (opts.extensions) parent.extensions = opts.extensions;
- if (opts.modules) parent.modules = opts.modules;
-
- self.resolver(id, parent, function onresolve (err, file, pkg, fakePath) {
- if (err) return cb(err);
- if (!file) return cb(new Error(
- 'module not found: "' + id + '" from file '
- + parent.filename
- ));
-
- if (!pkg || !pkg.__dirname) {
- self.lookupPackage(file, function (err, p) {
- if (err) return cb(err);
- if (!p) p = {};
- if (!p.__dirname) p.__dirname = path.dirname(file);
- self.pkgCache[file] = p;
- onresolve(err, file, opts.packageFilter
- ? opts.packageFilter(p, p.__dirname) : p,
- fakePath
- );
- });
- }
- else cb(err, file, pkg, fakePath);
- });
- };
- Deps.prototype.readFile = function (file, id, pkg) {
- var self = this;
- if (xhas(this.fileCache, file)) {
- return toStream(this.fileCache[file]);
- }
- var rs = fs.createReadStream(file, {
- encoding: 'utf8'
- });
- return rs;
- };
- Deps.prototype.getTransforms = function (file, pkg, opts) {
- if (!opts) opts = {};
- var self = this;
-
- var isTopLevel;
- if (opts.builtin || opts.inNodeModules) isTopLevel = false;
- else isTopLevel = this._isTopLevel(file);
-
- var transforms = [].concat(isTopLevel ? this.transforms : [])
- .concat(getTransforms(pkg, {
- globalTransform: this.globalTransforms,
- transformKey: this.options.transformKey
- }))
- ;
- if (transforms.length === 0) return through();
-
- var pending = transforms.length;
- var streams = [];
- var input = through();
- var output = through();
- var dup = duplexer(input, output);
-
- for (var i = 0; i < transforms.length; i++) (function (i) {
- makeTransform(transforms[i], function (err, trs) {
- if (err) {
- return dup.emit('error', err);
- }
- streams[i] = trs;
- if (-- pending === 0) done();
- });
- })(i);
- return dup;
-
- function done () {
- var middle = combine.apply(null, streams);
- middle.on('error', function (err) {
- err.message += ' while parsing file: ' + file;
- if (!err.filename) err.filename = file;
- dup.emit('error', err);
- });
- input.pipe(middle).pipe(output);
- }
-
- function makeTransform (tr, cb) {
- var trOpts = {};
- if (Array.isArray(tr)) {
- trOpts = tr[1] || {};
- tr = tr[0];
- }
- trOpts._flags = trOpts.hasOwnProperty('_flags') ? trOpts._flags : self.options;
- if (typeof tr === 'function') {
- var t = tr(file, trOpts);
- // allow transforms to `stream.emit('dep', path)` to add dependencies for this file
- t.on('dep', function (dep) {
- if (!self._transformDeps[file]) self._transformDeps[file] = [];
- self._transformDeps[file].push(dep);
- });
- self.emit('transform', t, file);
- nextTick(cb, null, wrapTransform(t));
- }
- else {
- loadTransform(tr, trOpts, function (err, trs) {
- if (err) return cb(err);
- cb(null, wrapTransform(trs));
- });
- }
- }
-
- function loadTransform (id, trOpts, cb) {
- var params = {
- basedir: path.dirname(file),
- preserveSymlinks: false
- };
- nodeResolve(id, params, function nr (err, res, again) {
- if (err && again) return cb && cb(err);
-
- if (err) {
- params.basedir = pkg.__dirname;
- return nodeResolve(id, params, function (e, r) {
- nr(e, r, true);
- });
- }
-
- if (!res) return cb(new Error(
- 'cannot find transform module ' + tr
- + ' while transforming ' + file
- ));
-
- var r = require(res);
- if (typeof r !== 'function') {
- return cb(new Error(
- 'Unexpected ' + typeof r + ' exported by the '
- + JSON.stringify(res) + ' package. '
- + 'Expected a transform function.'
- ));
- }
-
- var trs = r(file, trOpts);
- // allow transforms to `stream.emit('dep', path)` to add dependencies for this file
- trs.on('dep', function (dep) {
- if (!self._transformDeps[file]) self._transformDeps[file] = [];
- self._transformDeps[file].push(dep);
- });
- self.emit('transform', trs, file);
- cb(null, trs);
- });
- }
- };
- Deps.prototype.walk = function (id, parent, cb) {
- var self = this;
- var opts = self.options;
- this.pending ++;
-
- var rec = {};
- var input;
- if (typeof id === 'object') {
- rec = xtend(id);
- if (rec.entry === false) delete rec.entry;
- id = rec.file || rec.id;
- input = true;
- this.inputPending ++;
- }
-
- self.resolve(id, parent, function (err, file, pkg, fakePath) {
- // this is checked early because parent.modules is also modified
- // by this function.
- var builtin = has(parent.modules, id);
- if (rec.expose) {
- // Set options.expose to make the resolved pathname available to the
- // caller. They may or may not have requested it, but it's harmless
- // to set this if they didn't.
- self.options.expose[rec.expose] =
- self.options.modules[rec.expose] = file;
- }
- if (pkg && !self._emittedPkg[pkg.__dirname]) {
- self._emittedPkg[pkg.__dirname] = true;
- self.emit('package', pkg);
- }
-
- if (opts.postFilter && !opts.postFilter(id, file, pkg)) {
- if (--self.pending === 0) self.push(null);
- if (input) --self.inputPending;
- return cb && cb(null, undefined);
- }
- if (err && rec.source) {
- file = rec.file;
-
- var ts = self.getTransforms(file, pkg);
- ts.on('error', function (err) {
- self.emit('error', err);
- });
- ts.pipe(concat(function (body) {
- rec.source = body.toString('utf8');
- fromSource(file, rec.source, pkg);
- }));
- return ts.end(rec.source);
- }
- if (err && self.options.ignoreMissing) {
- if (--self.pending === 0) self.push(null);
- if (input) --self.inputPending;
- self.emit('missing', id, parent);
- return cb && cb(null, undefined);
- }
- if (err) {
- var message = 'Can\'t walk dependency graph: ' + err.message;
- message += '\n required by ' + parent.filename;
- err.message = message;
- return self.emit('error', err);
- }
- if (self.visited[file]) {
- if (-- self.pending === 0) self.push(null);
- if (input) --self.inputPending;
- return cb && cb(null, file);
- }
- self.visited[file] = true;
-
- if (rec.source) {
- var ts = self.getTransforms(file, pkg);
- ts.on('error', function (err) {
- self.emit('error', err);
- });
- ts.pipe(concat(function (body) {
- rec.source = body.toString('utf8');
- fromSource(file, rec.source, pkg);
- }));
- return ts.end(rec.source);
- }
-
- var c = self.cache && self.cache[file];
- if (c) return fromDeps(file, c.source, c.package, fakePath, Object.keys(c.deps));
-
- self.persistentCache(file, id, pkg, persistentCacheFallback, function (err, c) {
- self.emit('file', file, id);
- if (err) {
- self.emit('error', err);
- return;
- }
- fromDeps(file, c.source, c.package, fakePath, Object.keys(c.deps));
- });
- function persistentCacheFallback (dataAsString, cb) {
- var stream = dataAsString ? toStream(dataAsString) : self.readFile(file, id, pkg).on('error', cb);
- stream
- .pipe(self.getTransforms(fakePath || file, pkg, {
- builtin: builtin,
- inNodeModules: parent.inNodeModules
- }))
- .on('error', cb)
- .pipe(concat(function (body) {
- var src = body.toString('utf8');
- try { var deps = getDeps(file, src); }
- catch (err) { cb(err); }
- if (deps) {
- cb(null, {
- source: src,
- package: pkg,
- deps: deps.reduce(function (deps, dep) {
- deps[dep] = true;
- return deps;
- }, {})
- });
- }
- }));
- }
- });
- function getDeps (file, src) {
- var deps = rec.noparse ? [] : self.parseDeps(file, src);
- // dependencies emitted by transforms
- if (self._transformDeps[file]) deps = deps.concat(self._transformDeps[file]);
- return deps;
- }
- function fromSource (file, src, pkg, fakePath) {
- var deps = getDeps(file, src);
- if (deps) fromDeps(file, src, pkg, fakePath, deps);
- }
-
- function fromDeps (file, src, pkg, fakePath, deps) {
- var p = deps.length;
- var resolved = {};
-
- if (input) --self.inputPending;
-
- (function resolve () {
- if (self.inputPending > 0) return setTimeout(resolve);
- deps.forEach(function (id) {
- if (opts.filter && !opts.filter(id)) {
- resolved[id] = false;
- if (--p === 0) done();
- return;
- }
- var isTopLevel = self._isTopLevel(fakePath || file);
- var current = {
- id: file,
- filename: file,
- basedir: path.dirname(file),
- paths: self.paths,
- package: pkg,
- inNodeModules: parent.inNodeModules || !isTopLevel
- };
- self.walk(id, current, function (err, r) {
- resolved[id] = r;
- if (--p === 0) done();
- });
- });
- if (deps.length === 0) done();
- })();
-
- function done () {
- if (!rec.id) rec.id = file;
- if (!rec.source) rec.source = src;
- if (!rec.deps) rec.deps = resolved;
- if (!rec.file) rec.file = file;
-
- if (self.entries.indexOf(file) >= 0) {
- rec.entry = true;
- }
- self.push(rec);
-
- if (cb) cb(null, file);
- if (-- self.pending === 0) self.push(null);
- }
- }
- };
- Deps.prototype.parseDeps = function (file, src, cb) {
- var self = this;
- if (this.options.noParse === true) return [];
- if (/\.json$/.test(file)) return [];
-
- if (Array.isArray(this.options.noParse)
- && this.options.noParse.indexOf(file) >= 0) {
- return [];
- }
-
- try { var deps = self.detective(src) }
- catch (ex) {
- var message = ex && ex.message ? ex.message : ex;
- throw new Error(
- 'Parsing file ' + file + ': ' + message
- );
- }
- return deps;
- };
- Deps.prototype.lookupPackage = function (file, cb) {
- var self = this;
-
- var cached = this.pkgCache[file];
- if (cached) return nextTick(cb, null, cached);
- if (cached === false) return nextTick(cb, null, undefined);
-
- var dirs = parents(file ? path.dirname(file) : self.basedir);
-
- (function next () {
- if (dirs.length === 0) {
- self.pkgCache[file] = false;
- return cb(null, undefined);
- }
- var dir = dirs.shift();
- if (dir.split(/[\\\/]/).slice(-1)[0] === 'node_modules') {
- return cb(null, undefined);
- }
-
- var pkgfile = path.join(dir, 'package.json');
-
- var cached = self.pkgCache[pkgfile];
- if (cached) return nextTick(cb, null, cached);
- else if (cached === false) return next();
-
- var pcached = self.pkgFileCachePending[pkgfile];
- if (pcached) return pcached.push(onpkg);
- pcached = self.pkgFileCachePending[pkgfile] = [];
-
- fs.readFile(pkgfile, function (err, src) {
- if (err) return onpkg();
- try { var pkg = JSON.parse(src) }
- catch (err) {
- return onpkg(new Error([
- err + ' while parsing json file ' + pkgfile
- ].join('')));
- }
- pkg.__dirname = dir;
-
- self.pkgCache[pkgfile] = pkg;
- self.pkgCache[file] = pkg;
- onpkg(null, pkg);
- });
-
- function onpkg (err, pkg) {
- if (self.pkgFileCachePending[pkgfile]) {
- var fns = self.pkgFileCachePending[pkgfile];
- delete self.pkgFileCachePending[pkgfile];
- fns.forEach(function (f) { f(err, pkg) });
- }
- if (err) cb(err);
- else if (pkg && typeof pkg === 'object') cb(null, pkg);
- else {
- self.pkgCache[pkgfile] = false;
- next();
- }
- }
- })();
- };
-
- function getTransforms (pkg, opts) {
- var trx = [];
- if (opts.transformKey) {
- var n = pkg;
- var keys = opts.transformKey;
- for (var i = 0; i < keys.length; i++) {
- if (n && typeof n === 'object') n = n[keys[i]];
- else break;
- }
- if (i === keys.length) {
- trx = [].concat(n).filter(Boolean);
- }
- }
- return trx.concat(opts.globalTransform || []);
- }
- function nextTick (cb) {
- var args = [].slice.call(arguments, 1);
- process.nextTick(function () { cb.apply(null, args) });
- }
- function xhas (obj) {
- if (!obj) return false;
- for (var i = 1; i < arguments.length; i++) {
- var key = arguments[i];
- if (!has(obj, key)) return false;
- obj = obj[key];
- }
- return true;
- }
- function toStream (dataAsString) {
- var tr = through();
- tr.push(dataAsString);
- tr.push(null);
- return tr;
- }
- function has (obj, key) {
- return obj && Object.prototype.hasOwnProperty.call(obj, key);
- }
- function wrapTransform (tr) {
- if (typeof tr.read === 'function') return tr;
- var input = through(), output = through();
- input.pipe(tr).pipe(output);
- var wrapper = duplexer(input, output);
- tr.on('error', function (err) { wrapper.emit('error', err) });
- return wrapper;
- }
|