pp-parachute
Version:
Airdrop JS Applications
250 lines (222 loc) • 7.83 kB
JavaScript
var EventEmitter = require('events').EventEmitter;
var Promise = require('bluebird');
var concat = require('gulp-concat');
var inherits = require('util').inherits;
var fastbind = require('../util/fastbind');
var globby = require('globby');
var vfs = require('vinyl-fs');
var path = require('path');
var AssetWatcher = require('./asset_watcher');
var idGenerator = 0;
var Stream = require('stream').Stream;
var SourceStream = require('vinyl-source-stream');
var debounceHead = require('../util/debounce').debounceHead;
var BundleStatus = {
None: 1,
Succeeded: 2,
Failed: 3,
Bundling: 4
};
var Bundler = function (assetGlobs, name, rootPath) {
EventEmitter.call(this);
this.bundlerId = idGenerator++; //used by environments to uniquely identify bundlers
this.assetGlobs = assetGlobs;
this.pipes = [];
this.bundlePromise = null;
this.bundleError = null;
this.vinylStream = null;
this.rootPath = rootPath || findFilePath();
this.name = name || 'bundler' + this.bundlerId;
this.bundleStatus = BundleStatus.None;
this.dirtyFileList = true;
this.rawOutput = null;
this.debouncedBundleFn = debounceHead(this, this._bundle, 0);
};
Bundler.BundleStatus = BundleStatus;
inherits(Bundler, EventEmitter);
Bundler.prototype.setRootPath = function (rootPath) {
this.rootPath = rootPath;
return this;
};
Bundler.prototype.getRootPath = function () {
return this.rootPath;
};
Bundler.prototype.setDebugName = function (name) {
this.name = name;
return this;
};
Bundler.prototype.pipe = function (stream) {
this.pipes.push(stream);
return this;
};
Bundler.prototype.isWatched = function () {
return this.assetWatcher !== null;
};
Bundler.prototype.build = function () {
if (!this.bundlePromise) {
this.buildPromise = this.debouncedBundleFn();
} else if (this.bundlePromise.isPending()) {
return this.bundlePromise;
} else {
this.emit('bundleCompleted', this, this.vinylStream, 0);
if (this.bundleStatus === BundleStatus.Succeeded) {
this.emit('bundleSucceeded', this, this.vinylStream, 0)
} else if (this.bundleStatus === BundleStatus.Failed) {
this.emit('bundleFailed', this, this.bundleError, 0);
}
}
return this.bundlePromise;
};
Bundler.prototype.watch = function () {
var self = this;
if (!this.assetWatcher) {
return this.watchFiles().then(function () {
return self.build();
});
}
return this.build();
};
//bundle resolves w/ vinyl or rejects w/ error
Bundler.prototype._bundle = function () {
var self = this;
this.bundleError = null;
var start = new Date();
this.bundlePromise = this.getFilePaths().then(function (filePaths) {
self.bundleStatus = BundleStatus.Bundling;
self.emit('bundleStarted', self, filePaths);
return self.bundle(filePaths);
}).then(function (file) {
self.rawOutput = file;
var stream = Bundler.Streamify(file);
self.vinylStream = Bundler.Streamify(self.applyPipes(stream));
self.bundleStatus = BundleStatus.Succeeded;
self.emit('bundleSucceeded', self, self.bundleError, new Date() - start);
self.emit('bundleCompleted', self, self.vinylStream, new Date() - start);
return Promise.resolve(self);
}).catch(function (bundleError) {
self.vinylStream = Bundler.Streamify(bundleError);
self.rawOutput = bundleError;
self.bundleError = bundleError;
self.bundleStatus = BundleStatus.Failed;
self.emit('bundleFailed', self, self.bundleError, new Date() - start);
self.emit('bundleCompleted', self, self.vinylStream,new Date() - start);
return Promise.resolve(self);
});
return this.bundlePromise;
};
Bundler.prototype.bundle = function (filePaths) {
return vfs.src(filePaths).pipe(concat(this.name));
};
Bundler.prototype.watchFiles = function () {
this.assetWatcher = new AssetWatcher();
this.assetWatcher.on('fileAdded', fastbind(this._onFileAdded, this));
this.assetWatcher.on('fileChanged', fastbind(this._onFileChanged, this));
this.assetWatcher.on('fileRemoved', fastbind(this._onFileRemoved, this));
return this.assetWatcher.watch(this.getWatchGlobs());
};
Bundler.prototype.getFilePaths = function () {
var globs = this.getWatchGlobs();
return new Promise(fastbind(function (resolve, reject) {
globby(globs, function (err, files) {
err && reject(err) || resolve(files);
});
}, this));
};
Bundler.prototype.getWatchGlobs = function () {
if (!Array.isArray(this.assetGlobs)) this.assetGlobs = [this.assetGlobs];
return this.assetGlobs.map(function (glob) {
//we need to handle negated paths explicitly because we prepend application root
var negated = false;
if (glob.indexOf('!') === 0) {
negated = true;
glob = glob.slice(1);
}
var joined = path.join(this.rootPath, glob);
if (negated) {
joined = '!' + joined;
}
return joined;
}, this);
};
Bundler.prototype.applyPipes = function (stream) {
for (var i = 0; i < this.pipes.length; i++) {
stream = stream.pipe(this.pipes[i]);
}
return stream;
};
Bundler.prototype.showBundleErrorJson = function() {
return JSON.stringify(this.bundleError);
};
Bundler.prototype.showBundleErrorHtml = function() {
console.log(this.bundleError);
return this.bundleError.toString();
};
Bundler.prototype._onFileAdded = function (filePath) {
this.dirtyFileList = true;
this.onFileAdded && this.onFileAdded(filePath);
this.debouncedBundleFn();
};
Bundler.prototype._onFileChanged = function (filePath) {
this.emit('fileChanged', filePath);
this.onFileChanged && this.onFileChanged(filePath);
this.debouncedBundleFn();
};
Bundler.prototype._onFileRemoved = function (filePath) {
this.dirtyFileList = true;
this.emit('fileRemoved', filePath);
this.onFileRemoved && this.onFileRemoved(filePath);
this.debouncedBundleFn();
};
Bundler.prototype.onFileAdded = function () {};
Bundler.prototype.onFileChanged = function () {};
Bundler.prototype.onFileRemoved = function () {};
Bundler.prototype.shutdown = function () {
this.assetWatcher && this.assetWatcher.shutdown();
};
Bundler.Streamify = function (input) {
var stream = null;
if (!input) {
stream = SourceStream('file');
stream.write(new Buffer(''));
stream.end();
} else if (input instanceof Stream) {
return input;
} else if (input instanceof Buffer) {
stream = SourceStream('file');
stream.write(input);
stream.end();
} else if (typeof input === 'string') {
stream = SourceStream('file');
stream.write(new Buffer(input));
stream.end();
} else if (typeof input === 'object') {
stream = SourceStream('file');
stream.write(new Buffer(JSON.stringify(input)));
stream.end();
} else {
stream = SourceStream('file');
stream.write(new Buffer(input.toString()));
stream.end();
}
return stream;
};
//gets the path to the file that the constructor was called in
//by parsing a stack trace.
function findFilePath() {
try {
throw new Error('give me a stack');
}
catch (e) {
var fileLines = e.stack.split('\n');
for (var i = 0; i < fileLines.length; i++) {
if (fileLines[i].indexOf('at new') !== -1) {
break;
}
}
var fileLine = fileLines[i + 1];
var start = fileLine.indexOf('(') + 1;
var end = fileLine.lastIndexOf('/');
return fileLine.substring(start, end);
}
}
module.exports = Bundler;