| var mdeps = require('module-deps'); |
| var depsSort = require('deps-sort'); |
| var bpack = require('browser-pack'); |
| var insertGlobals = require('insert-module-globals'); |
| var syntaxError = require('syntax-error'); |
| |
| var builtins = require('./lib/builtins.js'); |
| |
| var splicer = require('labeled-stream-splicer'); |
| var through = require('through2'); |
| var concat = require('concat-stream'); |
| |
| var inherits = require('inherits'); |
| var EventEmitter = require('events').EventEmitter; |
| var xtend = require('xtend'); |
| var isArray = Array.isArray; |
| var defined = require('defined'); |
| var has = require('has'); |
| var sanitize = require('htmlescape').sanitize; |
| var shasum = require('shasum'); |
| |
| var bresolve = require('browser-resolve'); |
| var resolve = require('resolve'); |
| |
| var readonly = require('read-only-stream'); |
| |
| module.exports = Browserify; |
| inherits(Browserify, EventEmitter); |
| |
| var fs = require('fs'); |
| var path = require('path'); |
| var relativePath = require('cached-path-relative') |
| var paths = { |
| empty: path.join(__dirname, 'lib/_empty.js') |
| }; |
| |
| function Browserify (files, opts) { |
| var self = this; |
| if (!(this instanceof Browserify)) return new Browserify(files, opts); |
| if (!opts) opts = {}; |
| |
| if (typeof files === 'string' || isArray(files) || isStream(files)) { |
| opts = xtend(opts, { entries: [].concat(opts.entries || [], files) }); |
| } |
| else opts = xtend(files, opts); |
| |
| self._options = opts; |
| if (opts.noparse) opts.noParse = opts.noparse; |
| |
| if (opts.basedir !== undefined && typeof opts.basedir !== 'string') { |
| throw new Error('opts.basedir must be either undefined or a string.'); |
| } |
| |
| opts.dedupe = opts.dedupe === false ? false : true; |
| |
| self._external = []; |
| self._exclude = []; |
| self._ignore = []; |
| self._expose = {}; |
| self._hashes = {}; |
| self._pending = 0; |
| self._transformOrder = 0; |
| self._transformPending = 0; |
| self._transforms = []; |
| self._entryOrder = 0; |
| self._ticked = false; |
| self._bresolve = opts.browserField === false |
| ? function (id, opts, cb) { |
| if (!opts.basedir) opts.basedir = path.dirname(opts.filename) |
| resolve(id, opts, cb) |
| } |
| : bresolve |
| ; |
| self._syntaxCache = {}; |
| |
| var ignoreTransform = [].concat(opts.ignoreTransform).filter(Boolean); |
| self._filterTransform = function (tr) { |
| if (isArray(tr)) { |
| return ignoreTransform.indexOf(tr[0]) === -1; |
| } |
| return ignoreTransform.indexOf(tr) === -1; |
| }; |
| |
| self.pipeline = self._createPipeline(opts); |
| |
| [].concat(opts.transform).filter(Boolean).filter(self._filterTransform) |
| .forEach(function (tr) { |
| self.transform(tr); |
| }); |
| |
| [].concat(opts.entries).filter(Boolean).forEach(function (file) { |
| self.add(file, { basedir: opts.basedir }); |
| }); |
| |
| [].concat(opts.require).filter(Boolean).forEach(function (file) { |
| self.require(file, { basedir: opts.basedir }); |
| }); |
| |
| [].concat(opts.plugin).filter(Boolean).forEach(function (p) { |
| self.plugin(p, { basedir: opts.basedir }); |
| }); |
| } |
| |
| Browserify.prototype.require = function (file, opts) { |
| var self = this; |
| if (isArray(file)) { |
| file.forEach(function (x) { |
| if (typeof x === 'object') { |
| self.require(x.file, xtend(opts, x)); |
| } |
| else self.require(x, opts); |
| }); |
| return this; |
| } |
| |
| if (!opts) opts = {}; |
| var basedir = defined(opts.basedir, self._options.basedir, process.cwd()); |
| var expose = opts.expose; |
| if (file === expose && /^[\.]/.test(expose)) { |
| expose = '/' + relativePath(basedir, expose); |
| expose = expose.replace(/\\/g, '/'); |
| } |
| if (expose === undefined && this._options.exposeAll) { |
| expose = true; |
| } |
| if (expose === true) { |
| expose = '/' + relativePath(basedir, file); |
| expose = expose.replace(/\\/g, '/'); |
| } |
| |
| if (isStream(file)) { |
| self._pending ++; |
| var order = self._entryOrder ++; |
| file.pipe(concat(function (buf) { |
| var filename = opts.file || file.file || path.join( |
| basedir, |
| '_stream_' + order + '.js' |
| ); |
| var id = file.id || expose || filename; |
| if (expose || opts.entry === false) { |
| self._expose[id] = filename; |
| } |
| if (!opts.entry && self._options.exports === undefined) { |
| self._bpack.hasExports = true; |
| } |
| var rec = { |
| source: buf.toString('utf8'), |
| entry: defined(opts.entry, false), |
| file: filename, |
| id: id |
| }; |
| if (rec.entry) rec.order = order; |
| if (rec.transform === false) rec.transform = false; |
| self.pipeline.write(rec); |
| |
| if (-- self._pending === 0) self.emit('_ready'); |
| })); |
| return this; |
| } |
| |
| var row; |
| if (typeof file === 'object') { |
| row = xtend(file, opts); |
| } |
| else if (!opts.entry && isExternalModule(file)) { |
| // external module or builtin |
| row = xtend(opts, { id: expose || file, file: file }); |
| } |
| else { |
| row = xtend(opts, { file: path.resolve(basedir, file) }); |
| } |
| |
| if (!row.id) { |
| row.id = expose || row.file; |
| } |
| if (expose || !row.entry) { |
| // Make this available to mdeps so that it can assign the value when it |
| // resolves the pathname. |
| row.expose = row.id; |
| } |
| |
| if (opts.external) return self.external(file, opts); |
| if (row.entry === undefined) row.entry = false; |
| |
| if (!row.entry && self._options.exports === undefined) { |
| self._bpack.hasExports = true; |
| } |
| |
| if (row.entry) row.order = self._entryOrder ++; |
| |
| if (opts.transform === false) row.transform = false; |
| self.pipeline.write(row); |
| return self; |
| }; |
| |
| Browserify.prototype.add = function (file, opts) { |
| var self = this; |
| if (!opts) opts = {}; |
| if (isArray(file)) { |
| file.forEach(function (x) { self.add(x, opts) }); |
| return this; |
| } |
| return this.require(file, xtend({ entry: true, expose: false }, opts)); |
| }; |
| |
| Browserify.prototype.external = function (file, opts) { |
| var self = this; |
| if (isArray(file)) { |
| file.forEach(function (f) { |
| if (typeof f === 'object') { |
| self.external(f, xtend(opts, f)); |
| } |
| else self.external(f, opts) |
| }); |
| return this; |
| } |
| if (file && typeof file === 'object' && typeof file.bundle === 'function') { |
| var b = file; |
| self._pending ++; |
| |
| var bdeps = {}; |
| var blabels = {}; |
| |
| b.on('label', function (prev, id) { |
| self._external.push(id); |
| |
| if (prev !== id) { |
| blabels[prev] = id; |
| self._external.push(prev); |
| } |
| }); |
| |
| b.pipeline.get('deps').push(through.obj(function (row, enc, next) { |
| bdeps = xtend(bdeps, row.deps); |
| this.push(row); |
| next(); |
| })); |
| |
| self.on('dep', function (row) { |
| Object.keys(row.deps).forEach(function (key) { |
| var prev = bdeps[key]; |
| if (prev) { |
| var id = blabels[prev]; |
| if (id) { |
| row.indexDeps[key] = id; |
| } |
| } |
| }); |
| }); |
| |
| b.pipeline.get('label').once('end', function () { |
| if (-- self._pending === 0) self.emit('_ready'); |
| }); |
| return this; |
| } |
| |
| if (!opts) opts = {}; |
| var basedir = defined(opts.basedir, process.cwd()); |
| this._external.push(file); |
| this._external.push('/' + relativePath(basedir, file)); |
| return this; |
| }; |
| |
| Browserify.prototype.exclude = function (file, opts) { |
| if (!opts) opts = {}; |
| var basedir = defined(opts.basedir, process.cwd()); |
| this._exclude.push(file); |
| this._exclude.push('/' + relativePath(basedir, file)); |
| return this; |
| }; |
| |
| Browserify.prototype.ignore = function (file, opts) { |
| if (!opts) opts = {}; |
| var basedir = defined(opts.basedir, process.cwd()); |
| |
| // Handle relative paths |
| if (file[0] === '.') { |
| this._ignore.push(path.resolve(basedir, file)); |
| } |
| else { |
| this._ignore.push(file); |
| } |
| return this; |
| }; |
| |
| Browserify.prototype.transform = function (tr, opts) { |
| var self = this; |
| if (typeof opts === 'function' || typeof opts === 'string') { |
| tr = [ opts, tr ]; |
| } |
| if (isArray(tr)) { |
| opts = tr[1]; |
| tr = tr[0]; |
| } |
| |
| //if the bundler is ignoring this transform |
| if (typeof tr === 'string' && !self._filterTransform(tr)) { |
| return this; |
| } |
| |
| function resolved () { |
| self._transforms[order] = rec; |
| -- self._pending; |
| if (-- self._transformPending === 0) { |
| self._transforms.forEach(function (transform) { |
| self.pipeline.write(transform); |
| }); |
| |
| if (self._pending === 0) { |
| self.emit('_ready'); |
| } |
| } |
| } |
| |
| if (!opts) opts = {}; |
| opts._flags = '_flags' in opts ? opts._flags : self._options; |
| |
| var basedir = defined(opts.basedir, this._options.basedir, process.cwd()); |
| var order = self._transformOrder ++; |
| self._pending ++; |
| self._transformPending ++; |
| |
| var rec = { |
| transform: tr, |
| options: opts, |
| global: opts.global |
| }; |
| |
| if (typeof tr === 'string') { |
| var topts = { |
| basedir: basedir, |
| paths: (self._options.paths || []).map(function (p) { |
| return path.resolve(basedir, p); |
| }) |
| }; |
| resolve(tr, topts, function (err, res) { |
| if (err) return self.emit('error', err); |
| rec.transform = res; |
| resolved(); |
| }); |
| } |
| else process.nextTick(resolved); |
| return this; |
| }; |
| |
| Browserify.prototype.plugin = function (p, opts) { |
| if (isArray(p)) { |
| opts = p[1]; |
| p = p[0]; |
| } |
| if (!opts) opts = {}; |
| var basedir = defined(opts.basedir, this._options.basedir, process.cwd()); |
| if (typeof p === 'function') { |
| p(this, opts); |
| } |
| else { |
| var pfile = resolve.sync(String(p), { basedir: basedir }) |
| var f = require(pfile); |
| if (typeof f !== 'function') { |
| throw new Error('plugin ' + p + ' should export a function'); |
| } |
| f(this, opts); |
| } |
| return this; |
| }; |
| |
| Browserify.prototype._createPipeline = function (opts) { |
| var self = this; |
| if (!opts) opts = {}; |
| this._mdeps = this._createDeps(opts); |
| this._mdeps.on('file', function (file, id) { |
| pipeline.emit('file', file, id); |
| self.emit('file', file, id); |
| }); |
| this._mdeps.on('package', function (pkg) { |
| pipeline.emit('package', pkg); |
| self.emit('package', pkg); |
| }); |
| this._mdeps.on('transform', function (tr, file) { |
| pipeline.emit('transform', tr, file); |
| self.emit('transform', tr, file); |
| }); |
| |
| var dopts = { |
| index: !opts.fullPaths && !opts.exposeAll, |
| dedupe: opts.dedupe, |
| expose: this._expose |
| }; |
| this._bpack = bpack(xtend(opts, { raw: true })); |
| |
| var pipeline = splicer.obj([ |
| 'record', [ this._recorder() ], |
| 'deps', [ this._mdeps ], |
| 'json', [ this._json() ], |
| 'unbom', [ this._unbom() ], |
| 'unshebang', [ this._unshebang() ], |
| 'syntax', [ this._syntax() ], |
| 'sort', [ depsSort(dopts) ], |
| 'dedupe', [ this._dedupe() ], |
| 'label', [ this._label(opts) ], |
| 'emit-deps', [ this._emitDeps() ], |
| 'debug', [ this._debug(opts) ], |
| 'pack', [ this._bpack ], |
| 'wrap', [] |
| ]); |
| if (opts.exposeAll) { |
| var basedir = defined(opts.basedir, process.cwd()); |
| pipeline.get('deps').push(through.obj(function (row, enc, next) { |
| if (self._external.indexOf(row.id) >= 0) return next(); |
| if (self._external.indexOf(row.file) >= 0) return next(); |
| |
| if (isAbsolutePath(row.id)) { |
| row.id = '/' + relativePath(basedir, row.file); |
| } |
| Object.keys(row.deps || {}).forEach(function (key) { |
| row.deps[key] = '/' + relativePath(basedir, row.deps[key]); |
| }); |
| this.push(row); |
| next(); |
| })); |
| } |
| return pipeline; |
| }; |
| |
| Browserify.prototype._createDeps = function (opts) { |
| var self = this; |
| var mopts = xtend(opts); |
| var basedir = defined(opts.basedir, process.cwd()); |
| |
| // Let mdeps populate these values since it will be resolving file paths |
| // anyway. |
| mopts.expose = this._expose; |
| mopts.extensions = [ '.js', '.json' ].concat(mopts.extensions || []); |
| self._extensions = mopts.extensions; |
| |
| mopts.transform = []; |
| mopts.transformKey = defined(opts.transformKey, [ 'browserify', 'transform' ]); |
| mopts.postFilter = function (id, file, pkg) { |
| if (opts.postFilter && !opts.postFilter(id, file, pkg)) return false; |
| if (self._external.indexOf(file) >= 0) return false; |
| if (self._exclude.indexOf(file) >= 0) return false; |
| |
| //filter transforms on module dependencies |
| if (pkg && pkg.browserify && pkg.browserify.transform) { |
| //In edge cases it may be a string |
| pkg.browserify.transform = [].concat(pkg.browserify.transform) |
| .filter(Boolean) |
| .filter(self._filterTransform); |
| } |
| return true; |
| }; |
| mopts.filter = function (id) { |
| if (opts.filter && !opts.filter(id)) return false; |
| if (self._external.indexOf(id) >= 0) return false; |
| if (self._exclude.indexOf(id) >= 0) return false; |
| if (opts.bundleExternal === false && isExternalModule(id)) { |
| return false; |
| } |
| return true; |
| }; |
| mopts.resolve = function (id, parent, cb) { |
| if (self._ignore.indexOf(id) >= 0) return cb(null, paths.empty, {}); |
| |
| self._bresolve(id, parent, function (err, file, pkg) { |
| if (file && self._ignore.indexOf(file) >= 0) { |
| return cb(null, paths.empty, {}); |
| } |
| if (file && self._ignore.length) { |
| var nm = file.split('/node_modules/')[1]; |
| if (nm) { |
| nm = nm.split('/')[0]; |
| if (self._ignore.indexOf(nm) >= 0) { |
| return cb(null, paths.empty, {}); |
| } |
| } |
| } |
| |
| if (file) { |
| var ex = '/' + relativePath(basedir, file); |
| if (self._external.indexOf(ex) >= 0) { |
| return cb(null, ex); |
| } |
| if (self._exclude.indexOf(ex) >= 0) { |
| return cb(null, ex); |
| } |
| if (self._ignore.indexOf(ex) >= 0) { |
| return cb(null, paths.empty, {}); |
| } |
| } |
| if (err) cb(err, file, pkg) |
| else if (file) fs.realpath(file, function (err, res) { |
| cb(err, res, pkg, file); |
| }); |
| else cb(err, null, pkg) |
| }); |
| }; |
| |
| if (opts.builtins === false) { |
| mopts.modules = {}; |
| self._exclude.push.apply(self._exclude, Object.keys(builtins)); |
| } |
| else if (opts.builtins && isArray(opts.builtins)) { |
| mopts.modules = {}; |
| opts.builtins.forEach(function (key) { |
| mopts.modules[key] = builtins[key]; |
| }); |
| } |
| else if (opts.builtins && typeof opts.builtins === 'object') { |
| mopts.modules = opts.builtins; |
| } |
| else mopts.modules = xtend(builtins); |
| |
| Object.keys(builtins).forEach(function (key) { |
| if (!has(mopts.modules, key)) self._exclude.push(key); |
| }); |
| |
| mopts.globalTransform = []; |
| if (!this._bundled) { |
| this.once('bundle', function () { |
| self.pipeline.write({ |
| transform: globalTr, |
| global: true, |
| options: {} |
| }); |
| }); |
| } |
| |
| var no = [].concat(opts.noParse).filter(Boolean); |
| var absno = no.filter(function(x) { |
| return typeof x === 'string'; |
| }).map(function (x) { |
| return path.resolve(basedir, x); |
| }); |
| |
| function globalTr (file) { |
| if (opts.detectGlobals === false) return through(); |
| |
| if (opts.noParse === true) return through(); |
| if (no.indexOf(file) >= 0) return through(); |
| if (absno.indexOf(file) >= 0) return through(); |
| |
| var parts = file.split('/node_modules/'); |
| for (var i = 0; i < no.length; i++) { |
| if (typeof no[i] === 'function' && no[i](file)) { |
| return through(); |
| } |
| else if (no[i] === parts[parts.length-1].split('/')[0]) { |
| return through(); |
| } |
| else if (no[i] === parts[parts.length-1]) { |
| return through(); |
| } |
| } |
| |
| var vars = xtend({ |
| process: function () { return "require('_process')" }, |
| }, opts.insertGlobalVars); |
| |
| if (opts.bundleExternal === false) { |
| vars.process = undefined; |
| vars.buffer = undefined; |
| } |
| |
| return insertGlobals(file, xtend(opts, { |
| debug: opts.debug, |
| always: opts.insertGlobals, |
| basedir: opts.commondir === false |
| ? '/' |
| : opts.basedir || process.cwd() |
| , |
| vars: vars |
| })); |
| } |
| return mdeps(mopts); |
| }; |
| |
| Browserify.prototype._recorder = function (opts) { |
| var self = this; |
| var ended = false; |
| this._recorded = []; |
| |
| if (!this._ticked) { |
| process.nextTick(function () { |
| self._ticked = true; |
| self._recorded.forEach(function (row) { |
| stream.push(row); |
| }); |
| if (ended) stream.push(null); |
| }); |
| } |
| |
| var stream = through.obj(write, end); |
| return stream; |
| |
| function write (row, enc, next) { |
| self._recorded.push(row); |
| if (self._ticked) this.push(row); |
| next(); |
| } |
| function end () { |
| ended = true; |
| if (self._ticked) this.push(null); |
| } |
| }; |
| |
| Browserify.prototype._json = function () { |
| return through.obj(function (row, enc, next) { |
| if (/\.json$/.test(row.file)) { |
| row.source = 'module.exports=' + sanitize(row.source); |
| } |
| this.push(row); |
| next(); |
| }); |
| }; |
| |
| Browserify.prototype._unbom = function () { |
| return through.obj(function (row, enc, next) { |
| if (/^\ufeff/.test(row.source)) { |
| row.source = row.source.replace(/^\ufeff/, ''); |
| } |
| this.push(row); |
| next(); |
| }); |
| }; |
| |
| Browserify.prototype._unshebang = function () { |
| return through.obj(function (row, enc, next) { |
| if (/^#!/.test(row.source)) { |
| row.source = row.source.replace(/^#![^\n]*\n/, ''); |
| } |
| this.push(row); |
| next(); |
| }); |
| }; |
| |
| Browserify.prototype._syntax = function () { |
| var self = this; |
| return through.obj(function (row, enc, next) { |
| var h = shasum(row.source); |
| if (typeof self._syntaxCache[h] === 'undefined') { |
| var err = syntaxError(row.source, row.file || row.id); |
| if (err) return this.emit('error', err); |
| self._syntaxCache[h] = true; |
| } |
| this.push(row); |
| next(); |
| }); |
| }; |
| |
| Browserify.prototype._dedupe = function () { |
| return through.obj(function (row, enc, next) { |
| if (!row.dedupeIndex && row.dedupe) { |
| row.source = 'arguments[4][' |
| + JSON.stringify(row.dedupe) |
| + '][0].apply(exports,arguments)' |
| ; |
| row.nomap = true; |
| } |
| else if (row.dedupeIndex) { |
| row.source = 'arguments[4][' |
| + JSON.stringify(row.dedupeIndex) |
| + '][0].apply(exports,arguments)' |
| ; |
| row.nomap = true; |
| } |
| if (row.dedupeIndex && row.indexDeps) { |
| row.indexDeps.dup = row.dedupeIndex; |
| } |
| this.push(row); |
| next(); |
| }); |
| }; |
| |
| Browserify.prototype._label = function (opts) { |
| var self = this; |
| var basedir = defined(opts.basedir, process.cwd()); |
| |
| return through.obj(function (row, enc, next) { |
| var prev = row.id; |
| |
| if (self._external.indexOf(row.id) >= 0) return next(); |
| if (self._external.indexOf('/' + relativePath(basedir, row.id)) >= 0) { |
| return next(); |
| } |
| if (self._external.indexOf(row.file) >= 0) return next(); |
| |
| if (row.index) row.id = row.index; |
| |
| self.emit('label', prev, row.id); |
| if (row.indexDeps) row.deps = row.indexDeps || {}; |
| |
| Object.keys(row.deps).forEach(function (key) { |
| if (self._expose[key]) { |
| row.deps[key] = key; |
| return; |
| } |
| |
| var afile = path.resolve(path.dirname(row.file), key); |
| var rfile = '/' + relativePath(basedir, afile); |
| if (self._external.indexOf(rfile) >= 0) { |
| row.deps[key] = rfile; |
| } |
| if (self._external.indexOf(afile) >= 0) { |
| row.deps[key] = rfile; |
| } |
| if (self._external.indexOf(key) >= 0) { |
| row.deps[key] = key; |
| return; |
| } |
| |
| for (var i = 0; i < self._extensions.length; i++) { |
| var ex = self._extensions[i]; |
| if (self._external.indexOf(rfile + ex) >= 0) { |
| row.deps[key] = rfile + ex; |
| break; |
| } |
| } |
| }); |
| |
| if (row.entry || row.expose) { |
| self._bpack.standaloneModule = row.id; |
| } |
| this.push(row); |
| next(); |
| }); |
| }; |
| |
| Browserify.prototype._emitDeps = function () { |
| var self = this; |
| return through.obj(function (row, enc, next) { |
| self.emit('dep', row); |
| this.push(row); |
| next(); |
| }) |
| }; |
| |
| Browserify.prototype._debug = function (opts) { |
| var basedir = defined(opts.basedir, process.cwd()); |
| return through.obj(function (row, enc, next) { |
| if (opts.debug) { |
| row.sourceRoot = 'file://localhost'; |
| row.sourceFile = relativePath(basedir, row.file) |
| .replace(/\\/g, '/'); |
| } |
| this.push(row); |
| next(); |
| }); |
| }; |
| |
| Browserify.prototype.reset = function (opts) { |
| if (!opts) opts = {}; |
| var hadExports = this._bpack.hasExports; |
| this.pipeline = this._createPipeline(xtend(opts, this._options)); |
| this._bpack.hasExports = hadExports; |
| this._entryOrder = 0; |
| this._bundled = false; |
| this.emit('reset'); |
| }; |
| |
| Browserify.prototype.bundle = function (cb) { |
| var self = this; |
| if (cb && typeof cb === 'object') { |
| throw new Error( |
| 'bundle() no longer accepts option arguments.\n' |
| + 'Move all option arguments to the browserify() constructor.' |
| ); |
| } |
| if (this._bundled) { |
| var recorded = this._recorded; |
| this.reset(); |
| recorded.forEach(function (x) { |
| self.pipeline.write(x); |
| }); |
| } |
| var output = readonly(this.pipeline); |
| if (cb) { |
| output.on('error', cb); |
| output.pipe(concat(function (body) { |
| cb(null, body); |
| })); |
| } |
| |
| function ready () { |
| self.emit('bundle', output); |
| self.pipeline.end(); |
| } |
| |
| if (this._pending === 0) ready(); |
| else this.once('_ready', ready); |
| |
| this._bundled = true; |
| return output; |
| }; |
| |
| function isStream (s) { return s && typeof s.pipe === 'function' } |
| function isAbsolutePath (file) { |
| var regexp = process.platform === 'win32' ? |
| /^\w:/ : |
| /^\//; |
| return regexp.test(file); |
| } |
| function isExternalModule (file) { |
| var regexp = process.platform === 'win32' ? |
| /^(\.|\w:)/ : |
| /^[\/.]/; |
| return !regexp.test(file); |
| } |