Skip to content
This repository
Browse code

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

  • Loading branch information...
commit 929d76eb19280bdadd890a97b03999c4b4ae5632 1 parent 74176a5
John-David Dalton authored
15  README.md
Source Rendered
@@ -154,13 +154,14 @@ Unless specified by `-o` or `--output`, all files created are saved to the curre
154 154
 
155 155
 The following options are also supported:
156 156
 
157  
- * `-c`, `--stdout`     Write output to standard output
158  
- * `-d`, `--debug`       Write only the debug output
159  
- * `-h`, `--help`         Display help information
160  
- * `-m`, `--minify`     Write only the minified output
161  
- * `-o`, `--output`     Write output to a given path/filename
162  
- * `-s`, `--silent`     Skip status updates normally logged to the console
163  
- * `-V`, `--version`   Output current version of Lo-Dash
  157
+ * `-c`, `--stdout`          Write output to standard output
  158
+ * `-d`, `--debug`            Write only the debug output
  159
+ * `-h`, `--help`              Display help information
  160
+ * `-m`, `--minify`          Write only the minified output
  161
+ * `-o`, `--output`          Write output to a given path/filename
  162
+ * `-p`, `--source-map`   Generate a source map for the minified output
  163
+ * `-s`, `--silent`          Skip status updates normally logged to the console
  164
+ * `-V`, `--version`        Output current version of Lo-Dash
164 165
 
165 166
 The `lodash` command-line utility is available when Lo-Dash is installed as a global package (i.e. `npm install -g lodash`).
166 167
 
61  build.js
@@ -580,13 +580,14 @@
580 580
       '',
581 581
       '  Options:',
582 582
       '',
583  
-      '    -c, --stdout   Write output to standard output',
584  
-      '    -d, --debug    Write only the debug output',
585  
-      '    -h, --help     Display help information',
586  
-      '    -m, --minify   Write only the minified output',
587  
-      '    -o, --output   Write output to a given path/filename',
588  
-      '    -s, --silent   Skip status updates normally logged to the console',
589  
-      '    -V, --version  Output current version of Lo-Dash',
  583
+      '    -c, --stdout      Write output to standard output',
  584
+      '    -d, --debug       Write only the debug output',
  585
+      '    -h, --help        Display help information',
  586
+      '    -m, --minify      Write only the minified output',
  587
+      '    -o, --output      Write output to a given path/filename',
  588
+      '    -p, --source-map  Generate a source map for the minified output',
  589
+      '    -s, --silent      Skip status updates normally logged to the console',
  590
+      '    -V, --version     Output current version of Lo-Dash',
590 591
       ''
591 592
     ].join('\n'));
592 593
   }
@@ -1182,7 +1183,7 @@
1182 1183
     // used to report invalid command-line arguments
1183 1184
     var invalidArgs = _.reject(options.slice(options[0] == 'node' ? 2 : 0), function(value, index, options) {
1184 1185
       if (/^(?:-o|--output)$/.test(options[index - 1]) ||
1185  
-          /^(?:category|exclude|exports|iife|include|moduleId|minus|plus|settings|template)=.*$/i.test(value)) {
  1186
+          /^(?:category|exclude|exports|iife|include|moduleId|minus|plus|settings|template)=.*$/.test(value)) {
1186 1187
         return true;
1187 1188
       }
1188 1189
       return [
@@ -1198,6 +1199,7 @@
1198 1199
         '-h', '--help',
1199 1200
         '-m', '--minify',
1200 1201
         '-o', '--output',
  1202
+        '-p', '--source-map',
1201 1203
         '-s', '--silent',
1202 1204
         '-V', '--version'
1203 1205
       ].indexOf(value) > -1;
@@ -1241,6 +1243,9 @@
1241 1243
       return match ? match[1] : result;
1242 1244
     }, null);
1243 1245
 
  1246
+    // the path to the source file
  1247
+    var filePath = path.join(__dirname, 'lodash.js');
  1248
+
1244 1249
     // flag used to specify a Backbone build
1245 1250
     var isBackbone = options.indexOf('backbone') > -1;
1246 1251
 
@@ -1256,8 +1261,11 @@
1256 1261
     // flag used to specify an Underscore build
1257 1262
     var isUnderscore = options.indexOf('underscore') > -1;
1258 1263
 
  1264
+    // flag used to specify creating a source map for the minified source
  1265
+    var isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1;
  1266
+
1259 1267
     // flag used to specify only creating the minified build
1260  
-    var isMinify = !isDebug && options.indexOf('-m') > -1 || options.indexOf('--minify')> -1;
  1268
+    var isMinify = options.indexOf('-m') > -1 || options.indexOf('--minify') > -1;
1261 1269
 
