Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Sourcemaps #141

Closed
wants to merge 5 commits into
from
View
@@ -0,0 +1,30 @@
+var path = require('path')
+var fs = require('fs')
+var child_process = require('child_process')
+
+var supportpath = path.resolve(__dirname, '../support/')
+var jarpath = path.join(supportpath, 'closure.jar')
+var mappath = '_tmp_map' // Should probably make use of that generateTempFile function
+var cmd = 'java -jar "' + jarpath + '" --compilation_level SIMPLE_OPTIMIZATIONS --create_source_map ' + mappath + ' --source_map_format V3'
+
+// This needs some error handling and general cleanup
+function minify(source, cb) {
+ var child = child_process.exec(cmd), minified = ''
+
+ child.stdout.setEncoding('utf8')
+ child.stdout.on('data', function(data) {
+ minified += data
+ })
+
+ child.on('exit', function() {
+ fs.readFile(mappath, 'utf8', function(err, mapdata) {
+ fs.unlink(mappath, function () {
+ cb(null, minified, mapdata)
+ })
+ })
+ })
+
+ child.stdin.end(source);
+}
+
+exports.minify = minify;
View
@@ -32,7 +32,7 @@
var async = require('async')
, fs = require('fs')
- , minify = require('./minify')
+ , minify = require('./minify-closure')
, template = require('./template')
, argsParse = require('./args-parse')
, BuildParseError = require('./errors').BuildParseError
@@ -46,6 +46,10 @@ var async = require('async')
init: function (options) {
this.options = options
this.packages = []
+
+ this.asString = this.asString.bind(this) // Are these needed?
+ this.sources = this.sources.bind(this)
+
return this
}
@@ -84,6 +88,26 @@ var async = require('async')
}
)
}
+
+ , sources: function (callback) {
+ async.map(
+ this.packages
+ , function (srcPackage, callback) {
+ srcPackage.sources(callback)
+ }
+ , function (err, sources) {
+ if (err) return callback(err)
+
+ var glom = {}
+ sources.forEach(function (v) {
+ for(var file in v) {
+ glom[file] = v[file]
+ }
+ })
+ callback(null, glom)
+ }
+ )
+ }
}
// a utility static method to partially read an ender build file and parse the head comment
@@ -54,9 +54,10 @@ var fs = require('fs')
}
// utility to read multiple files in order and append them
- , loadFiles = function (root, files, callback) {
+ , loadFiles = function (files, callback) {
if (!Array.isArray(files)) files = [ files ]
if (!files.length || (files.length == 1 && files[0] == 'noop')) return callback()
+ var root = this.rootPath
collectFiles(root, files, function (err, files) {
// read each source file in parallel and assemble them together
View
@@ -49,6 +49,7 @@ var fs = require('fs')
template.generateSource(templateFiles[root ? 'root' : 'standard'], data, callback)
}
+ , INDENT_STR = ' '
, indent = function (str) {
// was this: return str.replace(/^(?!\s*$)/gm, ' ')
// but in some odd cases ^ was matching other things and inserting ' ' in unhelpful places
@@ -58,7 +59,7 @@ var fs = require('fs')
// which starts with: // If the space parameter...
// and gets converted to: / / If the space parameter
return str.split('\n').map(function (line) {
- return (/^\s*$/).test(line) ? line : (' ' + line)
+ return (/^\s*$/).test(line) ? line : (INDENT_STR + line)
}).join('\n')
}
@@ -94,10 +95,12 @@ var fs = require('fs')
this.isRoot = isRoot
this.packageName = packageName
this.packageJSON = packageJSON
+ this.rootPath = packageUtil.getPackageRoot(this.parents, this.packageName) // Moved to init since it gets used more than once
// custom hasher function for async.memoize so we have a single key, default will use
// first arg (callback) as hash key which won't work
this.asString = async.memoize(this.asString.bind(this), function () { return '_' })
+ this.sources = this.sources.bind(this)
return this
}
@@ -110,8 +113,7 @@ var fs = require('fs')
// note that "main" and "ender" are processed in the same way so they can both be just
// a string pointing to a source file or an array of source files that are concatenated
// or be left unspecified
- var root = packageUtil.getPackageRoot(this.parents, this.packageName)
- , mainSources = this.packageJSON.main || []
+ var mainSources = this.packageJSON.main || []
, enderBridgeSources = this.packageJSON.ender || []
, handleSourceData = function (err, sources) {
@@ -132,14 +134,35 @@ var fs = require('fs')
}.bind(this)
, sourceLoaders = {
- main: sourcePackageUtil.loadFiles.bind(this, root, mainSources)
- , ender: sourcePackageUtil.loadFiles.bind(this, root, enderBridgeSources)
+ main: sourcePackageUtil.loadFiles.bind(this, mainSources)
+ , ender: sourcePackageUtil.loadFiles.bind(this, enderBridgeSources)
}
async.parallel(sourceLoaders, handleSourceData)
}
+
+ , sources: function (callback) {
+ async.parallel({
+ main: sourcePackageUtil.loadFiles.bind(this, this.packageJSON.main || [])
+ , ender: sourcePackageUtil.loadFiles.bind(this, this.packageJSON.ender || [])
+ }, function (err, sources) {
+ if (err) return callback(err)
+
+ var map = {}
+ function toMap(source) {
+ map[path.relative('.', source.file)] = source.contents
+ }
+
+ sources.main && sources.main.forEach(toMap)
+ sources.ender && sources.ender.forEach(toMap)
+
+ callback(err, map)
+ }.bind(this))
+ }
}
module.exports.create = function (packageName, parents, isRoot, packageJSON, options) {
return Object.create(SourcePackage).init(packageName, parents, isRoot, packageJSON, options)
-}
+}
+exports.indent = indent;
+exports.INDENT_STR = INDENT_STR;
View
@@ -0,0 +1,180 @@
+var SourcePackage = require('./source-package');
+
+function SourceMap(data) {
+ data = JSON.parse(data);
+ this.sources = data.sources;
+ this.names = data.names;
+ this.version = data.version;
+ this.sourceRoot = data.sourceRoot;
+ this.file = data.file;
+
+ var prev = [0, 0, 0, 0, 0];
+ this.groups = data.mappings.split(';').map(function(group) {
+ prev[0] = 0;
+ return group.split(',').map(function(segment) {
+ segment = decodeVLQStr(segment);
+
+ segment = segment.map(function(v, i) { // Stabby function would really help here
+ return v + prev[i];
+ });
+ prev = prev.map(function(v, i) {
+ v = segment[i];
+ return (v != null && !isNaN(v)) ? v : prev[i];
+ });
+
+ var r = {};
+ for(var i = 0; i < segment.length; i++) {
+ r['field' + (i + 1)] = segment[i];
+ }
+ return r;
+ });
+ });
+}
+SourceMap.buildSourceMap = function(options, plainSource, sources, orgMap) {
+ var map = {}
+ for(var filepath in sources) {
+ var source = sources[filepath], cols = 0
+ if (!options.noop) { // This may not work if the template changes. Solutions?
+ source = SourcePackage.indent(source)
+ cols = SourcePackage.INDENT_STR.length
+ }
+
+ var start = plainSource.indexOf(source)
+ var offset = plainSource.substr(0, start).split('\n').length - 1
+ map[filepath] = {offsetTop: offset, offsetLeft: cols, lines: source.split('\n').length}
+ }
+
+ var sm = new SourceMap(orgMap)
+ sm.translate(map)
+ return sm.serialize()
+}
+SourceMap.prototype = {
+ translate: function(offsetMap) {
+ var sources = this.sources = Object.keys(offsetMap);
+ var groups = this.groups;
+ var self = this;
+
+ sources.forEach(function(sourceName, sourceIndex) {
+ var source = offsetMap[sourceName];
+ var offsetTop = source.offsetTop;
+ var offsetLeft = source.offsetLeft;
+ var lines = source.lines;
+
+ groups.forEach(function(segments, minline) {
+ segments.forEach(function(segment) {
+ if(segment.field3 == null) {
+ return;
+ }
+ var tmp = segment.field3 - offsetTop;
+ if(tmp >= 0 && tmp < lines) {
+ //console.log(sourceName, offsetTop, lines, tmp + ' <- ' + segment.field3, segment.field4 - offsetLeft + ' <- ' + segment.field4, (self.names[segment.field5] || segment.field5));
+ segment.field3 = tmp;
+ segment.field2 = sourceIndex;
+ segment.field4 -= offsetLeft;
+ }
+ });
+ });
+ });
+ },
+ serialize: function() {
+ var prev = [0, 0, 0, 0, 0];
+ var mappings = this.groups.map(function(group) {
+ prev[0] = 0;
+ return group.map(function(segment) {
+ var r = [];
+ var len = Object.keys(segment).length;
+ for(var i = 0; i < len; i++) {
+ r.push(segment['field' + (i + 1)]);
+ }
+ segment = r;
+
+ var saved = segment;
+ segment = segment.map(function(v, i) {
+ return v - prev[i];
+ });
+ prev = saved.map(function(v, i) {
+ return (v != null && !isNaN(v)) ? v : prev[i];
+ });
+
+ return encodeVLQArr(segment);
+ }).join(',');
+ }).join(';');
+
+ return JSON.stringify({
+ version: this.version,
+ file: this.file,
+ sourceRoot: this.sourceRoot,
+ sources: this.sources,
+ names: this.names,
+ mappings: mappings
+ });
+ }
+}
+
+
+var iToB = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+var bToI = {};
+iToB.forEach(function(v, i) {
+ bToI[v] = i;
+});
+
+function encodeVLQArr(values) {
+ return values.map(function(v) {
+ return encodeVLQ(v);
+ }).join('');
+}
+function encodeVLQ(aValue) {
+ var encoded = '';
+ var vlq = toVLQSigned(aValue);
+ var sign = aValue < 0 ? 1 : 0;
+
+ do {
+ var digit = vlq & 31; // Mask to first 5 bits
+ vlq >>>= 5; // Remove the first 5 bits
+ if(vlq > 0) {
+ digit |= 32; // Set sixth bit
+ }
+ encoded += iToB[digit];
+ } while(vlq > 0);
+
+ return encoded;
+}
+function toVLQSigned(aValue) {
+ return aValue < 0 ? ((-aValue) << 1) | 1 : (aValue << 1);
+}
+
+function decodeVLQStr(str) {
+ var r = [];
+ while(str.length > 0) {
+ var t = decodeVLQ(str);
+ r.push(t.value);
+ str = t.rest;
+ }
+ return r;
+}
+function decodeVLQ(aStr) {
+ var i = 0;
+ var result = 0;
+ var shift = 0;
+ var continuation;
+
+ do {
+ if(i >= aStr.length) {
+ throw new Error('Expected more digits in base 64 VLQ value.');
+ }
+
+ var digit = bToI[aStr[i++]];
+ continuation = digit >>> 5;
+ digit &= 31;
+ result |= digit << shift;
+ shift += 5;
+ } while(continuation);
+
+ return {value: fromVLQSigned(result), rest: aStr.slice(i)};
+}
+function fromVLQSigned(aValue) {
+ var shifted = aValue >>> 1;
+ return (aValue & 1) ? -shifted : shifted;
+}
+
+module.exports = SourceMap;
Oops, something went wrong.