Permalink
Browse files

Add `--source-map` build option. [closes #161]

  • Loading branch information...
1 parent 74176a5 commit 929d76eb19280bdadd890a97b03999c4b4ae5632 @jdalton jdalton committed Jan 18, 2013
Showing with 134 additions and 50 deletions.
  1. +8 −7 README.md
  2. +41 −20 build.js
  3. +84 −22 build/minify.js
  4. +1 −1 build/post-compile.js
View
@@ -154,13 +154,14 @@ Unless specified by `-o` or `--output`, all files created are saved to the curre
The following options are also supported:
- * `-c`, `--stdout`     Write output to standard output
- * `-d`, `--debug`       Write only the debug output
- * `-h`, `--help`         Display help information
- * `-m`, `--minify`     Write only the minified output
- * `-o`, `--output`     Write output to a given path/filename
- * `-s`, `--silent`     Skip status updates normally logged to the console
- * `-V`, `--version`   Output current version of Lo-Dash
+ * `-c`, `--stdout`          Write output to standard output
+ * `-d`, `--debug`            Write only the debug output
+ * `-h`, `--help`              Display help information
+ * `-m`, `--minify`          Write only the minified output
+ * `-o`, `--output`          Write output to a given path/filename
+ * `-p`, `--source-map`   Generate a source map for the minified output
+ * `-s`, `--silent`          Skip status updates normally logged to the console
+ * `-V`, `--version`        Output current version of Lo-Dash
@mathiasbynens

mathiasbynens Jan 18, 2013

Owner

Why not use a table for this? Cleaner than having to           all over the place, and much easier to maintain.

@jdalton

jdalton Jan 18, 2013

Owner

Yap, but I didn't like the look of the table, and considering most people view the readme through github/npm/jam repos the look is more important than the markup markdown.

@leeoniya

leeoniya Jan 18, 2013

what about just <pre>-wrapping it then and using normal spaces?

@jdalton

jdalton Jan 18, 2013

Owner

Ya, good point. Will do.

@jdalton

jdalton Jan 18, 2013

Owner

@leeoniya Drat. I tried it and that makes it look like it's wrapped in a generic code block, so the content loses its styling.

@jdalton

jdalton Jan 18, 2013

Owner

@leeoniya That's great! @mathiasbynens if you've got some time to spare could you update with that?

@leeoniya

leeoniya Jan 18, 2013

it sucks you gotta choose whether to cater to fixed or variable-width display fonts, i'd prefer the former but ultimately it boils down to the larger audience.

The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`).
View
@@ -580,13 +580,14 @@
'',
' Options:',
'',
- ' -c, --stdout Write output to standard output',
- ' -d, --debug Write only the debug output',
- ' -h, --help Display help information',
- ' -m, --minify Write only the minified output',
- ' -o, --output Write output to a given path/filename',
- ' -s, --silent Skip status updates normally logged to the console',
- ' -V, --version Output current version of Lo-Dash',
+ ' -c, --stdout Write output to standard output',
+ ' -d, --debug Write only the debug output',
+ ' -h, --help Display help information',
+ ' -m, --minify Write only the minified output',
+ ' -o, --output Write output to a given path/filename',
+ ' -p, --source-map Generate a source map for the minified output',
+ ' -s, --silent Skip status updates normally logged to the console',
+ ' -V, --version Output current version of Lo-Dash',
''
].join('\n'));
}
@@ -1182,7 +1183,7 @@
// used to report invalid command-line arguments
var invalidArgs = _.reject(options.slice(options[0] == 'node' ? 2 : 0), function(value, index, options) {
if (/^(?:-o|--output)$/.test(options[index - 1]) ||
- /^(?:category|exclude|exports|iife|include|moduleId|minus|plus|settings|template)=.*$/i.test(value)) {
+ /^(?:category|exclude|exports|iife|include|moduleId|minus|plus|settings|template)=.*$/.test(value)) {
return true;
}
return [
@@ -1198,6 +1199,7 @@
'-h', '--help',
'-m', '--minify',
'-o', '--output',
+ '-p', '--source-map',
'-s', '--silent',
'-V', '--version'
].indexOf(value) > -1;
@@ -1241,6 +1243,9 @@
return match ? match[1] : result;
}, null);
+ // the path to the source file
+ var filePath = path.join(__dirname, 'lodash.js');
+
// flag used to specify a Backbone build
var isBackbone = options.indexOf('backbone') > -1;
@@ -1256,8 +1261,11 @@
// flag used to specify an Underscore build
var isUnderscore = options.indexOf('underscore') > -1;
+ // flag used to specify creating a source map for the minified source
+ var isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1;
+
// flag used to specify only creating the minified build
- var isMinify = !isDebug && options.indexOf('-m') > -1 || options.indexOf('--minify')> -1;
+ var isMinify = options.indexOf('-m') > -1 || options.indexOf('--minify') > -1;
// flag used to specify a mobile build
var isMobile = !isLegacy && (isCSP || isUnderscore || options.indexOf('mobile') > -1);
@@ -1325,7 +1333,7 @@
var isTemplate = !!templatePattern;
// the lodash.js source
- var source = fs.readFileSync(path.join(__dirname, 'lodash.js'), 'utf8');
+ var source = fs.readFileSync(filePath, 'utf8');
// flag used to specify replacing Lo-Dash's `_.clone` with Underscore's
var useUnderscoreClone = isUnderscore;
@@ -2123,7 +2131,7 @@
/*------------------------------------------------------------------------*/
// used to specify creating a custom build
- var isCustom = isBackbone || isLegacy || isMobile || isStrict || isUnderscore ||
+ var isCustom = isBackbone || isLegacy || isMapped || isMobile || isStrict || isUnderscore ||
/(?:category|exclude|exports|iife|include|minus|plus)=/.test(options) ||
!_.isEqual(exportsOptions, exportsAll);
@@ -2144,30 +2152,35 @@
stdout.write(debugSource);
callback(debugSource);
} else if (!isStdOut) {
- callback(debugSource, (isDebug && outputPath) || path.join(cwd, basename + '.js'));
+ callback({
+ 'source': debugSource,
+ 'outputPath': (isDebug && outputPath) || path.join(cwd, basename + '.js')
+ });
}
}
// begin the minification process
if (!isDebug) {
outputPath || (outputPath = path.join(cwd, basename + '.min.js'));
minify(source, {
+ 'filePath': filePath,
+ 'isMapped': isMapped,
'isSilent': isSilent,
'isTemplate': isTemplate,
'outputPath': outputPath,
- 'onComplete': function(source) {
+ 'onComplete': function(data) {
// inject "use strict" directive
if (isStrict) {
- source = source.replace(/^([\s\S]*?function[^{]+{)([^"'])/, '$1"use strict";$2');
+ data.source = data.source.replace(/^([\s\S]*?function[^{]+{)([^"'])/, '$1"use strict";$2');
}
if (isCustom) {
- source = addCommandsToHeader(source, options);
+ data.source = addCommandsToHeader(data.source, options);
}
if (isStdOut) {
- stdout.write(source);
- callback(source);
+ stdout.write(data.source);
+ callback(data);
} else {
- callback(source, outputPath);
+ callback(data);
}
}
});
@@ -2182,8 +2195,16 @@
}
else {
// or invoked directly
- build(process.argv, function(source, filePath) {
- filePath && fs.writeFileSync(filePath, source, 'utf8');
+ build(process.argv, function(data) {
+ var outputPath = data.outputPath,
+ sourceMap = data.sourceMap;
+
+ if (outputPath) {
+ fs.writeFileSync(outputPath, data.source, 'utf8');
+ if (sourceMap) {
+ fs.writeFileSync(path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map'), sourceMap, 'utf8');
+ }
+ }
});
}
}());
View
@@ -89,6 +89,7 @@
options = source;
var filePath = options[options.length - 1],
+ isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1,
isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1,
isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1,
outputPath = path.join(path.dirname(filePath), path.basename(filePath, '.js') + '.min.js');
@@ -102,6 +103,8 @@
}, outputPath);
options = {
+ 'filePath': filePath,
+ 'isMapped': isMapped,
'isSilent': isSilent,
'isTemplate': isTemplate,
'outputPath': outputPath
@@ -157,15 +160,23 @@
this.hybrid = { 'simple': {}, 'advanced': {} };
this.uglified = {};
+ this.filePath = options.filePath;
+ this.isMapped = !!options.isMapped;
this.isSilent = !!options.isSilent;
this.isTemplate = !!options.isTemplate;
this.outputPath = options.outputPath;
source = preprocess(source, options);
this.source = source;
- this.onComplete = options.onComplete || function(source) {
- fs.writeFileSync(this.outputPath, source, 'utf8');
+ this.onComplete = options.onComplete || function(data) {
+ var outputPath = this.outputPath,
+ sourceMap = data.sourceMap;
+
+ fs.writeFileSync(outputPath, data.source, 'utf8');
+ if (sourceMap) {
+ fs.writeFileSync(getMapPath(outputPath), sourceMap, 'utf8');
+ }
};
// begin the minification process
@@ -182,8 +193,7 @@
* @private
* @param {Object} options The options object.
* id - The Git object ID of the `.tar.gz` file.
- * onComplete - The function, invoked with one argument (exception),
- * called once the extraction has finished.
+ * onComplete - The function called once the extraction has finished.
* path - The path of the extraction directory.
* title - The dependency's title used in status updates logged to the console.
*/
@@ -242,6 +252,17 @@
});
}
+ /**
+ * Resolves the source map path from the given output path.
+ *
+ * @private
+ * @param {String} outputPath The output path.
+ * @returns {String} Returns the source map path.
+ */
+ function getMapPath(outputPath) {
+ return path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map');
+ }
+
/*--------------------------------------------------------------------------*/
/**
@@ -254,17 +275,26 @@
* @param {Function} callback The function called once the process has completed.
*/
function closureCompile(source, mode, callback) {
+ var filePath = this.filePath,
+ outputPath = this.outputPath,
+ isMapped = this.isMapped,
+ mapPath = getMapPath(outputPath),
+ options = closureOptions.slice();
+
// use simple optimizations when minifying template files
- var options = closureOptions.slice();
options.push('--compilation_level=' + optimizationModes[this.isTemplate ? 'simple' : mode]);
+ if (isMapped) {
+ options.push('--create_source_map=' + mapPath, '--source_map_format=V3');
+ }
+
// the standard error stream, standard output stream, and the Closure Compiler process
var error = '',
output = '',
compiler = spawn('java', ['-jar', closurePath].concat(options));
if (!this.isSilent) {
- console.log('Compressing ' + path.basename(this.outputPath, '.js') + ' using the Closure Compiler (' + mode + ')...');
+ console.log('Compressing ' + path.basename(outputPath, '.js') + ' using the Closure Compiler (' + mode + ')...');
}
compiler.stdout.on('data', function(data) {
// append the data to the output stream
@@ -282,7 +312,18 @@
var exception = new Error(error);
exception.status = status;
}
- callback(exception, output);
+ if (isMapped) {
+ var mapOutput = fs.readFileSync(mapPath, 'utf8');
+ fs.unlinkSync(mapPath);
+
+ output = output
+ .replace(/[\s;]*$/, '\n//@ sourceMappingURL=' + path.basename(mapPath));
+
+ mapOutput = mapOutput
+ .replace(/("file":)""/, '$1"' + path.basename(outputPath) + '"')
+ .replace(/("sources":)\["stdin"\]/, '$1["' + path.basename(filePath) + '"]');
+ }
+ callback(exception, output, mapOutput);
});
// proxy the standard input to the Closure Compiler
@@ -350,13 +391,17 @@
* @private
* @param {Object|Undefined} exception The error object.
* @param {String} result The resulting minified source.
+ * @param {String} map The source map output.
*/
- function onClosureSimpleCompile(exception, result) {
+ function onClosureSimpleCompile(exception, result, map) {
if (exception) {
throw exception;
}
result = postprocess(result);
- this.compiled.simple.source = result;
+
+ var simple = this.compiled.simple;
+ simple.source = result;
+ simple.sourceMap = map;
zlib.gzip(result, onClosureSimpleGzip.bind(this));
}
@@ -386,13 +431,17 @@
* @private
* @param {Object|Undefined} exception The error object.
* @param {String} result The resulting minified source.
+ * @param {String} map The source map output.
*/
- function onClosureAdvancedCompile(exception, result) {
+ function onClosureAdvancedCompile(exception, result, map) {
if (exception) {
throw exception;
}
result = postprocess(result);
- this.compiled.advanced.source = result;
+
+ var advanced = this.compiled.advanced;
+ advanced.source = result;
+ advanced.sourceMap = map;
zlib.gzip(result, onClosureAdvancedGzip.bind(this));
}
@@ -412,8 +461,14 @@
}
this.compiled.advanced.gzip = result;
- // next, minify the source using only UglifyJS
- uglify.call(this, this.source, 'UglifyJS', onUglify.bind(this));
+ // if mapped, finish by choosing the smallest compressed file
+ if (this.isMapped) {
+ onComplete.call(this);
+ }
+ // else, minify the source using UglifyJS
+ else {
+ uglify.call(this, this.source, 'UglifyJS', onUglify.bind(this));
+ }
}
/**
@@ -538,18 +593,25 @@
// select the smallest gzipped file and use its minified counterpart as the
// official minified release (ties go to the Closure Compiler)
- var min = Math.min(
- compiledSimple.gzip.length,
- compiledAdvanced.gzip.length,
- uglified.gzip.length,
- hybridSimple.gzip.length,
- hybridAdvanced.gzip.length
- );
+ var min = this.isMapped
+ ? Math.min(
+ compiledSimple.gzip.length,
+ compiledAdvanced.gzip.length
+ )
+ : Math.min(
+ compiledSimple.gzip.length,
+ compiledAdvanced.gzip.length,
+ uglified.gzip.length,
+ hybridSimple.gzip.length,
+ hybridAdvanced.gzip.length
+ );
// pass the minified source to the "onComplete" callback
[compiledSimple, compiledAdvanced, uglified, hybridSimple, hybridAdvanced].some(function(data) {
- if (data.gzip.length == min) {
- this.onComplete(data.source);
+ var gzip = data.gzip;
+ if (gzip && gzip.length == min) {
+ data.outputPath = this.outputPath;
+ this.onComplete(data);
}
}, this);
}
View
@@ -49,7 +49,7 @@
// add trailing semicolon
if (source) {
- source = source.replace(/[\s;]*$/, ';');
+ source = source.replace(/[\s;]*(\n\/\/.+)?$/, ';$1');
}
// exit early if version snippet isn't found
var snippet = /VERSION\s*[=:]\s*([\'"])(.*?)\1/.exec(source);

0 comments on commit 929d76e

Please sign in to comment.