1262 1270
     // flag used to specify a mobile build
1263 1271
     var isMobile = !isLegacy && (isCSP || isUnderscore || options.indexOf('mobile') > -1);
@@ -1325,7 +1333,7 @@
1325 1333
     var isTemplate = !!templatePattern;
1326 1334
 
1327 1335
     // the lodash.js source
1328  
-    var source = fs.readFileSync(path.join(__dirname, 'lodash.js'), 'utf8');
  1336
+    var source = fs.readFileSync(filePath, 'utf8');
1329 1337
 
1330 1338
     // flag used to specify replacing Lo-Dash's `_.clone` with Underscore's
1331 1339
     var useUnderscoreClone = isUnderscore;
@@ -2123,7 +2131,7 @@
2123 2131
     /*------------------------------------------------------------------------*/
2124 2132
 
2125 2133
     // used to specify creating a custom build
2126  
-    var isCustom = isBackbone || isLegacy || isMobile || isStrict || isUnderscore ||
  2134
+    var isCustom = isBackbone || isLegacy || isMapped || isMobile || isStrict || isUnderscore ||
2127 2135
       /(?:category|exclude|exports|iife|include|minus|plus)=/.test(options) ||
2128 2136
       !_.isEqual(exportsOptions, exportsAll);
2129 2137
 
@@ -2144,7 +2152,10 @@
2144 2152
         stdout.write(debugSource);
2145 2153
         callback(debugSource);
2146 2154
       } else if (!isStdOut) {
2147  
-        callback(debugSource, (isDebug && outputPath) || path.join(cwd, basename + '.js'));
  2155
+        callback({
  2156
+          'source': debugSource,
  2157
+          'outputPath': (isDebug && outputPath) || path.join(cwd, basename + '.js')
  2158
+        });
2148 2159
       }
2149 2160
     }
2150 2161
     // begin the minification process
@@ -2152,22 +2163,24 @@
2152 2163
       outputPath || (outputPath = path.join(cwd, basename + '.min.js'));
2153 2164
 
