UNPKG

systemjs-builder

Version:
179 lines (145 loc) 5.42 kB
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; }); };