'use strict';
var Builder = require('systemjs-builder');
var fs = require('../fs');
var path = require('path');
var vm = require('vm');
var glob = require('glob');
/**
* @module tasks/javascript/bundle
* @description use systemJS to create bundles of the specified glob passed
*/
/**
* taskFactory
* @param options {Object}
* @returns {Function}
*/
module.exports = function taskFactory(options) {
var defaults = {
destination : '',
product : '',
channel : '',
configLocation : '',
base : '',
arithmetic : '',
application : {},
bundles : null,
builder : {
minify : false,
sourceMaps : false,
mangle : false
}
};
options = Object.assign({}, defaults, options);
const DEFAULT_MODULE_FORMAT = 'umd';
var fileConfig;
var configSet = false;
var sandbox = {
System: {
config: function (config) {
fileConfig = config;
}
}
};
var configPath = path.resolve(options.configLocation);
var builder = new Builder(options.base);
var configOverride = {
baseURL: options.base,
paths: {
'node_modules/*': './node_modules/*'
}
};
/**
* @param source {String}
* @param format {String}
* @returns {Object}
*/
function getBuilderOptions(source, format) {
var builderOptions = options.builder;
if (bundleIsSelfExecuting(source)) {
builderOptions.globalName = options.bundles[source].name || '';
}
if (format) {
builderOptions.format = format;
}
return builderOptions;
}
/**
* @description return the path for the module depending on, if any format is specified.
* @param destination {String}
* @param format {String}
* @returns {String}
*/
function getDestinationPath (destination, format) {
if (format === DEFAULT_MODULE_FORMAT) {
return destination;
}
var parts = path.parse(destination);
return path.join(parts.dir, format, parts.base);
}
/**
* @param source {String}
* @returns {Array}
*/
function getBundleFormats(source) {
if (options.bundles && options.bundles[source] && options.bundles[source].format) {
return options.bundles[source].format;
}
return [DEFAULT_MODULE_FORMAT];
}
/**
* @param source {String}
* @returns {boolean}
*/
function bundleIsSelfExecuting(source) {
return !!(options.bundles && options.bundles[source] && options.bundles[source].sfx);
}
/**
* @param source {String}
* @returns {boolean}
*/
function bundleHasName(source) {
return !!(options.bundles && options.bundles[source] && options.bundles[source].name);
}
/**
* @description get message about created bundle
* @param source {String}
* @param destination {String}
* @param format {String}
* @returns {Boolean}
*/
function logCreatedMessage(source, destination, format) {
var message = 'bundle created: ' + destination + (bundleIsSelfExecuting(source) ? ' (' + format + ' self executing)' : '') + '\n';
if (bundleIsSelfExecuting(source) && !bundleHasName(source)) {
message += source + ' has not been assigned a name to expose to global scope\n';
}
return process.stdout.write(message);
}
/**
* @description triggers promised builds of module for each of the specified formats in the bundles in
* the bundle config or just for default module type if no config exists.
* @param source {String} file path
* @returns {Promise}
*/
function createBundles(source) {
var destination = path.join(options.destination, source);
var bundleMethod = bundleIsSelfExecuting(source) ? 'buildStatic' : 'bundle';
return Promise.all(getBundleFormats(source).map(function(format) {
var builderOptions = getBuilderOptions(source, format);
return createBundleForFormatType(source + options.arithmetic, destination, bundleMethod, builderOptions);
}));
}
/**
* @description executes the system JS builder for a module of the specified format
* umd, amd, cjs or es6 for consumption in different environments.
* @param expression {String}
* @param destination {String}
* @param bundleMethod {String}
* @param builderOptions {Object}
* @returns {Promise}
*/
function createBundleForFormatType(expression, destination, bundleMethod, builderOptions) {
return (function () {
var bundleExpression = arguments[0];
var bundleDestination = arguments[1];
var bundleFormat = arguments[2].format;
bundleDestination = arguments[1] = getDestinationPath(bundleDestination, bundleFormat);
return builder[bundleMethod].apply(builder, arguments)
.then(function () {
return logCreatedMessage(bundleExpression, bundleDestination, bundleFormat);
})
.catch(function (error) {
throw new Error(error);
});
})(expression, destination, builderOptions);
}
/**
* @param sources {String} glob
* @returns {Promise}
*/
function bundle(sources) {
return new Promise(function (resolve, reject) {
glob(sources, {'cwd': options.base}, function (error, files) {
if (error) {
reject(error);
}
return resolve(Promise.all(files.map(createBundles)));
});
});
}
/**
* @param config
*/
function setBuilderConfig(config) {
if (!configSet) {
builder.config(config);
configSet = true;
}
}
/**
* @param config {Object}
* @returns {Object}
*/
function setApplicationConfig(config) {
config.map.config = options.application;
return config;
}
return function task(sources) {
return fs.readFile(configPath)
.then(function (data) {
var code = data.toString();
var script = new vm.Script(code, {filename: configPath});
script.runInNewContext(sandbox);
fileConfig = setApplicationConfig(fileConfig);
return Object.assign(fileConfig, configOverride);
})
.then(function (config) {
setBuilderConfig(config);
return bundle(sources);
})
.catch(function (error) {
throw new Error(error);
});
};
};