2154 2165
       minify(source, {
  2166
+        'filePath': filePath,
  2167
+        'isMapped': isMapped,
2155 2168
         'isSilent': isSilent,
2156 2169
         'isTemplate': isTemplate,
2157 2170
         'outputPath': outputPath,
2158  
-        'onComplete': function(source) {
  2171
+        'onComplete': function(data) {
2159 2172
           // inject "use strict" directive
2160 2173
           if (isStrict) {
2161  
-            source = source.replace(/^([\s\S]*?function[^{]+{)([^"'])/, '$1"use strict";$2');
  2174
+            data.source = data.source.replace(/^([\s\S]*?function[^{]+{)([^"'])/, '$1"use strict";$2');
2162 2175
           }
2163 2176
           if (isCustom) {
2164  
-            source = addCommandsToHeader(source, options);
  2177
+            data.source = addCommandsToHeader(data.source, options);
2165 2178
           }
2166 2179
           if (isStdOut) {
2167  
-            stdout.write(source);
2168  
-            callback(source);
  2180
+            stdout.write(data.source);
  2181
+            callback(data);
2169 2182
           } else {
2170  
-            callback(source, outputPath);
  2183
+            callback(data);
2171 2184
           }
2172 2185
         }
2173 2186
       });
@@ -2182,8 +2195,16 @@
2182 2195
   }
2183 2196
   else {
2184 2197
     // or invoked directly
2185  
-    build(process.argv, function(source, filePath) {
2186  
-      filePath && fs.writeFileSync(filePath, source, 'utf8');
  2198
+    build(process.argv, function(data) {
  2199
+      var outputPath = data.outputPath,
  2200
+          sourceMap = data.sourceMap;
  2201
+
  2202
+      if (outputPath) {
  2203
+        fs.writeFileSync(outputPath, data.source, 'utf8');
  2204
+        if (sourceMap) {
  2205
+          fs.writeFileSync(path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map'), sourceMap, 'utf8');
  2206
+        }
  2207
+      }
2187 2208
     });
2188 2209
   }
2189 2210
 }());
106  build/minify.js
@@ -89,6 +89,7 @@
89 89
       options = source;
90 90
 
91 91
       var filePath = options[options.length - 1],
  92
+          isMapped = options.indexOf('-p') > -1 || options.indexOf('--source-map') > -1,
92 93
           isSilent = options.indexOf('-s') > -1 || options.indexOf('--silent') > -1,
93 94
           isTemplate = options.indexOf('-t') > -1 || options.indexOf('--template') > -1,
94 95
           outputPath = path.join(path.dirname(filePath), path.basename(filePath, '.js') + '.min.js');
@@ -102,6 +103,8 @@
102 103
       }, outputPath);
103 104
 
104 105
       options = {
  106
+        'filePath': filePath,
  107
+        'isMapped': isMapped,
105 108
         'isSilent': isSilent,
106 109
         'isTemplate': isTemplate,
107 110
         'outputPath': outputPath
@@ -157,6 +160,8 @@
157 160
     this.hybrid = { 'simple': {}, 'advanced': {} };
158 161
     this.uglified = {};
159 162
 
  163
+    this.filePath = options.filePath;
  164
+    this.isMapped = !!options.isMapped;
160 165
     this.isSilent = !!options.isSilent;
161 166
     this.isTemplate = !!options.isTemplate;
162 167
     this.outputPath = options.outputPath;
@@ -164,8 +169,14 @@
164 169
     source = preprocess(source, options);
165 170
     this.source = source;
166 171
 
167  
-    this.onComplete = options.onComplete || function(source) {
168  
-      fs.writeFileSync(this.outputPath, source, 'utf8');
  172
+    this.onComplete = options.onComplete || function(data) {
  173
+      var outputPath = this.outputPath,
  174
+          sourceMap = data.sourceMap;
  175
+
  176
+      fs.writeFileSync(outputPath, data.source, 'utf8');
  177
+      if (sourceMap) {
  178
+        fs.writeFileSync(getMapPath(outputPath), sourceMap, 'utf8');
  179
+      }
169 180
     };
170 181
 
171 182
     // begin the minification process
@@ -182,8 +193,7 @@
182 193
    * @private
183 194
    * @param {Object} options The options object.
184 195
    *  id - The Git object ID of the `.tar.gz` file.
185  
-   *  onComplete - The function, invoked with one argument (exception),
186  
-   *   called once the extraction has finished.
  196
+   *  onComplete - The function called once the extraction has finished.
187 197
    *  path - The path of the extraction directory.
188 198
    *  title - The dependency's title used in status updates logged to the console.
189 199
    */
@@ -242,6 +252,17 @@
242 252
     });
243 253
   }
244 254
 
  255
+  /**
  256
+   * Resolves the source map path from the given output path.
  257
+   *
  258
+   * @private
  259
+   * @param {String} outputPath The output path.
  260
+   * @returns {String} Returns the source map path.
  261
+   */
  262
+  function getMapPath(outputPath) {
  263
+    return path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map');
  264
+  }
  265
+
245 266
   /*--------------------------------------------------------------------------*/
246 267
 
247 268
   /**
@@ -254,17 +275,26 @@
254 275
    * @param {Function} callback The function called once the process has completed.
255 276
    */
256 277
   function closureCompile(source, mode, callback) {
  278
+    var filePath = this.filePath,
  279
+        outputPath = this.outputPath,
  280
+        isMapped = this.isMapped,
  281
+        mapPath = getMapPath(outputPath),
  282
+        options = closureOptions.slice();
  283
+
257 284
     // use simple optimizations when minifying template files
258  
-    var options = closureOptions.slice();
259 285
     options.push('--compilation_level=' + optimizationModes[this.isTemplate ? 'simple' : mode]);
260 286
 
  287
+    if (isMapped) {
  288
+      options.push('--create_source_map=' + mapPath, '--source_map_format=V3');
  289
+    }
  290
+
261 291
     // the standard error stream, standard output stream, and the Closure Compiler process
262 292
     var error = '',
263 293
         output = '',
264 294
         compiler = spawn('java', ['-jar', closurePath].concat(options));
265 295
 
266 296
     if (!this.isSilent) {
267  
-      console.log('Compressing ' + path.basename(this.outputPath, '.js') + ' using the Closure Compiler (' + mode + ')...');
  297
+      console.log('Compressing ' + path.basename(outputPath, '.js') + ' using the Closure Compiler (' + mode + ')...');
268 298
     }
269 299
     compiler.stdout.on('data', function(data) {
270 300
       // append the data to the output stream
@@ -282,7 +312,18 @@
282 312
         var exception = new Error(error);
283 313
         exception.status = status;
284 314
       }
285  
-      callback(exception, output);
  315
+      if (isMapped) {
  316
+        var mapOutput = fs.readFileSync(mapPath, 'utf8');
  317
+        fs.unlinkSync(mapPath);
  318
+
  319
+        output = output
  320
+          .replace(/[\s;]*$/, '\n//@ sourceMappingURL=' + path.basename(mapPath));
  321
+
  322
+        mapOutput = mapOutput
  323
+          .replace(/("file":)""/, '$1"' + path.basename(outputPath) + '"')
  324
+          .replace(/("sources":)\["stdin"\]/, '$1["' + path.basename(filePath) + '"]');
  325
+      }
  326
+      callback(exception, output, mapOutput);
286 327
     });
287 328
 
288 329
     // proxy the standard input to the Closure Compiler
@@ -350,13 +391,17 @@
350 391
    * @private
351 392
    * @param {Object|Undefined} exception The error object.
352 393
    * @param {String} result The resulting minified source.
  394
+   * @param {String} map The source map output.
353 395
    */
354  
-  function onClosureSimpleCompile(exception, result) {
  396
+  function onClosureSimpleCompile(exception, result, map) {
355 397
     if (exception) {
356 398
       throw exception;
357 399
     }
358 400
     result = postprocess(result);
359  
-    this.compiled.simple.source = result;
  401
+
  402
+    var simple = this.compiled.simple;
  403
+    simple.source = result;
  404
+    simple.sourceMap = map;
360 405
     zlib.gzip(result, onClosureSimpleGzip.bind(this));
361 406
   }
362 407
 
@@ -386,13 +431,17 @@
386 431
    * @private
387 432
    * @param {Object|Undefined} exception The error object.
388 433
    * @param {String} result The resulting minified source.
  434
+   * @param {String} map The source map output.
389 435
    */
390  
-  function onClosureAdvancedCompile(exception, result) {
  436
+  function onClosureAdvancedCompile(exception, result, map) {
391 437
     if (exception) {
392 438
       throw exception;
393 439
     }
394 440
     result = postprocess(result);
395  
-    this.compiled.advanced.source = result;
  441
+
  442
+    var advanced = this.compiled.advanced;
  443
+    advanced.source = result;
  444
+    advanced.sourceMap = map;
396 445
     zlib.gzip(result, onClosureAdvancedGzip.bind(this));
397 446
   }
398 447
 
@@ -412,8 +461,14 @@
412 461
     }
413 462
     this.compiled.advanced.gzip = result;
414 463
 
415  
-    // next, minify the source using only UglifyJS
416  
-    uglify.call(this, this.source, 'UglifyJS', onUglify.bind(this));
  464
+    // if mapped, finish by choosing the smallest compressed file
  465
+    if (this.isMapped) {
  466
+      onComplete.call(this);
  467
+    }
  468
+    // else, minify the source using UglifyJS
  469
+    else {
  470
+      uglify.call(this, this.source, 'UglifyJS', onUglify.bind(this));
  471
+    }
417 472
   }
418 473
 
419 474
   /**
@@ -538,18 +593,25 @@
538 593
 
539 594
     // select the smallest gzipped file and use its minified counterpart as the
540 595
     // official minified release (ties go to the Closure Compiler)
541  
-    var min = Math.min(
542  
-      compiledSimple.gzip.length,
543  
-      compiledAdvanced.gzip.length,
544  
-      uglified.gzip.length,
545  
-      hybridSimple.gzip.length,
546  
-      hybridAdvanced.gzip.length
547  
-    );
  596
+    var min = this.isMapped
  597
+      ? Math.min(
  598
+          compiledSimple.gzip.length,
  599
+          compiledAdvanced.gzip.length
  600
+        )
  601
+      : Math.min(
  602
+          compiledSimple.gzip.length,
  603
+          compiledAdvanced.gzip.length,
  604
+          uglified.gzip.length,
  605
+          hybridSimple.gzip.length,
  606
+          hybridAdvanced.gzip.length
  607
+        );
548 608
 
549 609
     // pass the minified source to the "onComplete" callback
550 610
     [compiledSimple, compiledAdvanced, uglified, hybridSimple, hybridAdvanced].some(function(data) {
551  
-      if (data.gzip.length == min) {
552  
-        this.onComplete(data.source);
  611
+      var gzip = data.gzip;
  612
+      if (gzip && gzip.length == min) {
  613
+        data.outputPath = this.outputPath;
  614
+        this.onComplete(data);
553 615
       }
554 616
     }, this);
555 617
   }
2  build/post-compile.js
@@ -49,7 +49,7 @@
49 49
 
50 50
     // add trailing semicolon
51 51
     if (source) {
52  
-      source = source.replace(/[\s;]*$/, ';');
  52
+      source = source.replace(/[\s;]*(\n\/\/.+)?$/, ';$1');
53 53
     }
54 54
     // exit early if version snippet isn't found
55 55
     var snippet = /VERSION\s*[=:]\s*([\'"])(.*?)\1/.exec(source);

0 notes on commit 929d76e

Mathias Bynens

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

John-David Dalton

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.

Leon Sorokin

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

John-David Dalton

Ya, good point. Will do.

John-David Dalton

@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.

John-David Dalton

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

Leon Sorokin

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.

Please sign in to comment.
Something went wrong with that request. Please try again.