systemjs-builder
Version:
SystemJS Build Tool
179 lines (145 loc) • 5.42 kB
JavaScript
var path = require('path');
var mkdirp = require('mkdirp');
var fs = require('fs');
var Promise = require('bluebird');
var asp = require('bluebird').promisify;
var extend = require('./utils').extend;
var terser = require('terser');
var fromFileURL = require('./utils').fromFileURL;
var toFileURL = require('./utils').toFileURL;
// Ugly hack to get around terser's strange code loading and export system
// (inherited from uglify-js), and the fact that their SourceMap wrapper is
// not exported
var TerserSourceMap = new Function('MOZ_SourceMap', "exports", "require", function() {
var code = ['terser/lib/utils.js', 'terser/lib/sourcemap.js'].map(function(file) {
return fs.readFileSync(require.resolve(file), 'utf8');
});
code.push('return SourceMap;');
return code.join('\n');
}())(require('source-map'), terser, require);
function countLines(str) {
return str.split(/\r\n|\r|\n/).length;
}
// Process compiler outputs, gathering:
//
// concatOutputs: list of source strings to concatenate
// sourceMapsWithOffsets: list of [absolute offset,
// source map string] pairs
//
// Takes lists as empty references and populates via push.
function processOutputs(outputs) {
var removeSourceMaps = require('./sourcemaps').removeSourceMaps;
var offset = 0;
var outputObj = {};
var sources = outputObj.sources = [];
var sourceMapsWithOffsets = outputObj.sourceMapsWithOffsets = [];
outputs.forEach(function(output) {
var source;
if (typeof output == 'object') {
source = output.source || '';
var offset_ = output.sourceMapOffset || 0;
var map = output.sourceMap;
if (map) {
sourceMapsWithOffsets.push([offset + offset_, map]);
}
}
// NB perhaps we should enforce output is always an object down the chain?
else if (typeof output == 'string') {
source = output;
}
else {
var format = output ? output.toString() : typeof output;
throw new Error("Unexpected output format: " + format);
}
source = removeSourceMaps(source || '');
offset += countLines(source);
sources.push(source);
});
return outputObj;
}
function createOutput(outFile, outputs, basePath, sourceMaps, sourceMapContents) {
var concatenateSourceMaps = require('./sourcemaps').concatenateSourceMaps;
var outputObj = processOutputs(outputs);
if (sourceMaps)
var sourceMap = concatenateSourceMaps(outFile, outputObj.sourceMapsWithOffsets, basePath, sourceMapContents);
var output = outputObj.sources.join('\n');
return {
source: output,
sourceMap: sourceMap
};
}
function minify(output, fileName, mangle, uglifyOpts) {
var files = {};
files[fileName] = output.source;
var result;
try{
result = terser.minify(files, {
parse: {},
compress: uglifyOpts.compress,
mangle: mangle,
output: { ast: true, code: false }
});
} catch(e){
throw new Error(e);
}
if (result.error) {
throw new Error(result.error);
}
var ast = result.ast;
var sourceMap;
if (output.sourceMap) {
if (typeof output.sourceMap === 'string')
output.sourceMap = JSON.parse(output.sourceMap);
sourceMap = TerserSourceMap({
file: fileName,
orig: output.sourceMap
});
}
var outputOptions = uglifyOpts.beautify;
// keep first comment
outputOptions.comments = outputOptions.comments || function(node, comment) {
return comment.line === 1 && comment.col === 0;
};
outputOptions.source_map = sourceMap;
output.source = ast.print_to_string(outputOptions);
output.sourceMap = sourceMap;
return output;
}
function writeOutputFile(outFile, source, sourceMap) {
var outDir = path.dirname(outFile);
return asp(mkdirp)(path.dirname(outFile))
.then(function() {
if (!sourceMap)
return;
var sourceMapFileName = path.basename(outFile) + '.map';
source += '\n//# sourceMappingURL=' + sourceMapFileName;
return asp(fs.writeFile)(path.resolve(outDir, sourceMapFileName), sourceMap);
})
.then(function() {
return asp(fs.writeFile)(outFile, source);
});
}
exports.inlineSourceMap = inlineSourceMap;
function inlineSourceMap(source, sourceMap) {
if (!sourceMap)
throw new Error('NOTHING TO INLINE');
return source + '\n//# sourceMappingURL=data:application/json;base64,'
+ new Buffer(sourceMap.toString()).toString('base64');
}
exports.writeOutputs = function(outputs, baseURL, outputOpts) {
var outFile = outputOpts.outFile && path.resolve(outputOpts.outFile);
var basePath = fromFileURL(baseURL);
var fileName = outFile && path.basename(outFile) || 'output.js';
var output = createOutput(outFile || path.resolve(basePath, fileName), outputs, basePath, outputOpts.sourceMaps, outputOpts.sourceMapContents);
if (outputOpts.minify)
output = minify(output, fileName, outputOpts.mangle, outputOpts.uglify);
if (outputOpts.sourceMaps == 'inline') {
output.source = inlineSourceMap(output.source, output.sourceMap);
output.sourceMap = undefined;
}
if (!outputOpts.outFile)
return Promise.resolve(output);
return writeOutputFile(outFile, output.source, output.sourceMap).then(function() {
return output;
});
};