jspm
Version:
Registry and format agnostic JavaScript package manager
249 lines (200 loc) • 6.88 kB
JavaScript
/*
* Copyright 2014-2015 Guy Bedford (http://guybedford.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var ui = require('./ui');
var path = require('path');
var config = require('./config');
var SystemJSBuilder = require('systemjs-builder');
var fs = require('fs');
var asp = require('rsvp').denodeify;
var extend = require('./common').extend;
var alphabetize = require('./common').alphabetize;
var toFileURL = require('./common').toFileURL;
// jspm version of builder ignores config, baseURL arguments
// just allows cfg object
function Builder(_config) {
config.loadSync();
SystemJSBuilder.call(this, toFileURL(config.pjson.baseURL));
var cfg = config.loader.getConfig();
if (cfg.depCache)
delete cfg.depCache;
if (cfg.bundles)
delete cfg.bundles;
if (cfg.baseURL)
delete cfg.baseURL;
this.config(cfg, true);
if (typeof _config == 'object')
this.config(_config, true);
}
Builder.prototype = Object.create(SystemJSBuilder.prototype);
// extend build functions with jspm 0.16 compatibility options
Builder.prototype.bundle = function(expressionOrTree, outFile, opts) {
if (outFile && typeof outFile === 'object') {
opts = outFile;
outFile = undefined;
}
opts = opts || {};
if (outFile)
opts.outFile = outFile;
if (!('normalize' in opts))
opts.normalize = true;
if (!('lowResSourceMaps' in opts))
opts.lowResSourceMaps = true;
var self = this;
return SystemJSBuilder.prototype.bundle.call(this, expressionOrTree, opts)
.then(function(output) {
// Add the bundle to config if the inject flag was given
if (opts.injectConfig && opts.outFile) {
// NB deprecate
output.bundleName = output.bundleName || self.getCanonicalName(toFileURL(path.resolve(opts.outFile)));
var bundleName = output.bundleName;
if (!config.loader.bundles)
config.loader.bundles = {};
config.loader.bundles[bundleName] = output.modules;
return config.save()
.then(function() {
return output;
});
}
return output;
});
};
Builder.prototype.buildStatic = function(expressionOrTree, outFile, opts) {
if (outFile && typeof outFile === 'object') {
opts = outFile;
outFile = undefined;
}
opts = opts || {};
if (outFile)
opts.outFile = outFile;
if (!('format' in opts))
opts.format = 'global';
if (!('lowResSourceMaps' in opts))
opts.lowResSourceMaps = true;
return SystemJSBuilder.prototype.buildStatic.call(this, expressionOrTree, opts);
};
exports.Builder = Builder;
exports.depCache = function(expression) {
var systemBuilder = new Builder();
expression = expression || config.loader.main;
ui.log('info', 'Injecting the traced dependency tree for `' + expression + '`...');
return systemBuilder.trace(expression)
.then(function(tree) {
logTree(tree);
var depCache = config.loader.depCache || {};
extend(depCache, systemBuilder.getDepCache(tree));
config.loader.depCache = depCache;
})
.then(config.save)
.then(function() {
ui.log('ok', 'Dependency tree injected');
})
.catch(function(e) {
ui.log('err', e.stack || e);
});
};
// options.inject, options.sourceMaps, options.minify
exports.bundle = function(moduleExpression, fileName, opts) {
var systemBuilder = new Builder();
opts = opts || {};
opts.normalize = true;
fileName = fileName || path.resolve(config.pjson.baseURL, 'build.js');
return Promise.resolve()
.then(function() {
if (!opts.sourceMaps)
return removeExistingSourceMap(fileName);
})
.then(function() {
ui.log('info', 'Building the bundle tree for `' + moduleExpression + '`...');
return systemBuilder.trace(moduleExpression);
})
.then(function(buildTree) {
logTree(buildTree);
return systemBuilder.bundle(buildTree, fileName, opts);
})
.then(function(output) {
if (opts.injectConfig)
ui.log('ok', '`' + output.bundleName + '` added to config bundles.');
logBuild(path.relative(process.cwd(), fileName), opts);
})
.catch(function(e) {
ui.log('err', e.stack || e);
throw e;
});
};
exports.unbundle = function() {
return config.load()
.then(function() {
config.loader.bundles = {};
config.loader.depCache = {};
return config.save();
})
.then(function() {
ui.log('ok', 'Bundle configuration removed.');
});
};
function logBuild(outFile, opts) {
var resolution = opts.lowResSourceMaps ? '' : 'high-res ';
ui.log('ok', 'Built into `' + outFile + '`' +
(opts.sourceMaps ? ' with ' + resolution + 'source maps' : '') + ', ' +
(opts.minify ? '' : 'un') + 'minified' +
(opts.minify ? (opts.mangle ? ', ' : ', un') + 'mangled.' : '.'));
}
// options.minify, options.sourceMaps
exports.bundleSFX = function(expression, fileName, opts) {
var systemBuilder = new Builder();
opts = opts || {};
fileName = fileName || path.resolve(config.pjson.baseURL, 'build.js');
return Promise.resolve()
.then(function() {
if (!opts.sourceMaps)
return removeExistingSourceMap(fileName);
})
.then(function() {
ui.log('info', 'Building the single-file sfx bundle for `' + expression + '`...');
opts.format = opts.format || 'global';
return systemBuilder.buildStatic(expression, fileName, opts);
})
.then(function() {
logBuild(path.relative(process.cwd(), fileName), opts);
})
.catch(function(e) {
// catch sfx globals error to give a better error message
if (e.toString().indexOf('globalDeps option') != -1) {
var module = e.toString().match(/dependency "([^"]+)"/);
ui.log('err', 'SFX exclusion "' + module[1] + '" needs a reference.\nEither output an SFX module format like %--format amd% or map the module to an environment global via %--globals "{\'test.js\': \'test\'}"%.');
throw 'SFX Bundle input error';
}
ui.log('err', e.stack || e);
throw e;
});
};
function logTree(tree) {
ui.log('info', '');
tree = alphabetize(tree);
for (var name in tree) {
if (tree[name] && tree[name].metadata && tree[name].metadata.build !== false)
ui.log('info', ' `' + name + '`');
}
ui.log('info', '');
}
function removeExistingSourceMap(fileName) {
return asp(fs.unlink)(fileName + '.map')
.catch(function(e) {
if (e.code === 'ENOENT')
return;
throw e;
});
}