lasso
Version:
Lasso.js is a build tool and runtime library for building and bundling all of the resources needed by a web application
282 lines (221 loc) • 10 kB
JavaScript
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
var transforms = require('./transforms');
var through = require('through');
var logger = require('raptor-logging').logger(module);
var ok = require('assert').ok;
var fs = require('fs');
var CombinedStream = require('./util/CombinedStream');
var DeferredReadable = require('./util/DeferredReadable');
var nodePath = require('path');
var AsyncValue = require('raptor-async/AsyncValue');
function createReadDependencyStream(dependency, lassoContext, transformerAsyncValue) {
var deferredReadable = new DeferredReadable();
transformerAsyncValue.done(function (err, transformer) {
if (err) {
deferredReadable.emit('error', err);
return;
}
var contentType = dependency.getContentType();
var readContext = Object.create(lassoContext || {});
readContext.contentType = contentType;
readContext.dependency = dependency;
readContext.transformer = transformer;
readContext.dir = dependency.getDir ? dependency.getDir(lassoContext) : null;
if (dependency.getSourceFile) {
readContext.path = dependency.getSourceFile();
}
function createReadStream() {
var err;
var readStream = dependency.read(readContext);
if (!readStream) {
err = new Error('Dependency did not return read stream: ' + dependency);
}
if (typeof readStream.pipe !== 'function') {
err = new Error('Dependency returned invalid stream: ' + dependency);
}
if (err) {
return new DeferredReadable(function () {
this.emit('error', err);
this.push(null);
});
}
return readStream;
}
function createTransformedStream(readStream) {
if (!transformer.hasTransforms()) {
// simply return the dependency read stream if there are no transforms
return readStream;
}
return transformer.transform(readStream, readContext);
}
var cache = lassoContext.cache;
var cacheKey = dependency.getReadCacheKey();
if (cache && dependency.shouldCache(lassoContext) && cacheKey) {
var readCache = cache.readCache;
dependency.getLastModified(lassoContext).then(lastModified => {
if (!lastModified || lastModified <= 0) {
// This dependency does not support caching
// so don't go through the caching layer
deferredReadable.setWrappedStream(createTransformedStream(createReadStream()));
return;
}
var cachedReadStream = readCache.createReadStream(cacheKey, {
lastModified: lastModified,
builder: function (callback) {
// The read dependency has not been cached
callback(null, createReadStream);
}
});
deferredReadable.setWrappedStream(createTransformedStream(cachedReadStream));
});
} else {
deferredReadable.setWrappedStream(createTransformedStream(createReadStream()));
}
});
return deferredReadable;
}
function createReadBundleStream(bundle, lassoContext, transformerAsyncValue) {
var combinedStream = new CombinedStream({
separator: '\n'
});
if (!bundle.hasContent()) {
return combinedStream;
}
var curIndex;
var timeoutId;
var timeout = lassoContext.config.getBundleReadTimeout();
if (timeout == null) {
timeout = exports.DEFAULT_READ_TIMEOUT;
}
logger.info('Bundle read timeout value: ' + timeout);
var dependencies = bundle.getDependencies();
var len = dependencies.length;
combinedStream.on('beginStream', function (event) {
curIndex = event.index;
var dependency = event.stream._dependency;
logger.debug('(' + (curIndex + 1) + ' of ' + len + ')', 'Begin reading dependency: ', dependency.toString());
if (timeout > 0) {
timeoutId = setTimeout(onTimeout, timeout);
}
});
combinedStream.on('error', function () {
if (timeoutId) {
clearTimeout(timeoutId);
}
});
combinedStream.on('endStream', function (event) {
if (timeoutId) {
clearTimeout(timeoutId);
}
var dependency = event.stream._dependency;
logger.debug('(' + (curIndex + 1) + ' of ' + len + ')', 'Completed reading dependency: ', dependency.toString());
});
logger.debug('Reading bundle: ' + bundle.getKey());
for (var i = 0; i < len; i++) {
var dependency = dependencies[i];
if (dependency && dependency.hasContent() && !dependency.isExternalResource(lassoContext)) {
// Each transform needs its own lassoContext since we update the lassoContext with the
// current dependency and each dependency is transformed in parallel
var readContext = Object.create(lassoContext || {});
readContext.dependency = dependency;
readContext.bundle = bundle;
var stream = createReadDependencyStream(dependency, readContext, transformerAsyncValue);
// tag the stream with the dependency
stream._dependency = dependency;
combinedStream.addStream(stream);
}
}
function onTimeout() {
var dependency = dependencies[curIndex];
var message = 'Reading dependency timed out after ' + timeout + 'ms: ' + dependency.toString();
combinedStream.emit('error', new Error(message));
combinedStream.forEachStream(function (stream) {
if (stream.end) {
stream.end();
}
});
}
return combinedStream;
}
function createBundleReader(bundle, lassoContext) {
ok(bundle, 'bundle is required');
ok(lassoContext, 'lassoContext is required');
var transformContext = Object.create(lassoContext || {});
transformContext.contentType = bundle.contentType;
// TODO: Change to fully use async/await
var transformerAsyncValue = new AsyncValue();
transforms.createTransformer(lassoContext.config.getTransforms(), transformContext).then(transformer => {
transformerAsyncValue.resolve(transformer);
}).catch(err => {
transformerAsyncValue.reject(err);
});
return {
readBundle: function () {
return createReadBundleStream(bundle, lassoContext, transformerAsyncValue);
},
readDependency: function (dependency) {
ok(dependency, 'dependency is required');
ok(typeof dependency.read === 'function', 'Invalid dependency');
return createReadDependencyStream(dependency, lassoContext, transformerAsyncValue);
},
readBundleFully() {
var _this = this;
return _asyncToGenerator(function* () {
if (!bundle.hasContent()) return '';
return new Promise(function (resolve, reject) {
var hasError = false;
function handleError(e) {
if (hasError) {
return;
}
hasError = true;
reject(e);
}
var input = _this.readBundle();
var code = '';
var captureStream = through(function write(data) {
code += data;
}, function end() {
if (hasError) {
return;
}
resolve(code);
});
input.on('error', handleError);
captureStream.on('error', handleError);
input.pipe(captureStream);
});
})();
}
};
}
function createResourceReader(path, lassoContext) {
return {
readResource(options) {
var readStream = fs.createReadStream(path, options);
var filename = nodePath.basename(path);
// Use the file extension as the content type
var contentType = filename.substring(filename.lastIndexOf('.') + 1);
var transformContext = Object.create(lassoContext || {});
transformContext.contentType = contentType;
transformContext.path = path;
transformContext.dir = nodePath.dirname(path);
var readable = new DeferredReadable();
transforms.createTransformer(lassoContext.config.getTransforms(), transformContext).then(transformer => {
if (transformer.hasTransforms() === false) {
// simply use the input stream since there are no transforms after the filtering
readable.setWrappedStream(readStream);
return;
}
readable.setWrappedStream(transformer.transform(readStream, transformContext));
}).catch(err => {
readable.emit('error', err);
});
return readable;
}
};
}
exports.DEFAULT_READ_TIMEOUT = 10000;
exports.readBundle = createReadBundleStream;
exports.createBundleReader = createBundleReader;
exports.createResourceReader = createResourceReader;